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.3.0] - 2026-09-28
Added
vaara-mcp-proxy checks a tools/call against the tool's own inputSchema before the call is scored or forwarded. The proxy keeps the schema each tool advertised in tools/list; arguments outside it are refused, and the refusal lands on the chain as action_blocked with policy_idupstream_input_schema, violation_typeparameter_schema, and a reason naming each failing parameter. The checker reads type, required, properties, additionalProperties, enum, const, numeric bounds, string length and pattern, and array items and length. It does not evaluate anyOf, oneOf, allOf, not or $ref, and the gate record says so (parameter_schema:pass_partial:<keywords>). In shadow mode a violation is recorded and the call proceeds. Under fan-out the schema is kept per upstream, so two upstreams that each expose a tool of the same name are checked against their own shapes. docs/COMPLIANCE.md marked OVERT TOOL-2.2 and TOOL-2.3 as met when nothing checked parameters.
A deployer can override a guardrail-to-article mapping without adapter code: override_mapping, remove_override, clear_overrides and load_overrides(path) (a JSON list of rows) in vaara.integrations._content_safety_articles. Every adapter reads through lookup, so an override reaches the findings they produce. docs/COMPLIANCE.md already said this was possible; there was no way to do it.
Security
A seal placeholder no longer says anything about the secret it stands for. It was the first 48 bits of the secret's plain SHA-256, and the provider sees every placeholder, so anyone holding a prompt could confirm a guess at a named secret offline by hashing candidates, which for a phrase or a concept name is a short search. The placeholder is now HMAC-SHA256 under a key the proxy makes once beside the seal file (keys/seal-hmac.key, mode 0600, under the same deny rules as the other keys). Placeholders stay stable across restarts under that key, so provider prompt caching is unaffected. They change once at the upgrade, and a value learned by --seal-known-secrets is keyed the same way. A registry with no file, and one whose key cannot be read or written, uses a key drawn for the process and says so.
The llm-proxy tool gate decides a streamed tool call that never reaches its terminal event. It held each call from its first event to its last and decided it there, and released whatever was still held when the stream ended. A reply whose finish_reason, content_block_stop or output_item.done never came, whether the provider left it out or the connection was cut, therefore handed the agent a call no rule had read, and after the [DONE] sentinel. A held call is now decided on what arrived, at the message's end event or when the stream closes, and a refused one is replaced in place as at its terminal event. An allowed one is still released as received.
vaara dashboard refuses to bind an address other than loopback unless --allow-network is passed. The page carries the write token for policy thresholds, the hook config and OS-layer approvals, and GET /api/config serves it to any client that can reach the port, so on --host 0.0.0.0 anyone on the network could change thresholds and approve pending requests, while the startup line still said the dashboard was not reachable from the network. With the flag the dashboard binds and says what the bind exposes.
vaara dashboard answers only to requests addressed as an IP address or localhost. It binds 127.0.0.1 on a fixed port and guarded writes with a token that GET /api/config returns to its own page, so a page whose name was re-resolved to 127.0.0.1 could read the token and then change the policy thresholds, the settings and the OS layer, and read the trail summary. Any other Host now gets 403.
The cross-site guard on the local HTTP surfaces (vaara llm-proxy, the inference proxy, the inference console, vaara-mcp-proxy over HTTP) is no longer open to DNS rebinding. It treated the request's Host as the server's own origin, so a page whose name was re-resolved to 127.0.0.1 sent a matching Origin and Host and was let through, and its same-origin reads carried no Origin at all. Host now counts as the server's own only when it is an IP address, localhost or a .localhost name, or a host from the allowed origins. A browser request (one carrying Origin or Sec-Fetch-Site) addressed to any other name is refused. Clients that send neither header, such as curl, the SDKs and agent harnesses, are not affected. A console reached through a reverse proxy under its own host name needs that origin in its allowed-origins setting.
Fixed
The README and docs/supported-platforms.md said the package had no runtime dependencies. Since 2.2.0 the base install carries cryptography, rfc8785 and cbor2 so a plain install signs receipts. Both pages now name them, and tests/test_readme_numbers.py fails when the claim and pyproject.toml disagree again.
vaara review resolve --audit-db writes the operator's resolution as a human disposition. The ESCALATION_RESOLVED row it appends, the Article 14(4)(d) override evidence, carried the reviewer's name but not approver and human_disposed, so the row and the receipt built from it read as a policy disposition. It now carries approver: human and human_disposed: true like the approvals handshake does.
A tool call the llm-proxy gate refuses lands on the chain with policy_iddeny_rule:<rule id>, as the same refusal does from the plugin hook and the MCP proxy. The gate's denies carried the generic policy, so a reader looking for a rule's denials by policy_id did not find the ones the gate made.
scripts/release_prepare.sh bumps CFBundleShortVersionString in both macOS bundles with the other version files, reads each back, and stages them. It had not heard that 2.2.0 tied the bundles to the package version, so its own test step would have stopped every release from here on. tests/test_release_script_bumps_every_version_file.py holds the script to the files tests/test_version.py checks.
vaara compliance report tells a declared gap from a break. Every article is still pinned to evidence_insufficient and absent on a trail that is not intact, but when the only breaks are gaps a repair declared on the chain, the gap text says so and names the lost sequence numbers and the declaring record. It used to call every non-intact trail compromised, so a regulator could not tell a recorded loss from a rewrite. docs/VERDICTS.md describes both readings.
A human approval at the inference proxy's model-layer gate is recorded as a human disposition. The gate resolved the escalation through the same approvals handshake as the plugin hook, the MCP server and the OS-layer guard, but wrote the resolution without approver and human_disposed, so the record and the receipt built from it read as a policy disposition. It now carries approver: human and human_disposed: true like the other three.
Trail salvage copied the side tables (gdpr_redactions, api_keys, pending_outcomes) by the column names the damaged file declared. A column the current schema lacks made the whole table read as damaged, so its rows were dropped while the salvage reported success. Only the columns the fresh table has are read and written now.
write_decision refuses an action id that is not a plain file name. The dashboard passes the id from its request body into the decision file's name, so ../ in it wrote the decision outside the approvals directory when a matching request file existed there.
The macOS app and its network filter extension reported version 1.60.1 in their bundle, which Finder and the About panel show. Both now carry the package version, and tests/test_version.py fails when either drifts from pyproject.toml.
The EU trusted-list parser refused a DTD only in the first 4 KB of the document. Comments are allowed before the DTD, so one could be pushed past that offset. The whole document is checked now.
Changed
The Vaara GitHub Action (action.yml) pins actions/setup-python by commit SHA. It named the v7 tag, which its owner can move. Every workflow in the repository now pins its actions the same way, and Dependabot waits 7 days before proposing an update.
The release and republish workflows check mcp-publisher against the SHA-256 in the MCP registry release's checksums file before running it with the publish token, and download it over HTTPS only. They had piped the tarball straight into tar. The conformance desk grants its write permissions per job, and the refresh job no longer holds issues: write.
docs/COMPLIANCE.md and docs/OVERT_CONTROLS.md grade OVERT IDENT-1 as not met. Both said vaara.auth accepts an authenticated caller identity into the audit record; nothing in the tree calls that module's key store or role check, so no identity reaches a record through it. The rows now say what the HTTP surfaces do: one shared bearer key admits a caller, and the identity on a record is the agent_id the client asserts. tests/test_docs_ident1_claim.py holds the row to the code.
docs/COMPLIANCE.md: OVERT TOOL-1.2 names the gates that apply the capability policy (operator lists, deny rules, inputSchema, intercept()); it had named a policy.evaluate and policy-declared parameter ranges and destinations that do not exist. The tool-call attestation section names vaara.attestation.tool_call_attestation, with vaara.attestation.sep2787 as its deprecated alias.