{"_id":"@askalf/strongroom","_rev":"8-267109bf1f383e5f6e0f4f74564ced6e","name":"@askalf/strongroom","dist-tags":{"latest":"0.5.0"},"versions":{"0.0.1":{"name":"@askalf/strongroom","version":"0.0.1","license":"MIT","_id":"@askalf/strongroom@0.0.1","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"dist":{"shasum":"cb99a626053acae47b3b3b399f55842f2a8e15eb","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.0.1.tgz","fileCount":2,"integrity":"sha512-P0EEYoUXRHAbhIEawv6MWmhPoLcFAD5g7pcEzpfxBidZr57Xwq5JtvO6oUOIyeQS3xOhvVbPv4upV5F0n+Mmfw==","signatures":[{"sig":"MEQCIFw9ygwa9IGYenCxypRfbtNohC2WV42lFa3qfpsHV/g2AiBfUyM6jcTIdKXdQKEs7cC0C8K+MapuYBCAuqEVmxcSxg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":379},"_npmUser":{"name":"askalf","email":"support@askalf.org"},"_npmVersion":"12.0.1","description":"agent secrets: leases not keys — name reservation for the rename of keeper (Own Your Stack). Real release imminent: https://github.com/askalf/keeper","directories":{},"_nodeVersion":"22.22.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.0.1_1783729375078_0.9295055391489122","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"},"0.1.1":{"name":"@askalf/strongroom","version":"0.1.1","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"author":{"name":"Thomas Sprayberry"},"license":"MIT","_id":"@askalf/strongroom@0.1.1","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"homepage":"https://github.com/askalf/strongroom#readme","bugs":{"url":"https://github.com/askalf/strongroom/issues"},"bin":{"keeper":"src/cli.mjs","strongroom":"src/cli.mjs"},"dist":{"shasum":"a5d8bb4c26eaa3467acd9084760cc69db1f2f2a9","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.1.1.tgz","fileCount":13,"integrity":"sha512-UrccljtfwsUODPgLEV3I3QV0c3dGWy8HOdx5kqc7H9T+EUcp/fh9Xssk0x3ISORbO4ANE2ZwG7wLoCDd1Ztilg==","signatures":[{"sig":"MEYCIQClzsyvgLXOO7M0ye9IhAIqHWfM28Ym2isviUHUdLionwIhALqpwq4Lp1AYrI8ASFofFkyh+BGCbbM+woxiYH/qBZPD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@askalf%2fstrongroom@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":78409},"type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.mjs","./broker":"./src/broker.mjs","./client":"./src/client.mjs","./daemon":"./src/daemon.mjs"},"gitHead":"7dcacc036128c2656dfa1ed44ce8e863da54cff9","scripts":{"demo":"node demo/demo.mjs","test":"node --test","demo:platform":"node demo/platform.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4412a936-bd17-4d42-91a6-86d6e9a4b632"}},"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"_npmVersion":"11.16.0","description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"@askalf/redstamp":"github:askalf/redstamp#3da60b9d720cb3f152f3db906673be0a2f8116b7"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.1.1_1783732079292_0.674967614584165","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"},"0.2.0":{"name":"@askalf/strongroom","version":"0.2.0","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"author":{"name":"Thomas Sprayberry"},"license":"MIT","_id":"@askalf/strongroom@0.2.0","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"homepage":"https://github.com/askalf/strongroom#readme","bugs":{"url":"https://github.com/askalf/strongroom/issues"},"bin":{"keeper":"src/cli.mjs","strongroom":"src/cli.mjs"},"dist":{"shasum":"61889e160eba35f4a33aae77cc545c79d7e394f3","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.2.0.tgz","fileCount":13,"integrity":"sha512-26zqQqJy5xaMZJWEH9vALjpuuE0qvkAHsaxyptpjQ+V6sdW60GqStA6VoeBefruQoL/LoNSa3csQMl2aIoy7iw==","signatures":[{"sig":"MEYCIQCYPzFGC1EYL4yqy2oEQSMEM+wGPNOuDXPCvKS9W1Yi3QIhAOmNXMzSEPSGNIaxHwURk7lbi+dOUiRc304ulOw1OM5T","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@askalf%2fstrongroom@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":87624},"type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.mjs","./broker":"./src/broker.mjs","./client":"./src/client.mjs","./daemon":"./src/daemon.mjs"},"gitHead":"3452cfdaafa9927ef0acc99a9a0d613b70ddc568","scripts":{"demo":"node demo/demo.mjs","fuzz":"node fuzz/run.mjs","test":"node --test","demo:platform":"node demo/platform.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4412a936-bd17-4d42-91a6-86d6e9a4b632"}},"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"_npmVersion":"11.16.0","description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"@askalf/redstamp":"github:askalf/redstamp#3da60b9d720cb3f152f3db906673be0a2f8116b7"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@jazzer.js/core":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.2.0_1783790425235_0.46801580756948535","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"},"0.2.1":{"name":"@askalf/strongroom","version":"0.2.1","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"author":{"name":"Thomas Sprayberry"},"license":"MIT","_id":"@askalf/strongroom@0.2.1","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"homepage":"https://github.com/askalf/strongroom#readme","bugs":{"url":"https://github.com/askalf/strongroom/issues"},"bin":{"keeper":"src/cli.mjs","strongroom":"src/cli.mjs"},"dist":{"shasum":"ba3f7e7e50a426882fb33a48d365a6af337488cb","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.2.1.tgz","fileCount":13,"integrity":"sha512-V233EmQkoruosCMd9EE3wl/TAW9AJw1thqoWgrnmypUCi/s8UKtHV0lc1hzK1uGhrGPhdUwBlXBLGCfuc/LRPA==","signatures":[{"sig":"MEUCIQCS/vo6X6z1PdGSdTOif0ZBtq0Eo9sEAH85rEG5qcxirwIgFr7w5vvjedIzef9qKKpXtM/ROUzJ1yBQbdQzpuU1BrA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@askalf%2fstrongroom@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":87624},"type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.mjs","./broker":"./src/broker.mjs","./client":"./src/client.mjs","./daemon":"./src/daemon.mjs"},"gitHead":"baea828770cf850300d0cc107e0af50c60d5b7ae","scripts":{"demo":"node demo/demo.mjs","fuzz":"node fuzz/run.mjs","test":"node --test","demo:platform":"node demo/platform.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4412a936-bd17-4d42-91a6-86d6e9a4b632"}},"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"_npmVersion":"11.16.0","description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"@askalf/redstamp":"github:askalf/redstamp#3da60b9d720cb3f152f3db906673be0a2f8116b7"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@jazzer.js/core":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.2.1_1783811365567_0.0005055220644607505","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"},"0.3.0":{"name":"@askalf/strongroom","version":"0.3.0","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"author":{"name":"Thomas Sprayberry"},"license":"MIT","_id":"@askalf/strongroom@0.3.0","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"homepage":"https://github.com/askalf/strongroom#readme","bugs":{"url":"https://github.com/askalf/strongroom/issues"},"bin":{"keeper":"src/cli.mjs","strongroom":"src/cli.mjs"},"dist":{"shasum":"b625af6bc3c6e4d985f54a226fc7bc9f3c471c46","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.3.0.tgz","fileCount":13,"integrity":"sha512-n4ufpGkhENPNNv9+yIWzdybmeLZJoPGfwXV0qjCIen2YKfFq0Wc+t2aLoZv0KCLa33vOKviBbZi0ZYxSxEuGaA==","signatures":[{"sig":"MEYCIQDSh2wzDEC/LbLgn4gti90YgL6MJSk69x7aD3pd7eA38wIhAJhAQcjNZyaZVlws978uhzk045o9uDwegLq2IhFc0NwZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@askalf%2fstrongroom@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":103413},"type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.mjs","./broker":"./src/broker.mjs","./client":"./src/client.mjs","./daemon":"./src/daemon.mjs"},"gitHead":"189a2e76e49c2fb972d64e17791a43b9bb44e1cb","scripts":{"demo":"node demo/demo.mjs","fuzz":"node fuzz/run.mjs","test":"node --test","demo:platform":"node demo/platform.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4412a936-bd17-4d42-91a6-86d6e9a4b632"}},"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"_npmVersion":"11.16.0","description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"@askalf/redstamp":"github:askalf/redstamp#3da60b9d720cb3f152f3db906673be0a2f8116b7"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@jazzer.js/core":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.3.0_1784242296595_0.9980963803990677","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"},"0.4.0":{"name":"@askalf/strongroom","version":"0.4.0","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"author":{"name":"Thomas Sprayberry"},"license":"MIT","_id":"@askalf/strongroom@0.4.0","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"homepage":"https://github.com/askalf/strongroom#readme","bugs":{"url":"https://github.com/askalf/strongroom/issues"},"bin":{"keeper":"src/cli.mjs","strongroom":"src/cli.mjs"},"dist":{"shasum":"4ef3b064bc0c9070384f8bf7bb2c8f3277f33c4d","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.4.0.tgz","fileCount":14,"integrity":"sha512-w0OWBBChJuNMf4975E4KtMrjRuE+VKwmYA9BzHxY0ZvNb/o1TWRJ49h4t9gzXJNchEQ0zCXqgHuliT8Fj96s9Q==","signatures":[{"sig":"MEYCIQCEFOtXvaDOAOheT2/nVJDhhpi5BHjcpTAL4V6iIk3ldwIhAIqcPhl/8KwKQciGU3VcofwLqCZxPzUvpdGVNlC8i8Qr","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@askalf%2fstrongroom@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":114938},"type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.mjs","./broker":"./src/broker.mjs","./client":"./src/client.mjs","./daemon":"./src/daemon.mjs"},"gitHead":"94b653bf8e3157226fd9c16330711b5b10ce7414","scripts":{"demo":"node demo/demo.mjs","fuzz":"node fuzz/run.mjs","test":"node --test","demo:platform":"node demo/platform.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4412a936-bd17-4d42-91a6-86d6e9a4b632"}},"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"_npmVersion":"11.16.0","description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@jazzer.js/core":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.4.0_1784924377165_0.5525501857694937","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"},"0.5.0":{"name":"@askalf/strongroom","version":"0.5.0","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"author":{"name":"Thomas Sprayberry"},"license":"MIT","_id":"@askalf/strongroom@0.5.0","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"homepage":"https://github.com/askalf/strongroom#readme","bugs":{"url":"https://github.com/askalf/strongroom/issues"},"bin":{"keeper":"src/cli.mjs","strongroom":"src/cli.mjs"},"dist":{"shasum":"e21459046f4ddb73dadc4261d2cb41db8c419b73","tarball":"https://registry.npmjs.org/@askalf/strongroom/-/strongroom-0.5.0.tgz","fileCount":14,"integrity":"sha512-jZckJhDQ9eN10paOE8T5SOVwEy25ovQ/WyL1bBwqXge3sBNBsTu/wWvmyRsPnKxNd+ypw4bxmiVuLlO000AHuw==","signatures":[{"sig":"MEQCIEPVblBLiZ9W5/msPqzPA27gyFeNyl45d/pMCFm7BCp3AiB6ecmLvpRqjC+aUkMHWqQQZ63S+qRx4Vd6kUOeE0cVCw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@askalf%2fstrongroom@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":121112},"type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.mjs","./broker":"./src/broker.mjs","./client":"./src/client.mjs","./daemon":"./src/daemon.mjs"},"gitHead":"0cc74a627f140b44b5cb553cd56cd32106cb8820","scripts":{"demo":"node demo/demo.mjs","fuzz":"node fuzz/run.mjs","test":"node --test","demo:platform":"node demo/platform.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4412a936-bd17-4d42-91a6-86d6e9a4b632"}},"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"_npmVersion":"11.17.0","description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@jazzer.js/core":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/strongroom_0.5.0_1786927928067_0.04373443279296496","host":"s3://npm-registry-packages-npm-production"},"deprecated":"strongroom is archived and no longer maintained. Source:github.com/sprayberry-archive/strongroom"}},"time":{"created":"2026-07-11T00:22:54.901Z","modified":"2026-09-27T12:51:49.789Z","0.0.1":"2026-07-11T00:22:55.193Z","0.1.1":"2026-07-11T01:07:59.421Z","0.2.0":"2026-07-11T17:20:25.376Z","0.2.1":"2026-07-11T23:09:25.702Z","0.3.0":"2026-07-16T22:51:36.725Z","0.4.0":"2026-07-24T20:19:37.356Z","0.5.0":"2026-08-17T00:52:08.247Z"},"bugs":{"url":"https://github.com/askalf/strongroom/issues"},"author":{"name":"Thomas Sprayberry"},"license":"MIT","homepage":"https://github.com/askalf/strongroom#readme","keywords":["ai-agents","agent-security","secrets","credentials","vault","least-privilege","own-your-stack"],"repository":{"url":"git+https://github.com/askalf/strongroom.git","type":"git"},"description":"own your agent secrets — an encrypted vault that hands agents scoped, short-lived, single-use leases instead of raw keys, and audits every access. Part of Own Your Stack.","maintainers":[{"name":"askalf","email":"support@askalf.org"}],"readme":"<div align=\"center\">\n\n# strongroom\n\n**Own your agent secrets. An encrypted vault that hands agents scoped, short-lived, single-use leases — never raw keys.**\n\nThe key is revealed only at the egress point, only while the lease is valid.\n\n[![npm](https://img.shields.io/npm/v/@askalf/strongroom?color=blue&label=npm)](https://www.npmjs.com/package/@askalf/strongroom)\n[![downloads](https://img.shields.io/npm/dm/@askalf/strongroom?color=blue&label=downloads)](https://www.npmjs.com/package/@askalf/strongroom)\n[![zero dependencies](https://img.shields.io/badge/dependencies-0-blue)](package.json)\n[![ci](https://github.com/askalf/strongroom/actions/workflows/ci.yml/badge.svg)](https://github.com/askalf/strongroom/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/askalf/strongroom/actions/workflows/codeql.yml/badge.svg)](https://github.com/askalf/strongroom/actions/workflows/codeql.yml)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/askalf/strongroom/badge)](https://scorecard.dev/viewer/?uri=github.com/askalf/strongroom)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n<!-- strongroom on Glama — uncomment once the server is indexed in the directory (submit at https://glama.ai/mcp/servers; glama.json is already in place):\n[![strongroom on Glama](https://glama.ai/mcp/servers/askalf/strongroom/badges/card.svg)](https://glama.ai/mcp/servers/askalf/strongroom)\n-->\n<!-- OpenSSF Best Practices — uncomment once enrolled at https://www.bestpractices.dev and replace PROJECT_ID:\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/PROJECT_ID/badge)](https://www.bestpractices.dev/projects/PROJECT_ID)\n-->\n\n[Quick start](#quick-start) · [Egress broker](#egress-broker--the-agent-just-swaps-a-base-url) · [Redeem-daemon](#redeem-daemon--no-master-key-on-the-redeeming-side) · [Delegation](#delegating-a-lease--least-privilege-between-agents) · [Security model](#security-model) · [Commands](#commands)\n\n</div>\n\n---\n\n```bash\necho \"sk-live-…\" | strongroom add OPENAI_API_KEY          # stored encrypted\n\nLEASE=$(strongroom grant OPENAI_API_KEY --ttl 300 --uses 1 --host api.openai.com)\n# → the agent gets $LEASE — not the key\n\n# at the egress point, run the call with the key in the child's env only:\nstrongroom exec \"$LEASE\" --as OPENAI_API_KEY -- \\\n  curl https://api.openai.com/v1/models -H \"Authorization: Bearer $OPENAI_API_KEY\"\n\nstrongroom audit --verify                                 # tamper-evident access log\n```\n\nThe agent dispatched `strongroom exec <lease> …`; the key was decrypted inside strongroom and handed to the subprocess's environment — it never entered the agent's context, stdout, or logs. Run the whole story: `npm run demo`.\n\n## Why a lease, not the key\n\nAgents need credentials — API keys, tokens, passwords — to do anything useful. Today they get them the worst possible way: a long-lived key stuffed into an environment variable or, worse, into the prompt. OpenClaw leaked the keys of ~135k exposed instances exactly this way. A key in the model's context is a key in every log, every trace, and every place a poisoned tool can read.\n\n| | a raw key in env / prompt | a strongroom lease |\n|---|---|---|\n| in the model's context | **yes** — leaks to logs, traces, poisoned tools | no — only an opaque handle |\n| lifetime | until you rotate it | seconds (TTL) |\n| blast radius | every call, every host | one use, one host |\n| revocable | rotate everywhere | `strongroom revoke <lease>` |\n| audited | no | every access, tamper-evident |\n\n**strongroom holds the keys so the agent doesn't:**\n\n| piece | what it does |\n|---|---|\n| **vault** | secrets encrypted at rest (AES-256-GCM, name bound in as AAD). Never a plaintext env var, never in a prompt |\n| **lease** | `grant` mints an opaque handle bound to a **TTL**, a **use count**, and (optionally) a **destination host / upstream / paths / rate**. The agent's context holds the lease, not the secret |\n| **redeem** | exchange a lease for the secret at the point of use, *iff* it's still valid (not expired, uses remaining, host in scope). A denial is audited and never burns a use |\n| **audit** | every grant / redeem / deny / revoke is **hash-chained** (the same primitive [redstamp](https://github.com/askalf/redstamp) uses, vendored so strongroom installs with **zero dependencies**) with an authenticated tip — editing, truncating, or splicing the log breaks `strongroom audit --verify` |\n\n```mermaid\nflowchart LR\n    A[\"agent<br/>holds LEASE only\"] -->|\"base-URL swap\"| B[\"egress broker<br/>injects key at network boundary\"]\n    A -->|\"strongroom exec\"| E[\"subprocess env<br/>key never in agent context\"]\n    A -->|\"GIT_ASKPASS etc.\"| D[\"redeem-daemon<br/>local socket, keyless client\"]\n    B --> V[\"vault<br/>AES-256-GCM at rest\"]\n    E --> V\n    D --> V\n    V --> AU[\"hash-chained audit<br/>authenticated tip\"]\n```\n\nCompletes the agent-security stack: **redstamp** contains the call · **truecopy** vets the tool · **strongroom** holds the keys.\n\n> _**Formerly `keeper`.** Renamed to `strongroom` for the npm release; the GitHub repo redirects and the legacy `keeper` CLI alias keeps working. `KEEPER_*` env vars and the `~/.keeper` home directory are unchanged for compatibility._\n\n## Quick start\n\n> Install: `npm i -g @askalf/strongroom` (or run any command with `npx -y @askalf/strongroom`). **Zero runtime dependencies** — one package, no transitive tree, no `git` or GitHub reachability at install time, so it installs where the secrets actually live: air-gapped hosts, registry mirrors, and minimal CI images. (`npm i -g github:askalf/strongroom` also works, but that path *does* need git.)\n\nThe vault + lease + exec loop is the block at the top of this page. Three egress patterns cover the rest: the [broker](#egress-broker--the-agent-just-swaps-a-base-url) for HTTP APIs, the [redeem-daemon](#redeem-daemon--no-master-key-on-the-redeeming-side) for credentials a tool consumes directly, and the [MCP server](#mcp-server--leases-not-keys-over-mcp) when the control plane itself speaks MCP.\n\n## Egress broker — the agent just swaps a base URL\n\nRun the broker and the agent needs no key, no `exec`, no redeem — only a base-URL swap:\n\n```bash\n# bind a lease to ONE upstream, how to inject, which endpoints, and a rate cap\nLEASE=$(strongroom grant OPENAI_API_KEY \\\n  --upstream https://api.openai.com --inject bearer \\\n  --paths \"/v1/chat/*,/v1/models\" --rate 60 --concurrency 4 --ttl 600 --uses 100)\nstrongroom broker --port 8771 &\n```\n\nPoint the agent's client at the broker:\n\n```js\nconst openai = new OpenAI({ baseURL: `http://127.0.0.1:8771/${LEASE}`, apiKey: 'unused' });\nawait openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [/* … */] });\n```\n\nFor each call the broker redeems the lease (atomic + audited), makes the **real** upstream request itself with the secret injected (`Authorization: Bearer …`), and streams the response back. The key is injected at the network boundary — it never enters the agent's context, env, or logs. And because the lease is **bound to one upstream**, the secret can only ever go to that host; the agent can't redirect it. `--inject`: `bearer` (default) · `x-api-key` (Anthropic) · `Header-Name` (custom).\n\n**Scope it down further:**\n- `--paths \"/v1/chat/*,/v1/models\"` — restrict the lease to specific endpoints (glob; a chat lease can't reach billing or admin).\n- `--rate 60` — cap it at 60 requests/min.\n- `--concurrency 4` — cap simultaneous in-flight requests (a runaway or hijacked agent can't hold N parallel streams open through one lease).\n\nAll three are enforced **before** the secret is redeemed — an out-of-scope, over-rate, or over-concurrency request gets `403` / `429`, consumes no use, and is audited.\n\n**And the upstream call itself is bounded.** The broker's upstream request times out after **30 s** to first response headers (`strongroom broker --timeout <ms>`, or `KEEPER_BROKER_TIMEOUT_MS`). A black-hole upstream gets a `504` (audited as `deny`/`timeout`) instead of hanging the request forever — which also means a hung upstream can't pin a `--concurrency` slot and wedge the lease. The bound is to *headers*: a healthy streaming response is never cut off mid-stream.\n\n**And the response is sanitized on the way back.** If the upstream ever *reflects* the injected secret — an echo/debug endpoint, a verbose error, a misconfigured proxy — the broker redacts it from the relayed headers and body (`[strongroom:redacted]`) and records a `sanitize` audit event. The scan is streaming-safe: SSE passes through event-by-event, and a secret split across chunk boundaries is still caught. Without this, a reflecting upstream would hand the raw key straight back into the agent's context, defeating the injection boundary.\n\n> **Windows / Git Bash:** MSYS auto-rewrites an argument that looks like a Unix absolute path, so a bare `--paths \"/v1/models\"` reaches strongroom as `C:/Program Files/Git/v1/models` and silently never matches (every call then `403`s on `path`). A comma-list like `\"/v1/chat/*,/v1/models\"` is left alone, which is why it works. Prefix the run with `MSYS_NO_PATHCONV=1` (use drive-letter paths for any file args), or call strongroom from PowerShell/cmd. Not a strongroom bug — it mangles the arg before strongroom sees it.\n\n## Redeem-daemon — no master key on the redeeming side\n\nThe broker covers HTTP APIs. For credentials a tool consumes *directly* — git over `GIT_ASKPASS`, a CLI that reads a token — the redeem happens in the agent's own process tree, and a local `strongroom redeem` would need the master key there. The **redeem-daemon** removes that requirement:\n\n```bash\nstrongroom serve &                          # long-lived local process — HOLDS the master key\nKEEPER_DAEMON=1 strongroom redeem \"$LEASE\"  # this side holds NO key, NO passphrase\n```\n\nWith `KEEPER_DAEMON=1`, `strongroom redeem` / `strongroom exec` route lease→secret over a **local socket** (unix domain socket / Windows named pipe — token-gated, owner-only `0600`, never TCP) instead of opening the vault. Same-user callers need zero config — both sides share the default socket path, and the client reads the capability token from the daemon's `0600` info file; a **sandboxed worker** is instead handed only `KEEPER_SOCKET` + `KEEPER_DAEMON_TOKEN` (pin one via env before `serve`) and never reads strongroom's home at all. Either way the redeeming process never holds the master key: compromise it and you get its leases — scoped, expiring, revocable — not the vault. This is how a control plane hands git credentials to sandboxed workers: a `GIT_ASKPASS` helper that runs `strongroom redeem`, with zero token bytes on disk and zero key material in the worker.\n\n## MCP server — leases, not keys, over MCP\n\n```bash\nnpm install -g @askalf/strongroom-mcp\nstrongroom-mcp   # owns the vault + broker in-process; talk to it over stdio\n```\n\n`@askalf/strongroom-mcp` is the strongroom control plane as an MCP server. There is deliberately **no `add_secret` tool** — the operator loads secrets into the vault out-of-band with the CLI (`strongroom add NAME`), so no secret value ever crosses the MCP wire in either direction. `grant_lease` is the only credential-granting tool, and it returns a lease-backed base URL, never a key — safe to persist in agent history, logs, and traces. Point your MCP client at the `strongroom-mcp` binary; see [`mcp/README.md`](mcp/README.md) for the full tool list and client config, or [`examples/mcp-strongroom`](examples/mcp-strongroom) for a worked example.\n\n## Examples — real SDKs, zero keys in the agent\n\nThree end-to-end examples, each running a genuine client with a credential that never enters the agent's context:\n\n| Example | Shows |\n|---|---|\n| [`examples/anthropic-sdk-strongroom`](examples/anthropic-sdk-strongroom) | the **Anthropic SDK** (`@anthropic-ai/sdk`) making a real `messages.create` call through the broker — `x-api-key` injected at egress |\n| [`examples/openai-agents-strongroom`](examples/openai-agents-strongroom) | a real **OpenAI Agents SDK** agent run loop with its model calls brokered through a lease |\n| [`examples/mcp-strongroom`](examples/mcp-strongroom) | an **MCP server** whose tools return *leases, not keys* — the \"where does the key live?\" answer for every credentialed MCP server |\n\n## Dispatching to a fleet\n\nA platform that runs agents on remote devices shouldn't ship a long-lived key to each one — that's how OpenClaw leaked ~135k of them. Ship a **lease** instead:\n\n- the **control plane** stores the secret in strongroom and grants a scoped, short-lived lease per task (`--upstream`, `--paths`, `--rate`, `--concurrency`, `--ttl`, `--uses`);\n- the **device** receives only the lease id and runs through `strongroom broker` — the key is injected at egress, never written to the device;\n- a compromised device yields a *lease* (scoped, expiring, revocable), not a key. `strongroom revoke <lease>` kills it instantly — no production-key rotation.\n\n**The control plane never scrapes human output.** `grant`, `leases`, `ls`, and `audit` take `--json` and put exactly **one JSON value on stdout** — no ANSI, no prose, no stderr summary:\n\n```bash\nstrongroom grant TASK_API_KEY --ttl 300 --uses 50 --upstream https://api.example.com --json\n# → {\"id\":\"lease_…\",\"secret\":\"TASK_API_KEY\",\"usesLeft\":50,\"expiresAt\":1720000000000,\"ttlS\":300,\n#    \"host\":null,\"upstream\":\"https://api.example.com\",\"inject\":null,\"rate\":null,\"paths\":null,\"concurrency\":null}\nstrongroom leases --json          # → array of secret-safe lease records (fingerprints, never raw ids)\nstrongroom ls --json              # → [\"TASK_API_KEY\", …]\nstrongroom audit --json           # → the parsed event array\nstrongroom audit --verify --json  # → {\"ok\":true,\"entries\":n} | {\"ok\":false,\"reason\":\"audit-tip-forged\"} — exit code 0/1 preserved\n```\n\n`grant --json` returns the same one-time id + metadata the human path already returns — just machine-readable. Without `--json`, output is unchanged.\n\nSee it end to end: `npm run demo:platform`.\n\n## Delegating a lease — least privilege between agents\n\nIn a multi-agent tree, a parent agent can hand a **sub-agent** a *narrower* slice of its own access without ever touching the vault. `grant --from-lease` **attenuates** a lease the parent holds into a sub-lease whose every scope is `≤` the parent's — shorter TTL, fewer uses, tighter host/upstream/paths/rate/concurrency. **Never wider.**\n\n```bash\n# Parent holds a broad lease: 1h, 100 uses, all of /v1/*\nPARENT=$(strongroom grant OPENAI_API_KEY --upstream https://api.openai.com \\\n  --paths \"/v1/*\" --ttl 3600 --uses 100)\n\n# Delegate a tight sub-lease to a summarizer sub-agent: 5 min, 3 uses, chat only\nCHILD=$(strongroom grant --from-lease \"$PARENT\" \\\n  --paths \"/v1/chat/completions\" --ttl 300 --uses 3)\n```\n\n- **Attenuate-only.** A sub-lease may **narrow** any axis or **inherit** it (unset = inherit the parent's), but never widen: a longer TTL, more uses, a broader `--paths` glob, a different `--host`/`--upstream`, or a higher `--rate`/`--concurrency` is **rejected** with an error naming the axis. `--paths` must be a **subset** of the parent's (checked with the same segment-glob semantics the broker enforces). A parent axis left *unlimited* may be *capped* by the child.\n- **Recorded provenance.** The child lease carries the **parent lease fingerprint**, and the child's `grant` audit event carries it as `from` — so a delegation shows up in the hash-chained, authenticated-tip audit as a parent→child link that still verifies. `strongroom leases` and `strongroom audit` render it as `⤷ from <fp>`.\n- **Per-capability guarantee.** The child is an independent lease bounded by `child scope ⊆ parent scope`, so a sub-agent can never redeem toward anything its parent couldn't — and delegating does **not** spend a parent use.\n\n## Security model\n\nstrongroom is a vault, so its own security is the point:\n\n- **Encrypted at rest** — AES-256-GCM, with the secret *name* bound in as AAD, so a ciphertext can't be swapped between names.\n- **Master key** — three options, in priority order:\n  - `KEEPER_PASSPHRASE` — derived with **scrypt**; never on disk (only a salt is).\n  - `KEEPER_KEYCHAIN=1` — held by the **OS keychain**: macOS Keychain · Linux Secret Service · Windows DPAPI (user scope). Never plaintext on disk, and it **fails closed** if no keychain is available (no silent downgrade). `strongroom keychain` shows the active backend.\n  - else — a random key file in `~/.keeper` (`0600` + a restrictive ACL on Windows).\n\n  Use the passphrase or the keychain for anything that matters.\n- **Rotation is built in** — `strongroom rekey` re-encrypts every secret under a fresh master key, optionally switching key stores (`--to passphrase|keychain|file`; a passphrase target reads `KEEPER_NEW_PASSPHRASE`). It's atomic and fail-closed: a wrong current passphrase aborts with nothing changed, an interrupted swap is completed or discarded safely on the next run, retired key material (old salt / key file / keychain entry) is removed, and the audit's authenticated tip is re-MACed under the new key. Restart a running daemon/broker afterwards — they hold the old key and fail closed.\n- **Operator ceiling on grants** — set `KEEPER_MAX_TTL` (seconds) and/or `KEEPER_MAX_USES` and no lease minted from this vault — CLI **or** library — may exceed them. An over-cap grant is **rejected** with an error naming the cap (never silently clamped) and audited as a `deny`/`policy` event, so \"leases stay small\" is vault policy, not caller discipline. Unset = no ceiling (unchanged behavior). Zero, negative, or non-numeric `--ttl`/`--uses` are always rejected — a NaN would otherwise mint a lease that never expires.\n- **Leases are bearer tokens** — only `sha256(id)` is stored; the raw id is returned once, to you. Reading `leases.json` therefore can't redeem anything.\n- **Single-use is atomic** — redeem is a check-and-consume under a cross-process lock, so concurrent redeems can't double-spend a one-use lease.\n- **Fail-closed** — a tampered, swapped, or wrong-key entry returns null and denies; it never throws or leaks garbage.\n- **Tamper-evident audit** — every access is hash-chained (the shared stack primitive, vendored from redstamp in `src/chain.mjs`; the on-disk format is pinned by `test/chain-compat.test.mjs` so the two stay cross-verifiable) and logged by lease *fingerprint*, never the raw id. An **authenticated tip** (HMAC under a subkey of the master key) commits to the chain's length and last hash, so *truncating* or *splicing* the log is caught — not just editing an entry.\n- **Reflected secrets can't ride back in** — the broker redacts any occurrence of the injected secret from relayed response headers and bodies and audits it (`sanitize`), so an echoing or misconfigured upstream can't hand the raw key back into the agent's context.\n\nWhat it is **not**: a defense against an attacker who already has your passphrase / master key or full process memory — at that point they have the vault. strongroom shrinks the *agent's* exposure (a lease, not the key; short-lived; scoped; audited); it doesn't replace OS-level isolation.\n\n## Commands\n\n```\nstrongroom add <name>                  store a secret (stdin, or --value=)\nstrongroom ls [--json]                 list secret names (never values)\nstrongroom grant <name> [--ttl --uses --host]                        mint a lease\n              [--upstream --inject --paths --rate --concurrency]  (broker scoping)\n              (KEEPER_MAX_TTL / KEEPER_MAX_USES, if set, cap every grant — over-cap is rejected + audited)\n              [--json]                 one machine-readable JSON object on stdout\nstrongroom grant --from-lease <lease> [tighter opts]  DELEGATE: attenuate a lease you hold into a\n              narrower sub-lease for a sub-agent (shorter --ttl, fewer --uses, tighter\n              --host/--upstream/--paths/--rate/--concurrency; NEVER wider; unset scopes inherit)\nstrongroom redeem <lease> [--host]     exchange a valid lease for the secret (egress side)\nstrongroom exec <lease> --as <ENV> -- <cmd...>  redeem + run <cmd> with the secret in its env only\nstrongroom broker [--port 8771]        egress-injection proxy (base-URL swap, zero key in the agent)\nstrongroom serve [--socket <path>]     redeem-daemon: holds the master key, answers lease→secret\n                                   over a local socket (KEEPER_DAEMON=1 on the keyless side)\nstrongroom leases [--json] · strongroom revoke <lease> · strongroom rm <name>\nstrongroom audit [--verify] [--json]   the access log, optionally chain-verified\nstrongroom rekey [--to passphrase|keychain|file]   rotate the master key (re-encrypts the vault)\nstrongroom keychain                    master-key backend status (KEEPER_KEYCHAIN=1 to use the OS keychain)\n```\n\n## Library\n\n```js\nimport { addSecret, grant, redeem } from '@askalf/strongroom';\n\naddSecret('STRIPE_KEY', process.env.STRIPE_KEY);\nconst lease = grant('STRIPE_KEY', { ttlS: 60, uses: 1, host: 'api.stripe.com' });\n// hand `lease.id` to the agent; at egress:\nconst { ok, value } = redeem(lease.id, { host: 'api.stripe.com' });\n\n// Delegate a narrower sub-lease to a sub-agent (attenuate-only; child ⊆ parent):\nimport { grantFromLease } from '@askalf/strongroom';\nconst sub = grantFromLease(lease.id, { ttlS: 30, uses: 1 }); // sub.parent = parent fingerprint\n```\n\n## The agent-security stack\n\nThree composable layers, one defense: **[redstamp](https://github.com/askalf/redstamp)** contains the call · **[truecopy](https://github.com/askalf/truecopy)** vets the tool · **strongroom** holds the keys *(you are here)*. Run all three together → **[agent-security-stack](https://github.com/askalf/agent-security-stack)**.\n\nRelated: **[plumbline](https://github.com/askalf/plumbline)** — *own your agent trajectory.* Out-of-band, read-only monitoring of the whole action sequence against the declared job, catching escapes assembled from individually-authorized steps. It sits **above** the three in-path layers as a monitor — it never blocks an action.\n\n---\nPart of **[Own Your Stack](https://github.com/askalf)** — own your AI infrastructure instead of renting it by the token. Built by Thomas Sprayberry · MIT.\n","readmeFilename":"README.md"}