Skip to content
On this page

Security rationale ​

This page keeps the security rules whose reason the code cannot show: for each trust boundary, the rule, the design it rejects, and why. It does not describe the mechanisms or the residuals; the trust boundaries ledger owns both. A row exists only where a future change could plausibly reintroduce the rejected design.

Harness admission and the client allowlist ​

RuleRejected designWhy
Admission keys on the attested anchor (code signer or image hash); the client name is a log label onlyAdmit by the self-asserted client name (GENKAN_CLIENT_NAME)A name is a string any process can put in an environment variable; the anchor is what the kernel and the Security framework testify to about the running image
A signed client is paired with an explicit pair-client --signer anchor (--this-parent always pins the image hash); a hash anchor is for unsigned or ad-hoc builds and for LinuxPin the exact image hash for every clientA free Apple Development certificate re-signs about weekly and changes the cdhash each time, so a hash anchor would need a weekly re-pair; a control that nags weekly gets disabled
The first process to spawn the server is never enrolled automaticallyTrust on first useA silent first-use grant hands the slot to whichever process races first; enrollment is the user's act
No client paired yet ("clients": null in trust.json, or no record at all) means admission is not enforced, logged at ERROR on every start; a damaged or unreadable record refuses everyoneRead a damaged file as "unenrolled"A load failure read as unenrolled fails open
Revoking the last client leaves an empty list that admits nobodyReset the list to null when the last entry goesnull is how a first install starts; "the user revoked every client" must read as locked, never as reset

Host identity and attestation ​

RuleRejected designWhy
Where attestation is compiled in, a bridge peer is accepted only if it runs the same binary as the acceptor; the per-OS state is the table in the ledgerAn env-configurable list of trusted image hashesA list a same-user process can set through the environment lets it append its impostor's hash; "the same binary as me" is unforgeable by construction and needs no configuration
Self identity is measured at startup, before bind, accept, or dialMeasure self lazily on first useA later replacement of the binary file cannot redefine "self" and then be accepted as a matching peer

Revocation and the kill switch ​

RuleRejected designWhy
Admission is re-decided from a fresh read of the trust record on every request; the scoped epoch markers are change notices for the watchers, compared for inequality, never orderRe-decide only when the counter advancesA tamperer can write any value, and a bump that failed to persist leaves the counter stale, so a decision gated on the counter can serve a revoked client; a decision taken from the record itself cannot
A host that starts while killed stays up in control-plane modeRefuse the browser attach and let the host exitThe extension respawns the host every two seconds, so a kill would become a crash loop whose only exit is the CLI; control-plane mode keeps status and engage reachable
The extension's kill mirror allows on absent and refuses on malformedTreat an absent mirror as killedA fresh install has never heard from a host and the host enforces regardless; garbage where a record should be is evidence someone wrote there

Enrollment and user presence ​

RuleRejected designWhy
The CLI ceremonies that grant or restore capability (pair, pair-client, unkill, policy set) refuse a non-terminal stdin before any prompt, and their presence proof is a phrase typed on that terminalPrompt first, then check the terminal; or accept the phrase from any stdinA background script must not be able to flash an unexplained prompt at the user; echo release | genkan unkill cannot reopen the bridge
The host key is a software P-256 key in the OS credential store (keyring: the Keychain, the Credential Manager, the Secret Service), or in a file the user chose with pair --file-store, owner-only on Unix and under the runtime directory's ACL on WindowsA hardware-bound key available on macOS alone, whose every use demanded that OS's biometric promptPer-use presence comes from the browser's WebAuthn authenticator on every platform, which leaves the host key one job, identifying the installation to the extension, and a software key does that everywhere; the hardware key bound presence to macOS and left Linux and Windows no grant lane. Same-user readability is the accepted narrowing
--file-store is an explicit choice, and the file's presence IS the choice; a credential-store failure is reported, never silently redirected to the fileFall back to the file when the store errorsA store that is locked or refusing is indistinguishable from one that is absent, and a silent fallback moves the key out of the store without the user knowing
Presence for an act the extension asks for is a WebAuthn assertion the HOST verifies against a credential it enrolled; the extension's window may vouch only where the request's rule admits no enrolled credentialLet the extension report which surface satisfied presenceThe host is the enforcement point, and a verdict it does not verify is a bit any code on the browser side can set; a window answer for an enrolled browser would be the downgrade the presence ladder forbids
A credential is enrolled under one browser's label and answers only that browser's requests; enrolling another browser needs an assertion from any enrolled credentialOne pool of credentials answers every browserThe act runs for the browser connection that asked, and a second browser's authenticator vouching for the first would let a profile with its own key release a switch engaged against another; the cross-browser allowance at enrollment is what lets a second browser be enrolled at all
The first enrollment on a machine with none is trust on first use, re-checked under the trust-record lock at the write; every later one needs an assertion from an enrolled credentialGate the first enrollment on the CLI's terminal confirmation; or never trust on first useOn a fresh machine no credential exists to vouch, so the choice is first use or the terminal floor, and the floor would make a scriptable pty the root of trust for every browser; the lock-side re-check closes the race where two browsers both read an empty store
An assertion that fails verification is refused and audited and the request is consumed; the credential stays enrolledUnenroll the credential on a failed assertionA failed assertion is as often a stale request or a cancelled prompt as an attack, the sign counter already catches a clone of a counting authenticator, and unenrolling on failure would let anyone who can send one bad frame remove the browser's only hardware path, demoting it to the window
The WebAuthn verifier accepts attestation: "none" only, ES256 (COSE alg -7, P-256) only, and refuses an assertion whose signCount has not advanced when either side countsParse attestation formats and trust an authenticator's certificate chain; accept every COSE algorithm the key offersThe host's trust anchor is the credential key it recorded at enrollment on this machine, so an attestation chain adds parsing surface and no trust; one algorithm keeps the shipped crypto to the pure-Rust p256 verifier already in the graph (the same crate also compiles the signing half, which the WebAuthn path never reaches because the host holds no credential private key); a stalled counter is a cloned authenticator or a replay

Policy signing ​

RuleRejected designWhy
The host signs the exact stored policy bytes and the extension verifies the exact received bytesCanonicalize the JSON before signingCanonicalization is a parser-differential factory; with nothing to normalize there is nothing to exploit in the normalizer
Policy that only restricts needs no signature and travels unsigned; only a grant costs oneRequire a signature for every policy writeA presence prompt for routine tightening teaches users that prompts are routine, the reflex tap phishing needs; a forged restriction can only remove capability

MCP server behavior ​

RuleRejected designWhy
Known gap, kept: a tools/call whose params are malformed (no name, or arguments neither an object nor null) is refused by rmcp with -32601 before our handler, so no kill check or audit record firesPre-parse every frame ourselves to audit itNo browser action is possible on that path, and a second parser beside the SDK adds audit surface; the trail records well-formed calls only

Native-messaging protocol ​

RuleRejected designWhy
run.lock is parsed leniently; a change old readers must not survive gets a new, versioned filenameReject unknown fields in the lock file like every other on-disk recordThe lock is the one file an older build reads while a newer broker writes it, and it is discovery, not authorization; with a new filename old binaries see no lock and fail closed
Built from main at 25ae5f4Source: docs/security/rationale.md