feat(desktop): add non-secret exact-agent credential persistence attestation - #5312
Conversation
…station External controllers that assign work to a named managed agent need to verify that its credential is OS-keyring-backed, bound to exactly that agent, and not sitting in the inline JSON fallback - without any access to key material and without scraping managed-agents.json or the OS keychain. Adds buzz.desktop.exact_agent_credential_persistence.v1: a pure builder whose inputs cannot carry the nsec by construction, a read-only storage observation (raw pre-hydration store for inline detection, presence via load_all_readonly so it can never trigger migrate_legacy_key), one read-only Tauri command following the get_identity non-secret-projection precedent, a typed TS wrapper, and docs. Fails closed with attestation_keyring_unreachable / attestation_credential_missing rather than guessing; attestation_hash makes the object tamper-evident; the backend enum is extensible so a future secrets-provider backend becomes a new variant without breaking consumers. Signed-off-by: Ned Malki <ned@ottomato.ai>
wolfyy970
left a comment
There was a problem hiding this comment.
I reviewed the implementation at 96b4b06c20. I think the local storage signal is useful, but I don’t think this should merge as an external attestation in its current form.
The self-hash is only a checksum. A caller can change persistence_backend, stock_release_id, or issued_at and recompute it; there is no authenticated issuer, challenge, audience, expiry, replay protection, or launch binding. It cannot safely open the external compliance gate described in the PR.
The exact-agent and secret boundaries also are not yet true. An entry named agent:<pubkey> does not prove its stored nsec derives that pubkey, and load_all_readonly brings every stored agent key into this code path just to answer one presence question. That contradicts the stated no-secret path and widens exposure.
I would move the useful result into #2957 as narrow owner-local evidence: keyring verified, inline fallback, missing, unavailable, or mismatched. The storage layer should inspect only the target entry, and the UI should distinguish stored from used by the current launch. If an external controller needs a verifiable claim, that should be a separate challenge-bound signed receipt with an authenticated issuer and launch identity. A Tauri renderer command and self-hash are not that boundary.
|
@wolfyy970 you're right on all four points. Verifying them against the code rather than just agreeing: Self-hash. Correct, and my PR text calling it "tamper-evident" overstated it. Exact-agent. Correct. An entry named
One more, found while checking the above, which argues for #2957's approach specifically: Trust boundary. Agreed. A Tauri renderer command is owner-local IPC, not an external trust surface, and a verifiable external claim would need to be a challenge-bound signed receipt with an authenticated issuer and launch binding. That's a different design and shouldn't be bolted onto this one. Closing in favour of #2957. Thanks for the careful read. |
Summary
Adds a public, non-secret per-agent credential-persistence attestation (
buzz.desktop.exact_agent_credential_persistence.v1), so external controllers and downstream integrations can verify that a named managed agent's credential is OS-keyring-backed — exact-agent-bound, no inline fallback — without any access to key material and without scrapingmanaged-agents.jsonor the OS keychain.Proposed in #5311 (motivation, and how this relates to #2754 / #3721 / #4925 / #3205 — no duplicate found among open PRs).
What's in the change
managed_agents/persistence_attestation.rs— the schema (AgentPersistenceAttestation, extensiblePersistenceBackend) and a pure builder. By construction no code path can carry the nsec: inputs are a presence boolean, a keyring probe value, and public identity material. Fail-closed errors instead of guesses:attestation_keyring_unreachable,attestation_credential_missing.attestation_hash(SHA-256 of the payload with that field empty) makes the object tamper-evident;verify_attestation_hashis exported so callers and tests share one definition.managed_agents/storage.rs—readonly_agent_key_probe(viaload_all_readonly, so it can never triggermigrate_legacy_keyside effects) andobserve_agent_credential_persistence, which reads the raw persisted store: post-hydrate_keysrecords carry the nsec even in the normal keyring-backed case, so only pre-hydration JSON can distinguish inline fallback from keyring-backed.commands/agent_attestation.rs— one read-only#[tauri::command] get_agent_persistence_attestation(pubkey),spawn_blocking+ store lock, following thelist_managed_agents/get_identitynon-secret-projection precedent. Self-checks the hash invariant before returning.desktop/src/shared/api/agentAttestation.ts— thin typed wrapper. The wire shape stays snake_case verbatim: the hash binds the exact serialized payload, so client-side remapping would break external verification.docs/agent-credential-persistence-attestation.md— schema, semantics, and explicit non-goals (not NIP-TR, not a NIP-OA replacement, not a secret channel).Design notes
#[path]-included sibling_tests.rsfiles per the 1000-line file-size gate; the storage-side probe is tested against the existingFakeKeyStoreharness (present / other-agent's-entry / no-blob / outage / never-writes).system-keyringfeature attestinline_filehonestly rather than erroring — inline is a true statement about where the key lives there.KeyringProbe::Unreachableis an error, not an attestation: "may exist but unprovable this boot" must never read asos_keyring.nsecsubstring, so the no-secret guarantee is enforced by CI rather than by review attention.Verification
Run on Windows (
x86_64-pc-windows-msvc, toolchain 1.95.0):cargo test --libindesktop/src-tauri— 2156 passed, 1 failed, 10 ignored. The single failure ismanaged_agents::runtime::tests::claude_spawn_uses_the_probed_cli_executable, which passes in isolation (cargo test --lib claude_spawn_uses_the_probed_cli_executable) and is unrelated to this change — it mutates the process-widePATHand is order-dependent on this machine. The 11 new tests (7persistence_attestation, 4readonly_probe_*) all pass.cargo clippy -p buzz-desktop --all-targets --no-deps -- -D warnings— no findings in any file this PR touches. (Pre-existing findings elsewhere onmainat02f640b, e.g. unused imports incrates/buzz-terminalandmanaged_agents/nest.rs, are untouched by this PR.)cargo fmt --checkclean on all changed Rust files; biome clean on the new TS file.Happy to adjust surface details (command vs. settings export, field naming, casing) to maintainer preference.