VOR Stream 2026.1.2 is the second patch release on the v26.1 line and is primarily a security and operational reliability release. A broad sweep of CVE patches has been applied across the Go runtime, Django, and dozens of transitive Python and Node dependencies. Seven bugs reported from the field have been fixed in the engine, Django, and CLI, including two engine issues that affected correctness or reliability under load. A single new deployment capability rounds out the release: a custom Caddy imports directory that survives Ansible redeploys. There are no breaking changes; v26.1.2 is a drop-in upgrade from v26.1.1.
Highlights
Long-running database nodes no longer fail with 403 after a wait
A report came in from a v26.1.1 deployment: output nodes configured with db=mssql and an upstream getdyn= factor were failing with 403 permission denied from the engine’s database-credential helper. The same process worked when the wait was short, and failed when the upstream factor took 15 minutes or so to land.
The cause turned out to be ordering inside the generated worker code for the database I/O templates. The engine acquired its Vault-issued database credential after WaitSignals(...) returned, not before. Vault credentials are issued per node with a short time-to-live; a long wait outlives that TTL, so the post-wait credential fetch is rejected.
In v26.1.2, the database connection is acquired at the top of the worker function, before any wait. The credential is consumed while it is still valid, and the resulting *sql.DB is held open through the wait. Both the db=mssql and db=psql paths are fixed; the fix applies to the input-node read path as well as the output-node write path.
Who is affected
Processes with db=mssql or db=psql I/O nodes that also use getsig= or getdyn=. Processes where the awaited signal arrives in well under the credential TTL are unlikely to have seen this. Database nodes without an explicit wait, and waits on non-database nodes, were never affected.
No more phantom zero-rows from multi-threaded nodes
A separate deployment reported unexpected zero-valued rows appearing in the output of a multi-threaded Go model node: 100 phantom rows in a single run of an automotive LGD model, with the underlying data otherwise correct. The phantom rows were non-deterministic: they appeared under broker load and disappeared on quieter runs.
The cause was a race condition inside the engine’s multi-threading shutdown logic. Under heavy load on the message broker, the race allowed a stray “termination” signal from one worker thread to land in a sibling thread’s input queue as if it were a data row, surfacing as a zero-valued row in output.
In v26.1.2, the shutdown logic is no longer susceptible to this race. Multi-threaded nodes produce output that matches their single-threaded equivalent exactly, regardless of broker load.
Who is affected
Processes with multi-threaded Go, model, or Python nodes (anywhere threads > 1 is configured). Single-threaded nodes, SQL nodes, CSV I/O nodes, and S3 nodes are not affected. If you have ever observed unexpected zero-row data in output from a multi-threaded node, v26.1.2 is the upgrade that removes the cause.
Vault credential recovery on web nodes
Two related Django reliability issues have been fixed in this release.
The first: when Vault revoked dynamic Postgres credentials earlier than vault12factor’s cached lease expected (which happens during VM suspend/resume, clock skew, Vault restart, or manual revocation), the cached credentials were never refreshed and every subsequent Django query failed indefinitely with password authentication failed. A new pre-reconnect receiver clears the cached lease before the retry path runs, so the existing refresh hook fetches fresh credentials from Vault and the query succeeds.
The second: when Consul was healthy at uWSGI startup but had not yet registered Vault Agent, the Django Vault bootstrap loop’s narrow except clause did not catch the bare Exception raised by the “no healthy nodes” branch. The exception propagated out of settings.py, uWSGI dropped into “no python application found” mode, and supervisord could not restart it because uWSGI itself was still serving 500s. The exception type has been changed so the existing retry loop handles this case the same way it handles any other Consul transient.
Together with the existing token-file retry loop from v26.1.0 and v26.1.1, this closes the remaining gaps in Django’s Vault bootstrap and credential-refresh paths.
Spurious “Job Cancelled” popups on RHEL-style hosts
On hosts where the parent shell exports functions (for example, RHEL hosts with a which wrapper from /etc/profile.d/which2.sh), opening the Process Viewer for any run in Waterways showed a “Job Cancelled” popup containing shell error text. The generated run script was trying to export an environment variable whose name contained %% (bash’s encoding for exported functions), which bash rejected as not a valid identifier. The engine captured the stderr and surfaced it as a cancellation reason.
In v26.1.2, environment variable names are filtered against a POSIX-compliant shell identifier regex before being re-exported, so bash-function variables are skipped and the generated script runs cleanly.
Who is affected
Installations on RHEL and similar hosts where /etc/profile.d/ scripts export shell functions into the parent environment. The quickest check is env | grep ^BASH_FUNC_ on a super host: any matches mean this fix applies.
Getting Started guidePython nodes can import packages from the playpen venv
vor create process previously failed with import errors when a Python node depended on a package that had been installed only into a playpen-local virtual environment at <playpen>/venv. The build checked imports against the shared /opt/vor/venv runtime, so playpen-local packages (including editable setup.py develop installs) were invisible at build time even though they would have imported fine at run time.
In v26.1.2, the build auto-detects <playpen>/venv and runs its import checks against that interpreter when it exists. Playpen-local packages are visible, and vor create process succeeds without modifying the shared venv. Behavior is unchanged when no playpen-local venv exists.
The Python venv setup recipe in the Getting Started guide has also been rewritten around this: the shared venv is presented as the documented default runtime, the setup recipe is Python-version-agnostic (no longer hardcoding python3.9), and each shell step is its own copy-paste block.
Custom Caddy configuration that survives redeploys
A new admin-facing capability lands in this release. Caddy’s main Caddyfile is generated from Ansible templates during deployment, which means any manual changes to add custom routes or proxy targets get overwritten on the next deploy.
In v26.1.2, the main Caddyfile imports every file from etc/caddy.d/ under the VOR root. Administrators can drop site-specific configuration there and it will persist across deployments.
# Add a custom route
sudo -u vrisk vi /opt/vor/etc/caddy.d/my-route.conf
# Apply
sudo -u vrisk supervisorctl restart caddy
See the “Extending Caddy Web Server Configuration” page in the admin guide for the full recipe and the conflict-avoidance notes.
Security hardening
v26.1.2 includes a broad sweep of security updates across the runtime, server image, and application dependencies:
- The Go runtime has been upgraded to 1.26.3, picking up six runtime fixes.
- Django has been upgraded to address vulnerabilities in Django core and its packaged contrib applications.
- CVE patches have been applied across the Python and Node dependency trees, including updates to
urllib3,authlib,cryptography,idna,pip,pytest,gitpython,langchain-core,langchain-openai,langsmith,dompurify,hono,postcss,qs,fast-uri,ip-address,brace-expansion, andws. - Dependency scanning has been migrated to Google’s OSV-Scanner for recursive coverage across Python, npm, and Go manifests. New manifests are picked up without further CI configuration.
Did you see one of these symptoms?
If you experienced any of the following on v26.1.0 or v26.1.1, the underlying defect is fixed in v26.1.2:
- Phantom zero-valued rows in output from multi-threaded nodes.
db=mssqlordb=psqlnodes failing with Vault 403 after a longgetsig=orgetdyn=wait.- Django stuck in
password authentication failedafter Vault revokes Postgres credentials. - uWSGI stuck in
--- no python application found ---when Consul boots ahead of Vault Agent. - “Job Cancelled” popup with
BASH_FUNC_...%%: not a valid identifieron RHEL hosts. vor create processfailing with spurious import errors for packages installed into a playpen-local Python venv.vor create secret --cisilently accepting an invalid--sslmode and failing later at connect.
Upgrade Notes
Drop-in upgrade
v26.1.2 is a drop-in upgrade from v26.1.1 with no breaking changes and no required migration steps. Installations running v26.1.0 or v26.1.1 can upgrade directly.
If you have been working around any of the fixed bugs (phantom zero-rows from multi-threaded nodes, 403s on long-wait database nodes, “Job Cancelled” popups on RHEL hosts, or import errors when building Python nodes against a playpen-local venv), v26.1.2 is the upgrade that removes the cause.