Skip to content

v2.7.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 18:22
· 111 commits to main since this release
be643dd

[2.7.0] - 2026-10-09

Added

  • The cage layer (vaara cage, src/vaara/cage/, docs/cage.md). The containment an agent runs in becomes a choosable part with a driver interface: start, stop, status, enforcement state and events, the same for every cage. Twelve drivers ship, each over the cage's own tool or published API, unmodified: vaara-cage (vaara run under the Linux OS layer), openshell (NVIDIA OpenShell), codex (the OpenAI Codex sandbox), sandbox-runtime (Anthropic's srt), nono, gvisor and kata (as container runtimes through docker or podman), agent-sandbox (the Kubernetes SIG Sandbox object), firecracker, microsandbox, e2b (self-hosted infra over its HTTP API and envd) and apple-container (Apple's container, a Linux VM per container on a Mac). Every decision record now carries a cage block, and the signed decision receipt carries it as cage: which cage the deciding process ran in (a driver name, or none), the cage's name and version, sha256: over its effective configuration, and whether the kernel confirmed the confinement on the deciding process at decision time: the vaara-agent AppArmor label, a seccomp filter with no_new_privs, bubblewrap as the pid namespace's init, gVisor's own kernel log, or a hypervisor under the CPU, each named as the block's basis. A microVM gets the declaration on its kernel command line. A run outside any cage writes {"driver": "none", "confirmed": false}. vaara run hands the tree the declaration, and the guard's status reports the digest of the rendered profile. vaara cage observe prints the block a decision made by the calling process would carry. vaara cage drivers says which cages are ready on the machine: for each driver, the operating systems it runs on (Linux, macOS, Windows, or any for the cluster and service clients), whether its tools are on PATH, the version the cage reports, and the install hint for whatever is missing. --json prints the same. vaara menu gains the entry.
  • Hardening and locked egress for the Vaara cage. vaara os-layer harden on makes every vaara run launch start with no_new_privs and a seccomp filter (vaara-seccomp/1) that refuses module loading, kexec, eBPF, perf, ptrace and cross-process memory access, mounts and the new mount API, swap, reboot, the keyring, userfaultfd, open_by_handle_at, clock setting and io_uring, and every system call of a foreign ABI. vaara os-layer egress HOST... also locks the network: Landlock lets the launch open TCP connections only to an egress proxy vaara run starts for it, UDP and raw IP sockets are refused, and the proxy lets out the hosts listed (example.com, *.example.com, host:port), refusing names that resolve to loopback, link-local or multicast addresses. Every connection is a decision on the trail as egress.connect. With either switch on, the floor renders the tool profile as a stack (Cix -> &tool), which AppArmor allows under no_new_privs. A layer the kernel cannot apply stops the launch. Both are off unless set, and the profile is unchanged then. docs/cage.md lists what the layers do and do not cover.
  • SOC 2 Trust Services Criteria in the compliance engine and on the records. The default engine assesses six common criteria (CC6.1, CC6.2, CC6.3, CC7.2, CC7.3, CC8.1) beside the EU AI Act and DORA bundles, and the report names each with its heading and the records behind it. Records of data, infrastructure and identity actions carry the criterion tags, as do escalations, policy overrides and signing-key changes whatever the action was. Before this the taxonomy knew the SOC 2 domain for three identity actions and nothing read it. docs/COMPLIANCE.md lists the criteria and what each one gets.
  • vaara llm-proxy --policy FILE scores the tool calls in model replies against a policy, the format vaara serve --policy takes. A call no deny rule matches is scored under its own name, and with --enforce a call the policy denies or holds for a human is taken out of the reply and replaced by text naming the policy verdict. The proxy has no approval channel, so a held call is refused and the hold is closed on the record as a deny by policy. An invalid policy stops the proxy at startup. Without the flag nothing changes.
  • The Claude Code hook takes a policy file: "policy" in ~/.vaara/claude-code/config.json, or VAARA_PLUGIN_POLICY. It replaces the preset and custom thresholds, and every call that passes the deny rules is scored against it, shell and file calls included. A policy that cannot be read or fails validation blocks every call with the reason, unless the hook is in watch mode or fail_open is set. Without the key nothing changes.

Fixed

  • The PROV-JSON export (vaara trail export-prov) emits aiact:satisfies and dora:satisfies on records the trail wrote. It matched the domain labels "EU AI Act" and "DORA", while records carry the values eu_ai_act and dora, so a real export never carried either attribute; only the hand-built records in its tests did. Both spellings now land on the prefix, and soc2:satisfies joins them.
  • Bash deny rules no longer match text inside a heredoc body that no shell runs. A note mentioning netcat or rsync, written with cat > notes.md <<EOF, was refused as shell_netcat_egress or shell_copy_egress, although the Write tool carries the same bytes and no Bash rule reads them. Every rule now skips a body that cat or tee writes. A body read by Python or Node is skipped only by rules with the new shell_syntax key (rm_rf_root, shell_copy_egress, shell_netcat_egress); the metadata address and trail rules still read it. A body piped into a shell, sent over ssh, written to /dev/tcp, or read by any other command is matched as before, and so are the lines that open and close each heredoc.
  • The Hugging Face Space demo allowed every preset, including a transfer of 10,000 EUR and dropping the customers table. It built a Pipeline() with no policy, and every preset scored under the default allow line. The Space now loads policy.json, which escalates the transfer, the user delete and the public post, denies the table drop and allows the read, and shows that policy below the result. Each visitor gets a separate pipeline, so one visitor's calls no longer move another's scores. The app passes its theme and CSS to launch(), since Gradio 6 no longer accepts them on Blocks() and the pinned version failed at startup.
  • The shell command, outbound email and transfer of 250,000 that v2.5.0's model proxy let through in enforce mode also went through the llm-proxy and the Claude Code hook, and neither could be given the operator's policy. The llm-proxy decided tool calls by the deny rules alone, and no rule names any of the three; the hook passed a shell call that deletes nothing without scoring it, and could not load a policy. tests/test_hostile_replay_per_surface.py replays the three calls on the hook, the MCP proxy, the model proxy and the llm-proxy, each loaded with one policy, and requires each to be refused or held while a plain read still passes.
  • vaara-mcp-proxy takes --upstream-arg values that start with a dash. Given --upstream-arg -y, argparse read -y as an option and the proxy exited at startup, so the configs in the README, docs/adapters.md and the SAP and GitHub demos that pass flags to the upstream (npx -y, docker run -i --rm) did not start. The SAP demo README now lists the four records a call writes and the arguments vaara trail export requires.