You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
[2.0.0] - 2026-09-26
Upgrading
A tool call whose Vaara hook cannot answer is now blocked on Claude Code, Codex and Gemini CLI; see Fixed. Hooks written by 1.x let the call run. Re-run vaara init to rewrite them. Codex skips the rewritten hook until it is trusted again. "fail_open": true or VAARA_PLUGIN_FAIL_OPEN=1 lets such calls run.
The GitHub Action's major tag is v2. vaaraio/vaara@v1 stays on 1.x.
Fixed
A tool call ran when its Vaara hook could not answer. Claude Code, Codex and Gemini CLI all let the call through when the hook crashed. Claude Code and Codex also let it through when the hook binary was missing, and Claude Code when the hook outlived its timeout. A Claude Code approval wait of 60 seconds sat past the plugin's 30 second hook timeout, so an escalated call waiting for a person ran once the timeout hit. vaara init now writes the PreToolUse hook for Claude Code, Codex and Gemini CLI as a short sh command that runs vaara hook pre-tool-use under an 80 second deadline, inside the 90 second timeout it now sets on each: exit 0 and exit 2 pass through, and a crash, a missing binary or a hook still running at the deadline blocks the call with the reason. The plugin's run.sh does the same, and blocks when neither vaara nor python3 can be found. An approval wait ends inside the deadline. "fail_open": true or VAARA_PLUGIN_FAIL_OPEN=1 lets such calls run, as for OpenCode. A call refused this way has no record on the trail, since the engine that writes records is what did not answer. Cursor already blocked on a failed hook through failClosed. tests/test_hook_gate.py breaks the engine each way under dash and bash; the Claude Code 2.1.280, Codex 0.156.1 and Gemini CLI 0.61.0 real-binary tests now also crash, delete and hang the engine behind the hooks init writes, and each binary refuses the call in every case. Re-running vaara init rewrites existing hooks. Codex treats the rewritten hook as a new one and skips it until it is trusted again, which init reports.
verify_phase3_attestation checked that the notary key differs from the Arbiter key only when the caller also passed the Arbiter public key, so an attestation the Arbiter notarised itself verified for anyone holding just the notary key. The check now runs on every verify, and an envelope whose Arbiter key identifier is missing or not 32 bytes fails. A test builds such an attestation by hand, bypassing the emit-side check, and verify rejects it with and without the log root.
A tools/call, resources/read or prompts/get refused by the MCP proxy's operator allow/deny lists left nothing on the chain. Each is now recorded as a deny.
A deny on the chain named neither the policy that denied it nor the ground. Every deny the pipeline, the Claude Code deny-rule hook and the MCP proxy write now carries policy_id and violation_type in its action_blocked data: the deny rule (deny_rule:<id>, policy_rule), the scorer (scorer:<class>, risk_threshold, or invalid_decision for a verdict outside the vocabulary), capability_attenuation (privilege_attenuation), the MCP proxy's operator allow/deny lists (operator_perimeter, perimeter_filter), or a scorer crash (scorer_failure). A custom scorer can return its own policy_id and violation_type. intercept() takes policy_id alongside policy_decision. Allow and escalate records gain no keys, and a deny written straight through AuditTrail.record_decision without them hashes as before.
Records deleted from the end of a live trail went unnoticed. What was left still chained from genesis to a shorter tail, so vaara compliance report called the trail intact and vaara trail export signed chain intact: True. The store now records the seq and hash of its newest record in audit_meta, in the transaction that writes the record, and load_trail, verify_chain and verify_chain_streaming report the trail broken when that record is gone or replaced. The next record written after such a loss chains to the recorded head and takes a seq above it, so the loss stays a break in the chain after the head moves on. A purge that removes the head marks it as purged, which is retention and not reported; a purge cannot clear a loss that was already there. A tenant-scoped backend keeps its own head. A trail written before this version gets a head at its next write. The head sits in the same file as the rows, so it catches deletion by anyone who does not also rewrite it; vaara receipt verify-decision --db checks the trail against the signed decision receipts beside it. tests/test_trail_tail_truncation.py covers each case, and the compliance report end to end.
Changed
docs/COMPLIANCE.md and docs/OVERT_CONTROLS.md said Vaara does not act as an MCP client and that governing a third-party MCP server needed adapter work. vaara-mcp-proxy does that: it fronts any upstream server over stdio or Streamable HTTP, and a tools/call reaches it only after the operator lists, the deny rules and intercept() allow it. The MCP section now says so, and marks MCP-1 and MCP-3 as partial: per-call governance applies, identity measurement and change detection of the upstream server do not. docs/OWASP_AGENTIC.md said every audit record carries a policy id; it now says which records carry what.