# Security policy localharness custodies seeds and moves real money (`$LH`, sponsored gas). Read this before reporting, and read the trust model before assuming a bug: the security boundary is deliberately the host device, and keys are deliberately plaintext — see "Key custody and trust model" in [localharness.xyz/llms.txt](https://localharness.xyz/llms.txt). ## Supported versions Pre-1.0. Only the **latest published minor** (`0.x`) receives security fixes. Older minors are not patched; upgrade. ## Reporting a vulnerability Use **GitHub private vulnerability reporting** on [compusophy/localharness](https://github.com/compusophy/localharness/security/advisories/new) (enabled). Do not open a public issue with exploit details. If private reporting is unavailable to you, open a plain issue that says only "security report, need a private contact" — no details — and a maintainer will follow up. There is **no bug bounty program** pre-1.0. Reports are triaged best-effort by a solo maintainer; expect an acknowledgment within a few days, not an SLA. ## Scope Security-sensitive surfaces (in scope): - **Self-custody keys.** Identity seeds/keys are stored as plaintext by design (`~/.localharness/keys/.localharness.key` on the CLI; `.lh_wallet` in browser OPFS, exempt from at-rest encryption because it IS the key root). "The key file is readable by local processes" is the documented trust model, not a vulnerability. A path that leaks a key **off the device** — over the wire, into logs, to another origin — is very much in scope. - **The credit proxy** (`proxy/`): personal-sign auth, `$LH` metering, and the x402 settle path. Bugs that let a caller stream inference without being metered, or settle without a valid signature, are in scope. - **The sponsor relay** (`proxy/api/sponsor.ts`): the mainnet fee_payer signs server-side behind a selector allowlist, per-address rate window, gas cap, and a float circuit-breaker. Any way to make the relay sign outside those gates — or to spend the sponsor's fee-token float on non-gas value — is the highest-severity class here. - **x402 signatures** (X402Facet): replay past the one-shot nonce, ecrecover / EIP-1271 confusion, or bypassing the price-lock ceiling. - **The diamond owner key** (EIP-173 `owner()` / `diamondCut`): anything that lets a non-owner cut facets or reach owner-only selectors. - **wasm bundle integrity** (`web/`): the browser app is a static Vercel site; tampering paths (cache poisoning, CSP bypass, cross-origin postMessage abuse of the `?signer=1` / `?rpc=1` protocols) are in scope. Out of scope: - The **Moderato testnet** deployment and its embedded low-budget testnet sponsor key (loss capped at its balance, by design). - The **QA fleet personas** (`scripts/test-fleet/`) and their throwaway keys. - Local-attacker reads of plaintext key files (the documented custody model). - Social engineering, physical access, denial of service by paying for it. ## Incident response runbook Per key class, in the order an incident would demand: - **Sponsor relay key** (mainnet fee_payer, proxy env only — never in a build): generate a new key, move the remaining fee-token float to it, set `LH_SPONSOR_KEY` in the proxy's Vercel env, redeploy the proxy (`cd proxy && vercel --prod`). The old key holds nothing but gas float, so exposure is capped at whatever float it still had. This rotation has been done once already (the bundle-exposed testnet-era key was retired). - **Deployer / diamond owner key**: `transferOwnership` (EIP-173) on the diamond to a fresh key immediately; that key gates `diamondCut` and all owner-only facet methods, so it is the single most valuable key in the system. Then rotate any proxy env or script that referenced it. - **User seeds**: there is no server-side revocation — the remedy is the NFT-transfer race documented in the trust model (llms.txt): transfer the identity NFT to a secure wallet, which revokes all enrolled device signers. It only works if the owner moves before the thief does. - **Float drain in progress**: the relay's circuit-breaker (`LH_RELAY_MIN_FLOAT_WEI`) refuses to sign once the sponsor's fee-token balance drops below the floor, bounding a drain automatically. Response: let it trip (or set the floor high to halt sponsorship outright), rotate the sponsor key as above, then audit the selector allowlist and rate window in `proxy/api/sponsor.ts` before refunding the float. Disclosure after a fix is coordinated with the reporter; pre-1.0 the changelog entry is the advisory.