Security release. Remediates the 9 findings with a ticket from an independent cold audit of 0.6.4 (2 high, 7 medium), plus defects found by four rounds of exhaustive adversarial validation of the fixes themselves. The recurring theme was again a divergence between what a policy checks and what actually executes on the same operand; every fix makes the decider and the executor share one canonicalizer and fail closed. New regression suite: tests/security_audit_run1.rs.
Install: cargo install --locked kvendra
Upgrade action required
KVENDRA_APPROVAL_MODEcan only tighten the signed approval policy. A looser value (e.g.silent) is ignored and audited unless the signed config opts in. Headless/Linux users who relied on the variable: runkvendra config approval set silent(signed), orkvendra config approval allow-env-downgrade onand keep the variable.- Profiles with local transfer operands need
local_roots.aws.s3_sync,aws.s3_cp,git.cloneandpypi.uploadnow require an absolutelocal_rootslist in the signed allowlist; without it those operations are denied. Add it and re-sign withkvendra secret set-allowlist. - Every primitive operation is classified; unclassified is destructive. Signed profiles that list
github.release,github.add_topics,git.cloneor unknown operation names withoutaccept_destructive: trueare refused until re-signed. Runkvendra secret validate --allafter upgrading. - Audit log layout v4. New rows use an injective, versioned MAC layout. Do not run a 0.6.4 binary against the vault after 0.6.5 has written v4 rows.
kvendra audit --verifynow exits non-zero on a broken chain.
Fixed — high
- SA1 —
kvendra.githubtarget-shape bypass. The enforcer derived the target repo only fromrepo/urland allowed the call when it could not, while the primitive also acceptedowner+repo_name/name: arepos-scoped profile authorized API calls against any repo the token reaches.primitives::github::resolve_targetis now the single resolver for the primitive and the enforcer;urlis ignored,nameis never a repo alias onrelease, owner/name must be safe path components,orgcompares the owner half, and a declaredrepos/repo/orgwith no resolvable target fails closed for every primitive. - SA2 — unconstrained local operand of brokered transfers.
s3_sync/s3_cpsource/destination,git clonedestination and the pypi distribution path were free-form, so any owner-readable directory could be copied to an allowed bucket with the owner's keys. Newprimitives::local_operandand DSL keylocal_roots(validated at sign time: absolute, no..,/only withaccept_broad_scope; re-checked at runtime);s3_syncandgit.cloneare destructive unconditionally; s3 transfers always pass--no-follow-symlinks.
Fixed — medium
- SA3 — approval env override outranked the signed policy.
resolve_mode_ratchetedreplacesresolve_mode; new signed[approval] allow_env_downgradeandkvendra config approval allow-env-downgrade on|off; every audited row past the gate carriesapproval_mode_*/approval_src_*/approval_env_{ignored,tightened}flags.config approval get|statuslabel unverified values as such. - SA4 — audit-chain MAC was non-injective and did not cover the layout version (v3→v2 downgrade stripped diagnostics undetected). Layout v4: length-prefixed, domain-separated fields with the version inside the MAC; legacy v1–v3 rows stay verifiable only in canonical form; new
kvendra audit commit-layoutappends one commitment row pinning all legacy rows (never rewrites them). Audit writes are serialised withBEGIN IMMEDIATE+busy_timeout, which also fixes a pre-existing chain fork with two concurrentkvendra mcpprocesses and a pre-existing chain break when a status update landed after a successor row. - SA5 — broker
template_idused as a cache file path. Newpath_id::is_safe_path_component(shared withprofile_id, now max 128 bytes, no leading dot) plus parent-equality containment. - SA6 — config writers laundered a tampered config into signed defaults (completes the 0.6.4 A5 fix).
Config::load_for_updateis the only loader writers use; integrity errors propagate and the file is left untouched;rebind-homeverifies before consuming a recovery code. - SA7 —
github.release/add_topicsmissing from the destructive catalog. Catalog completed; every operation is ruled or explicitly read-only; thekvendra capabilitiesmanifest is derived from the catalog (kvendra.git.destructive_opsnow includesclone). - SA8 — secret detection defeated by decoys. One shared selector for
detectandsanitize_output: per-match entropy gate over a bounded window, rejected and accepted matches are not consumed, overlapping spans of all providers are redacted in one pass over the original text, and the work cap never consumes a true positive (bounded patterns are always fully scanned; unbounded ones widen to their alphabet run). - SA10 — floating dependency closure.
Cargo.lockis versioned; CI builds and tests with--locked; a blockingcargo-denyjob runsdeny.toml; documented installs usecargo install --locked. Patch bumps: anyhow 1.0.104, rustls 0.23.45, rustls-webpki 0.103.15, webbrowser 1.2.4.
Hardening
- Hostile agent-supplied tool names, operations and profile ids are stored escaped and length-capped in audit refusal rows;
kvendra audit, the audit watch view and the dashboard escape control, bidi and zero-width characters. - CI:
test (windows-latest)green again (path-dependent tests made platform-aware).