From a180cbedec4abcafcac41a72a4c9c79d268701eb Mon Sep 17 00:00:00 2001 From: chrisjleal <184772742+chrisjleal@users.noreply.github.com> Date: Mon, 26 Oct 2026 01:08:40 -0500 Subject: [PATCH] The authority kit keeps its agent key outside the repository folder The agent's private key now lives in ~/.proofable/aad (or AAD_STATE_DIR), so a commit, archive or copy of the kit folder cannot carry it. A key left in ./.aad by an earlier version is moved there on first use. Co-Authored-By: Claude Opus 5.5 --- .../authority-at-dispatch/reproduce/README.md | 6 +++-- .../authority-at-dispatch/reproduce/aad.mjs | 22 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/public/evidence/aaif/authority-at-dispatch/reproduce/README.md b/public/evidence/aaif/authority-at-dispatch/reproduce/README.md index 3fe6e03..1702b07 100644 --- a/public/evidence/aaif/authority-at-dispatch/reproduce/README.md +++ b/public/evidence/aaif/authority-at-dispatch/reproduce/README.md @@ -8,8 +8,9 @@ record offline. - Node.js 20 or later - An access key (`npk_…`) from your own Proofable account: open [Profile, Account tab, Access keys](https://proofable.me/profile?tab=account) and create one. The - cases run agent jobs, which use your account's credits on any plan. The agent wallet and the run folder are - created on your machine. Keep the key out of chat and version control. + cases run agent jobs, which use your account's credits on any plan. The agent's private key is kept in + `~/.proofable/aad`, outside this folder, and the run folder is created here. Keep both keys out of chat and + version control. ## Run it @@ -52,5 +53,6 @@ as the service observed them. | `AAD_OPERATOR` | required | your name or organization, recorded in every record | | `AAD_AGENT_ID` | `aaif-authority-review` | review agent id; give each reviewer sharing an account their own | | `AAD_RUN_ID` | `aad-` | run folder name | +| `AAD_STATE_DIR` | `~/.proofable/aad` | where the agent's private key is kept; keep it outside any repository | | `AAD_APPROVAL_HOLD_MS` | `0` | how long an approval stays unanswered before the hold ends | | `AAD_REVOKE_AFTER_MS` | `12000` | delay before the mid-job revocation | diff --git a/public/evidence/aaif/authority-at-dispatch/reproduce/aad.mjs b/public/evidence/aaif/authority-at-dispatch/reproduce/aad.mjs index 7c9a337..c85ccbf 100644 --- a/public/evidence/aaif/authority-at-dispatch/reproduce/aad.mjs +++ b/public/evidence/aaif/authority-at-dispatch/reproduce/aad.mjs @@ -18,12 +18,14 @@ * AAD_RUN_ID run id (default aad-) * PROOFABLE_MCP_URL default https://mcp.proofable.me/mcp * PROOFABLE_API_URL default https://api.proofable.me + * AAD_STATE_DIR where the agent key is kept (default ~/.proofable/aad, outside this folder) * * Every value in a record is read from the live service; a value it did not produce stays * null or an explicit not_* state. Revocation latency is not measured. */ import { createHash } from 'node:crypto'; -import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs'; +import { homedir } from 'node:os'; import { join, resolve } from 'node:path'; import { isDeepStrictEqual } from 'node:util'; import { verifyPortableProofEnvelope } from '@proofable/sdk'; @@ -34,8 +36,11 @@ const CONTRACT_ID = 'https://probityai.github.io/agent-evidence-observer/contrac const MCP_URL = process.env.PROOFABLE_MCP_URL || 'https://mcp.proofable.me/mcp'; const API_URL = process.env.PROOFABLE_API_URL || 'https://api.proofable.me'; const AGENT_ID = process.env.AAD_AGENT_ID || 'aaif-authority-review'; -const STATE_DIR = resolve('.aad'); +// The agent key lives outside the working tree, so a commit, archive or copy of this folder can +// never carry it. Earlier kit versions kept it in ./.aad; that file is moved on first use. +const STATE_DIR = process.env.AAD_STATE_DIR ? resolve(process.env.AAD_STATE_DIR) : join(homedir(), '.proofable', 'aad'); const WALLET_FILE = join(STATE_DIR, 'agent-wallet.json'); +const LEGACY_WALLET_FILE = resolve('.aad', 'agent-wallet.json'); const GRANT_CHAIN = 'eip155:8453'; const BASE_ACTIONS = ['read_context', 'read_proofs', 'create_proof', 'execute_jobs', 'run_command', 'manage_runtime']; const NO_RUN_ACTIONS = ['read_context', 'read_proofs', 'create_proof', 'execute_jobs', 'manage_runtime']; @@ -104,7 +109,15 @@ async function controllerWallet() { if (!/^0x[0-9a-f]{40}$/.test(address)) throw new Error('proofable_context returned no EVM primary account for this access key.'); return address; } +function moveLegacyWallet() { + if (existsSync(WALLET_FILE) || !existsSync(LEGACY_WALLET_FILE)) return; + mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 }); + writeFileSync(WALLET_FILE, readFileSync(LEGACY_WALLET_FILE), { mode: 0o600 }); + unlinkSync(LEGACY_WALLET_FILE); + console.log(`Moved the agent key from ${LEGACY_WALLET_FILE} to ${WALLET_FILE}, outside this folder.`); +} function agentWallet() { + moveLegacyWallet(); if (!existsSync(WALLET_FILE)) throw new Error('No agent wallet yet: run `node aad.mjs setup` first.'); const record = JSON.parse(readFileSync(WALLET_FILE, 'utf8')); return { address: lower(record.address), signer: new Wallet(String(record.privateKey)) }; @@ -113,10 +126,11 @@ function agentWallet() { // ── setup: the operator's own review agent and delegation ─────────────────────────────── async function setup() { const operator = requireOperator(); - mkdirSync(STATE_DIR, { recursive: true }); + mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 }); + moveLegacyWallet(); if (!existsSync(WALLET_FILE)) { const wallet = Wallet.createRandom(); - writeFileSync(WALLET_FILE, `${JSON.stringify({ address: wallet.address, privateKey: wallet.privateKey, createdAt: nowIso() }, null, 2)}\n`); + writeFileSync(WALLET_FILE, `${JSON.stringify({ address: wallet.address, privateKey: wallet.privateKey, createdAt: nowIso() }, null, 2)}\n`, { mode: 0o600 }); try { chmodSync(WALLET_FILE, 0o600); } catch { /* best effort on Windows */ } console.log(`Created agent wallet ${wallet.address} in ${WALLET_FILE} (keep it private; it never leaves this machine).`); }