{"_id":"@boolab/crypto-gateway","_rev":"6-b38543ded258d0a4c849ffed9f983fa4","name":"@boolab/crypto-gateway","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"@boolab/crypto-gateway","version":"0.1.0","keywords":["encryption","envelope-encryption","kms","data-key","audit"],"license":"MIT","_id":"@boolab/crypto-gateway@0.1.0","maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"dist":{"shasum":"f19658b0902d39d679095bfcf19da0ea83364ac5","tarball":"https://registry.npmjs.org/@boolab/crypto-gateway/-/crypto-gateway-0.1.0.tgz","fileCount":17,"integrity":"sha512-gnEwppwD5GdkHE9iVgaiPU4PxadPHs7EHU7wvA+i7pYPExpheI0WWxB90iwvV7n7T57jUh1N9Hgnv4TVmIkdFQ==","signatures":[{"sig":"MEUCIQCy0porBNi/S45UF5Uy23fRDlg76Z5XfxCsP7xTrhAyOQIgIAkWZlL0KyCtac6IgOZZ2ViQBuUlat5VwCQQvEpQFTU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":77876},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./adapters":{"import":{"types":"./dist/adapters/index.d.ts","default":"./dist/adapters/index.js"},"require":{"types":"./dist/adapters/index.d.cts","default":"./dist/adapters/index.cjs"}}},"gitHead":"6528a70ced483c501414387a41a6c2514ec614c2","scripts":{"lint":"biome check .","test":"node --test \"test/*.test.ts\"","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"},"_npmVersion":"11.12.1","description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.10.2","@biomejs/biome":"2.4.15"},"_npmOperationalInternal":{"tmp":"tmp/crypto-gateway_0.1.0_1779278811962_0.8274938581367621","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@boolab/crypto-gateway","version":"0.2.0","keywords":["encryption","envelope-encryption","kms","data-key","audit"],"license":"MIT","_id":"@boolab/crypto-gateway@0.2.0","maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"dist":{"shasum":"41574b0d68af6b261d9725e71795ab4bb7496697","tarball":"https://registry.npmjs.org/@boolab/crypto-gateway/-/crypto-gateway-0.2.0.tgz","fileCount":17,"integrity":"sha512-9TBTCDFOIwUrnLxJkJWwzq0R642nV+8LSZ3fi7/NwwwrEXTR+yOggDcKdrwR1iJiLandFj8azS7Fa574w3J4pA==","signatures":[{"sig":"MEUCIB1G1qwytXaTpvmVCZSvVB/Yoj80vemiUHWuVhh+TEMcAiEAzocEB+Jj0i41xLvTmX7841Ff7oC2ESA+1PmKnlZS6gI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":106743},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./adapters":{"import":{"types":"./dist/adapters/index.d.ts","default":"./dist/adapters/index.js"},"require":{"types":"./dist/adapters/index.d.cts","default":"./dist/adapters/index.cjs"}}},"gitHead":"ba90ab0b689f3ccf408c07b7dd8947f100969e13","scripts":{"lint":"biome check .","test":"node --test \"test/*.test.ts\"","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"},"_npmVersion":"11.12.1","description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.10.2","@biomejs/biome":"2.4.15"},"_npmOperationalInternal":{"tmp":"tmp/crypto-gateway_0.2.0_1779281892360_0.7603195882518703","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@boolab/crypto-gateway","version":"0.3.0","keywords":["encryption","envelope-encryption","kms","data-key","audit"],"license":"MIT","_id":"@boolab/crypto-gateway@0.3.0","maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"dist":{"shasum":"a8b761646af0389408a3a63ed8d0b20128fcc4b9","tarball":"https://registry.npmjs.org/@boolab/crypto-gateway/-/crypto-gateway-0.3.0.tgz","fileCount":17,"integrity":"sha512-qrTLIowIDM17bZKCSB02aaTGgxo2zBB975iTkiBfPM2jCM7/LVShidaKANXH7MMkOnrxlHif13PtBbD2VcbbwQ==","signatures":[{"sig":"MEUCIQDudsGateTWbFJ1FAFliTE9hFulZp3SxggCa7+pKRpJMgIgbgbjmjXxiPfJmJhqHvHNX333QqN216myTRshAoZ6mZo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":112307},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./adapters":{"import":{"types":"./dist/adapters/index.d.ts","default":"./dist/adapters/index.js"},"require":{"types":"./dist/adapters/index.d.cts","default":"./dist/adapters/index.cjs"}}},"gitHead":"6035cbea97dd7ca22339d598a4563e8f08522ccc","scripts":{"lint":"biome check .","test":"node --test \"test/*.test.ts\"","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"},"_npmVersion":"11.12.1","description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.10.2","@biomejs/biome":"2.4.15"},"_npmOperationalInternal":{"tmp":"tmp/crypto-gateway_0.3.0_1779289408496_0.2943694778683832","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@boolab/crypto-gateway","version":"0.4.0","keywords":["encryption","envelope-encryption","kms","data-key","audit"],"license":"MIT","_id":"@boolab/crypto-gateway@0.4.0","maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"dist":{"shasum":"99e62653fe56aefcf3be761ef7105109292b7c95","tarball":"https://registry.npmjs.org/@boolab/crypto-gateway/-/crypto-gateway-0.4.0.tgz","fileCount":47,"integrity":"sha512-wC70ttEC9xQ0FYraepJzf4sBmEu0I0a2E7MqauKYfrJyKFzscLSfG9lZb6VahtI4gG70K13LN60W37NWWPXb3g==","signatures":[{"sig":"MEUCIDlVchU1vCdLth5QAqWG+hTXICcMgHiFqZg94RrIOSPrAiEAjzSm2eNFrCpyKiHcQ9kov3CTmCUW8pbnwE1mP12Xwdw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":258900},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./adapters":{"import":{"types":"./dist/adapters/index.d.ts","default":"./dist/adapters/index.js"},"require":{"types":"./dist/adapters/index.d.cts","default":"./dist/adapters/index.cjs"}},"./adapters/vault":{"import":{"types":"./dist/adapters/vault/index.d.ts","default":"./dist/adapters/vault/index.js"},"require":{"types":"./dist/adapters/vault/index.d.cts","default":"./dist/adapters/vault/index.cjs"}},"./adapters/aws-kms":{"import":{"types":"./dist/adapters/aws-kms/index.d.ts","default":"./dist/adapters/aws-kms/index.js"},"require":{"types":"./dist/adapters/aws-kms/index.d.cts","default":"./dist/adapters/aws-kms/index.cjs"}},"./adapters/gcp-kms":{"import":{"types":"./dist/adapters/gcp-kms/index.d.ts","default":"./dist/adapters/gcp-kms/index.js"},"require":{"types":"./dist/adapters/gcp-kms/index.d.cts","default":"./dist/adapters/gcp-kms/index.cjs"}},"./adapters/azure-keyvault":{"import":{"types":"./dist/adapters/azure-keyvault/index.d.ts","default":"./dist/adapters/azure-keyvault/index.js"},"require":{"types":"./dist/adapters/azure-keyvault/index.d.cts","default":"./dist/adapters/azure-keyvault/index.cjs"}}},"gitHead":"e2f7294bf7b5991bc80f1d4fd13f7a452b1d88de","scripts":{"lint":"biome check .","test":"node --test \"test/*.test.ts\"","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build","test:integration":"node --test \"test/integration/*.test.ts\""},"_npmUser":{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"},"_npmVersion":"11.12.1","description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.10.2","@biomejs/biome":"2.4.15","testcontainers":"^12","@google-cloud/kms":"^5","@aws-sdk/client-kms":"^3","@azure/keyvault-keys":"^4","@testcontainers/localstack":"^12"},"peerDependencies":{"@google-cloud/kms":"^4 || ^5","@aws-sdk/client-kms":"^3","@azure/keyvault-keys":"^4"},"peerDependenciesMeta":{"@google-cloud/kms":{"optional":true},"@aws-sdk/client-kms":{"optional":true},"@azure/keyvault-keys":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/crypto-gateway_0.4.0_1779323948380_0.6487864604658631","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@boolab/crypto-gateway","version":"0.5.0","keywords":["encryption","envelope-encryption","kms","data-key","audit"],"license":"MIT","_id":"@boolab/crypto-gateway@0.5.0","maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"dist":{"shasum":"1c6e44ebf79357909318339dcbc12daee71e5a28","tarball":"https://registry.npmjs.org/@boolab/crypto-gateway/-/crypto-gateway-0.5.0.tgz","fileCount":63,"integrity":"sha512-pR/YpF/siNf4XE0+E7VD21xOmATuI4c6YIsqBPSL7RoncG6an1TEOSLK5v6gitjOgllCTtAheyUFYxzHWwCL/g==","signatures":[{"sig":"MEUCIQCaDfs646/NzbAEAvHokuae4Qqoo+CPfWDaEHXH7TIYBAIgUxrpeTQ0UjtKIM8fnBHxAzMS4iZ9DlnrZBH4WiHMm4s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":361987},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./content":{"import":{"types":"./dist/content/index.d.ts","default":"./dist/content/index.js"},"require":{"types":"./dist/content/index.d.cts","default":"./dist/content/index.cjs"}},"./keyring":{"import":{"types":"./dist/keyring/index.d.ts","default":"./dist/keyring/index.js"},"require":{"types":"./dist/keyring/index.d.cts","default":"./dist/keyring/index.cjs"}},"./adapters":{"import":{"types":"./dist/adapters/index.d.ts","default":"./dist/adapters/index.js"},"require":{"types":"./dist/adapters/index.d.cts","default":"./dist/adapters/index.cjs"}},"./adapters/vault":{"import":{"types":"./dist/adapters/vault/index.d.ts","default":"./dist/adapters/vault/index.js"},"require":{"types":"./dist/adapters/vault/index.d.cts","default":"./dist/adapters/vault/index.cjs"}},"./adapters/aws-kms":{"import":{"types":"./dist/adapters/aws-kms/index.d.ts","default":"./dist/adapters/aws-kms/index.js"},"require":{"types":"./dist/adapters/aws-kms/index.d.cts","default":"./dist/adapters/aws-kms/index.cjs"}},"./adapters/gcp-kms":{"import":{"types":"./dist/adapters/gcp-kms/index.d.ts","default":"./dist/adapters/gcp-kms/index.js"},"require":{"types":"./dist/adapters/gcp-kms/index.d.cts","default":"./dist/adapters/gcp-kms/index.cjs"}},"./adapters/azure-keyvault":{"import":{"types":"./dist/adapters/azure-keyvault/index.d.ts","default":"./dist/adapters/azure-keyvault/index.js"},"require":{"types":"./dist/adapters/azure-keyvault/index.d.cts","default":"./dist/adapters/azure-keyvault/index.cjs"}}},"gitHead":"61d3b245392efca2173c5eee5627bd991a91239e","scripts":{"lint":"biome check .","test":"node --test \"test/*.test.ts\"","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build","test:integration":"node --test \"test/integration/*.test.ts\""},"_npmUser":{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"},"_npmVersion":"11.12.1","description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.10.2","@biomejs/biome":"2.4.15","testcontainers":"^12","@google-cloud/kms":"^5","@aws-sdk/client-kms":"^3","@azure/keyvault-keys":"^4","@testcontainers/localstack":"^12"},"peerDependencies":{"@google-cloud/kms":"^4 || ^5","@aws-sdk/client-kms":"^3","@azure/keyvault-keys":"^4"},"peerDependenciesMeta":{"@google-cloud/kms":{"optional":true},"@aws-sdk/client-kms":{"optional":true},"@azure/keyvault-keys":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/crypto-gateway_0.5.0_1779459103993_0.36058865313450994","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@boolab/crypto-gateway","version":"1.0.0","description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","license":"MIT","type":"module","sideEffects":false,"publishConfig":{"access":"public"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./content":{"import":{"types":"./dist/content/index.d.ts","default":"./dist/content/index.js"},"require":{"types":"./dist/content/index.d.cts","default":"./dist/content/index.cjs"}},"./keyring":{"import":{"types":"./dist/keyring/index.d.ts","default":"./dist/keyring/index.js"},"require":{"types":"./dist/keyring/index.d.cts","default":"./dist/keyring/index.cjs"}},"./adapters":{"import":{"types":"./dist/adapters/index.d.ts","default":"./dist/adapters/index.js"},"require":{"types":"./dist/adapters/index.d.cts","default":"./dist/adapters/index.cjs"}},"./adapters/vault":{"import":{"types":"./dist/adapters/vault/index.d.ts","default":"./dist/adapters/vault/index.js"},"require":{"types":"./dist/adapters/vault/index.d.cts","default":"./dist/adapters/vault/index.cjs"}},"./adapters/aws-kms":{"import":{"types":"./dist/adapters/aws-kms/index.d.ts","default":"./dist/adapters/aws-kms/index.js"},"require":{"types":"./dist/adapters/aws-kms/index.d.cts","default":"./dist/adapters/aws-kms/index.cjs"}},"./adapters/gcp-kms":{"import":{"types":"./dist/adapters/gcp-kms/index.d.ts","default":"./dist/adapters/gcp-kms/index.js"},"require":{"types":"./dist/adapters/gcp-kms/index.d.cts","default":"./dist/adapters/gcp-kms/index.cjs"}},"./adapters/azure-keyvault":{"import":{"types":"./dist/adapters/azure-keyvault/index.d.ts","default":"./dist/adapters/azure-keyvault/index.js"},"require":{"types":"./dist/adapters/azure-keyvault/index.d.cts","default":"./dist/adapters/azure-keyvault/index.cjs"}}},"scripts":{"build":"tsup","test":"node --test \"test/*.test.ts\"","test:integration":"node --test \"test/integration/*.test.ts\"","typecheck":"tsc --noEmit","lint":"biome check .","lint:fix":"biome check --write .","format":"biome format --write .","release":"changeset publish","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build"},"keywords":["encryption","envelope-encryption","kms","data-key","audit"],"engines":{"node":">=18"},"peerDependencies":{"@aws-sdk/client-kms":"^3","@azure/keyvault-keys":"^4","@google-cloud/kms":"^4 || ^5"},"peerDependenciesMeta":{"@aws-sdk/client-kms":{"optional":true},"@azure/keyvault-keys":{"optional":true},"@google-cloud/kms":{"optional":true}},"devDependencies":{"@aws-sdk/client-kms":"^3","@azure/keyvault-keys":"^4","@biomejs/biome":"2.4.15","@changesets/changelog-github":"^0.7.0","@changesets/cli":"^2.31.0","@google-cloud/kms":"^5","@testcontainers/localstack":"^12","@types/node":"^22.10.2","testcontainers":"^12","tsup":"^8.3.5","typescript":"^5.7.2"},"gitHead":"e1deb0ed372f111d5ebfb502074828b64615a676","_id":"@boolab/crypto-gateway@1.0.0","_nodeVersion":"24.15.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-qcSJdDWAqdXzuJnjbKQMWEG0RGMICIRCFqPCaHCuE55Jk/1TCxREo6qAK+dStcjkGar0ARizEjXllgTj/XafAQ==","shasum":"6c09f41efb8a964854a5af601dd847cb5a5b9bf2","tarball":"https://registry.npmjs.org/@boolab/crypto-gateway/-/crypto-gateway-1.0.0.tgz","fileCount":63,"unpackedSize":403814,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDgp8QKnrSBS3viw0MKWqfkNtfpqFJvK3hFbyeKDPOvgQIhAJ0wXCjVS6l0sq07hvWKT6EICU5++N16OmSPiMqVKYE3"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:75b24da4-b1d2-4fb6-bdc3-61c3eec75172"}},"directories":{},"maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/crypto-gateway_1.0.0_1779963615276_0.05087623463562485"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-20T12:06:51.757Z","modified":"2026-05-28T10:20:16.246Z","0.1.0":"2026-05-20T12:06:52.097Z","0.2.0":"2026-05-20T12:58:12.549Z","0.3.0":"2026-05-20T15:03:28.650Z","0.4.0":"2026-05-21T00:39:08.509Z","0.5.0":"2026-05-22T14:11:44.191Z","1.0.0":"2026-05-28T10:20:15.431Z"},"license":"MIT","keywords":["encryption","envelope-encryption","kms","data-key","audit"],"description":"A thin envelope-crypto gateway that leases data keys and audits locally, while the authoritative grant/deny decision stays in a customer-owned key system.","maintainers":[{"name":"d2201","email":"daniel.kulinski.dev@gmail.com"}],"readme":"# @boolab/crypto-gateway\n\nA thin envelope-encryption gateway. It hands out **managed data-key handles**\nand **audits** every operation locally, but it never holds key material and it\nnever decides who may decrypt.\n\nThe decryption authority lives in a **customer-owned key system** (AWS KMS,\nGCP KMS, HashiCorp Vault/OpenBao, etc.) on the critical path of every unwrap.\nThe gateway cannot grant access it was never given: there is no approval gate\nto bypass, because access is enforced cryptographically by infrastructure the\ncustomer controls.\n\n## Install\n\n```sh\nnpm install @boolab/crypto-gateway\n```\n\n## Concepts\n\n- **`KeyProvider`**: an adapter to the customer's key system. It custodies the\n  KEK only: it wraps, unwraps, and re-wraps data keys, but never mints them. It\n  is intentionally \"dumb\": it relays operations and surfaces the key system's\n  verdict unchanged, and must never synthesize a `granted`.\n- **`AuditSink`**: where the host writes its local audit trail. The gateway\n  calls it for every operation and is **fail-closed**: if the audit write\n  fails, the operation fails. This is not the record of record; the\n  customer's key system keeps its own authoritative log.\n- **`CryptoGateway`**: what the host calls. It leases and audits; it does not\n  authorize.\n- **`DataKey`**: a managed handle returned on a grant. You reach the plaintext\n  through `key.use(fn)`, never as a field. Access goes through the handle so\n  the TTL is re-checked on every call; an expired key is transparently\n  re-unwrapped first, which re-runs the key system's authorization. A refresh\n  returns the same plaintext (the key is reused, not rotated); its point is to\n  bound how long access survives after the key system revokes it.\n\nAn unwrap returns one of two verdicts, both of which the caller must handle:\n\n| Verdict     | Meaning                                                          |\n| ----------- | --------------------------------------------------------------- |\n| `granted`   | The key system released the key; the result carries a `DataKey`. |\n| `denied`    | The key system refused (policy, revoked key).                   |\n\n`key.use(fn)` returns the same verdict: on `granted` it carries your callback's\nvalue; on a refresh that is no longer granted it returns `denied` and your\ncallback never runs.\n\n## Usage\n\nThe core entry point (`@boolab/crypto-gateway`) holds the gateway and the\ninterfaces, with no bundled adapters. The reference adapters live behind a\nseparate import (`@boolab/crypto-gateway/adapters`).\n\n```ts\nimport { createCryptoGateway, type CallerContext } from '@boolab/crypto-gateway';\nimport { InMemoryKeyProvider, InMemoryAuditSink } from '@boolab/crypto-gateway/adapters';\n\nconst gateway = createCryptoGateway({\n  keyProvider: new InMemoryKeyProvider(), // swap for a customer-owned adapter\n  auditSink: new InMemoryAuditSink(),     // swap for a SIEM / append-only store\n  leaseTtlSeconds: 300,\n});\n\n// The gateway trusts this context as-is and forwards it as the authorization\n// subject, so the host must build it at its trust boundary from authenticated,\n// server-observed data (verified token claims, an mTLS cert, the socket),\n// not from request fields the caller controls.\nconst context: CallerContext = {\n  principal: 'workload://content-service',\n  customerId: 'cust-1',\n  sourceAddress: '10.0.0.1',\n  requestId: 'req-abc123',\n};\n\n// Encrypt new content: persist key.wrapped alongside the ciphertext.\nconst key = await gateway.issueDataKey(context);\nawait key.use((plaintext) => encrypt(content, plaintext));\n\n// Decrypt stored content: handle both verdicts.\nconst result = await gateway.openDataKey(context, key.wrapped);\nswitch (result.status) {\n  case 'granted':\n    // The TTL is re-checked here; a stale key re-unwraps before `fn` runs.\n    await result.key.use((plaintext) => decrypt(content, plaintext));\n    break;\n  case 'denied':\n    fail(result.reason); // the key system said no\n    break;\n}\n```\n\nThe plaintext is reachable only inside `use`, and only for the duration of the\ncallback. Don't hoist it into a longer-lived variable: that would bypass the\nTTL re-check and the re-authorization that comes with it.\n\nWhether a given principal is auto-granted or routed to human approval is\ndecided entirely by the customer's key-system policy. The library never makes\nthat call.\n\n## Writing an adapter\n\nImplement `KeyProvider` against the customer's key system. The bundled\n`InMemoryKeyProvider` (AES-256-GCM, KEK held in process memory) is a working\nreference for tests, examples, and local development, **not** for production,\nwhere the KEK must live in a key system the customer controls.\n\n```ts\nimport type { KeyProvider } from '@boolab/crypto-gateway';\n\nclass MyKmsProvider implements KeyProvider {\n  // KEK encrypt: wrap a caller-supplied plaintext data key (e.g. kms:Encrypt).\n  wrapDataKey(context, plaintext) { /* ... */ }\n  // granted yields { status: 'granted', plaintext }; otherwise surface the\n  // key system's verdict unchanged, never synthesize a grant.\n  unwrapDataKey(context, wrapped) { /* ... */ }\n  rewrapDataKey(context, wrapped) { /* re-wrap to current KEK version */ }\n}\n```\n\nThe provider custodies the KEK only; it never mints data keys. The gateway\nmints them with a CSPRNG in `issueDataKey` and asks the provider to wrap them,\nand it owns the lease TTL (a granted unwrap returns plaintext, which the\ngateway wraps in a refreshing `DataKey`).\n\n### Migrating a data key between providers\n\nBecause data keys are minted by the caller and providers only wrap them, a\nwrapped key can be re-homed from one provider to another without re-encrypting\nthe content it protects. Open it under the old gateway and re-wrap it under the\nnew one; the plaintext data key only exists inside the `use` scope:\n\n```ts\nconst opened = await gatewayA.openDataKey(context, wrappedUnderA);\nif (opened.status !== 'granted') return opened; // denied\n\nconst result = await opened.key.use((dataKey) => gatewayB.wrapDataKey(context, dataKey));\nif (result.status !== 'granted') return result;\nconst wrappedUnderB = result.value; // persist this; same data key, same ciphertext\n```\n\n## Sealing content (`@boolab/crypto-gateway/content`)\n\nThe gateway stops at the data-key boundary. The `content` module is its generic\ncompanion: a self-describing AES-256-GCM envelope so you don't hand-roll the\nframing. The core runs *inside* `DataKey.use`, so the plaintext key is never\nreturned to the caller.\n\n```ts\nimport { sealWithDataKey, openWithDataKey, isSealed } from '@boolab/crypto-gateway/content';\n\nconst key = await gateway.issueDataKey(context, { keyId: 'dek-1' });\n\nconst sealed = await sealWithDataKey(key, new TextEncoder().encode('secret'));\nif (sealed.status !== 'granted') return sealed; // denied\n// sealed.value is the envelope: persist it as-is.\n\nconst opened = await openWithDataKey(key, sealed.value);\nif (opened.status === 'granted') {\n  opened.value.keyId; // 'dek-1', read from the envelope\n  opened.value.plaintext; // the original bytes\n}\n```\n\nThe envelope is `magic | version | keyIdLen | keyId | iv(12) | tag(16) | ciphertext`.\nThe GCM tag authenticates the ciphertext only — the framing is **not** bound as\nAAD. A fixed `magic` prefix (`isSealed`) lets a reader tell a sealed blob from\nlegacy plaintext, which makes a read-path passthrough and on-boot migration\nsafe. `sealContent` / `openContent` expose the same logic as pure, synchronous\nfunctions when you already hold the raw 32-byte key.\n\n## Keyrings (`@boolab/crypto-gateway/keyring`)\n\nA keyring persists **one** data key wrapped under **N** providers at once\n(primary + fallbacks), so content stays recoverable through any of them while\nthe envelope `keyId` stays constant. The module is codec + algorithms only: it\nnever does I/O and treats each binding's `provider` descriptor as opaque (typed\n`P`), so it never depends on your provider schema. You supply a `GatewayFor<P>`\nthat turns a descriptor into a gateway.\n\n```ts\nimport { parseKeyring, openKeyring, type GatewayFor } from '@boolab/crypto-gateway/keyring';\n\nconst gatewayFor: GatewayFor<MyDescriptor> = async (descriptor) => {\n  const keyProvider = await buildProvider(descriptor); // host owns this\n  return createCryptoGateway({ keyProvider, auditSink, leaseTtlSeconds: 300 });\n};\n\nconst keyring = parseKeyring<MyDescriptor>(JSON.parse(await readFile('dek.json', 'utf8')));\nconst result = await openKeyring(gatewayFor, keyring, context);\nif (result.status === 'granted') {\n  // result.dataKey is leased through result.openedBindingId, stamped with keyring.keyId\n} else {\n  result.perBinding; // human-readable denied/error per binding\n}\n```\n\nAlso provided: `serializeKeyring` (canonical, order-insensitive),\n`orderedWrappings`, `pruneExpiredWrappings`, `probeWrapping`, `wrapDekUnder`\n(bind a new provider to an open key without re-encrypting content), and\n`singleBindingKeyring` (the first-boot helper). Byte I/O and the provider schema\nstay in the host.\n\n## Production adapters\n\nEach production adapter lives behind its own import so you install only the SDK\nyou use. The cloud SDKs are **optional peer dependencies**: add the one your\nadapter needs alongside this package.\n\nEvery adapter follows the same contract: it wraps/unwraps/rewraps under a KEK\nthe key system holds and never exposes, and it surfaces the key system's\nverdict. An unwrap that the key system refuses (an access-denied error, a\ndisabled key) is mapped to a `denied` verdict; an operational failure (network,\nthrottling) propagates as a thrown error. Each adapter accepts a\n`classifyUnwrapError` hook to override that mapping — for example, to recognize\na custom error as authorization-refused rather than operational.\n\n| Adapter | Import | Peer dependency | Setup guide |\n| --- | --- | --- | --- |\n| Vault / OpenBao Transit | `@boolab/crypto-gateway/adapters/vault` | none (uses `fetch`) | [docs/adapters/vault.md](./docs/adapters/vault.md) |\n| AWS KMS | `@boolab/crypto-gateway/adapters/aws-kms` | `@aws-sdk/client-kms` | [docs/adapters/aws-kms.md](./docs/adapters/aws-kms.md) |\n| GCP Cloud KMS | `@boolab/crypto-gateway/adapters/gcp-kms` | `@google-cloud/kms` | [docs/adapters/gcp-kms.md](./docs/adapters/gcp-kms.md) |\n| Azure Key Vault / HSM | `@boolab/crypto-gateway/adapters/azure-keyvault` | `@azure/keyvault-keys` | [docs/adapters/azure-keyvault.md](./docs/adapters/azure-keyvault.md) |\n\nThe brief snippets below are enough to wire each adapter up; the linked\nguides cover provisioning the KEK, least-privilege IAM, rotation, and the\naudit story end-to-end. Start at [docs/adapters/](./docs/adapters/) if\nyou're evaluating.\n\n### Vault / OpenBao Transit\n\nThe closest fit: the Transit key never leaves Vault, wrapped data keys are\nVault's own `vault:vN:...` ciphertext, and rewrap uses Transit's native rewrap.\nNo SDK — it speaks the HTTP API over the global `fetch` (Node 18+).\n\n```ts\nimport { VaultTransitKeyProvider } from '@boolab/crypto-gateway/adapters/vault';\n\nconst keyProvider = new VaultTransitKeyProvider({\n  address: 'https://vault.internal:8200',\n  token: process.env.VAULT_TOKEN!, // a renewed token for a long-lived host\n  keyName: 'content-kek',          // the Transit key (the KEK)\n  // mount: 'transit', namespace: 'team-a', timeoutMs: 10_000,\n});\n```\n\nA 403 from Vault becomes `denied`; other non-2xx responses throw\n`VaultRequestError`.\n\nThe caller context rides along as `X-Crypto-Gateway-*` request headers\n(`Request-Id`, `Principal`, `Source-Address`, `Customer-Id`) for provenance.\nThey appear in Vault's audit log only if the operator allowlists them:\n\n```sh\nvault write sys/config/auditing/request-headers/x-crypto-gateway-request-id hmac=false\n```\n\nUnlike the AWS and GCP adapters, this one does **not** bind the tenant as\nauthenticated data: Vault's Transit `rewrap` cannot decrypt `associated_data`-\nbound ciphertext, so binding would break native rewrap. The context is carried\nfor audit (the headers above) rather than bound cryptographically.\n\n### AWS KMS\n\nWraps with `kms:Encrypt`, unwraps with `kms:Decrypt`, rewraps with\n`kms:ReEncrypt` — deliberately **not** `GenerateDataKey`, since data keys are\nminted gateway-side and the provider only wraps them. Pass a configured\n`KMSClient` (it carries region, credentials, and retry policy).\n\n```ts\nimport { KMSClient } from '@aws-sdk/client-kms';\nimport { createAwsKmsKeyProvider } from '@boolab/crypto-gateway/adapters/aws-kms';\n\nconst keyProvider = createAwsKmsKeyProvider({\n  client: new KMSClient({ region: 'eu-west-1' }),\n  keyId: 'arn:aws:kms:eu-west-1:111122223333:key/abcd-…', // key id, ARN, or alias\n});\n```\n\n`AccessDeniedException`, `DisabledException`, and `KMSInvalidStateException`\nbecome `denied`; other errors propagate.\n\n### GCP Cloud KMS\n\nWraps and unwraps with the symmetric `encrypt` / `decrypt` operations.\n\n```ts\nimport { KeyManagementServiceClient } from '@google-cloud/kms';\nimport { createGcpKmsKeyProvider } from '@boolab/crypto-gateway/adapters/gcp-kms';\n\nconst client = new KeyManagementServiceClient();\nconst keyProvider = createGcpKmsKeyProvider({\n  client,\n  keyName: client.cryptoKeyPath('my-project', 'global', 'my-ring', 'content-kek'),\n});\n```\n\n`PERMISSION_DENIED` (7) and `FAILED_PRECONDITION` (9) become `denied`. Cloud KMS\nhas no symmetric re-encrypt, so `rewrapDataKey` decrypts then re-encrypts under\nthe key's current primary version — meaning the plaintext data key is briefly\npresent in this process during a rewrap.\n\n### Azure Key Vault / Managed HSM\n\nWraps and unwraps with `wrapKey` / `unwrapKey` (RSA-OAEP-256 by default). Pass a\n`CryptographyClient` bound to the vault key; use an unversioned key id so\nrewraps target the current version.\n\n```ts\nimport { DefaultAzureCredential } from '@azure/identity';\nimport { CryptographyClient } from '@azure/keyvault-keys';\nimport { createAzureKeyVaultKeyProvider } from '@boolab/crypto-gateway/adapters/azure-keyvault';\n\nconst client = new CryptographyClient(\n  'https://my-vault.vault.azure.net/keys/content-kek',\n  new DefaultAzureCredential(),\n);\nconst keyProvider = createAzureKeyVaultKeyProvider({ client /*, algorithm */ });\n```\n\nHTTP 403 becomes `denied`. As with GCP, Key Vault has no re-wrap, so\n`rewrapDataKey` unwraps then re-wraps and the plaintext is briefly in-process.\n\n## Bundled reference adapters\n\nImported from `@boolab/crypto-gateway/adapters`. All are reference adapters for\ntests, examples, and local development, not a substitute for a customer-owned\nkey system.\n\n- **`InMemoryKeyProvider`**: KEK held in process memory, AES-256-GCM wrapping.\n- **`InMemoryAuditSink`**: keeps audit events in memory.\n- **`FileKeyProvider`**: custodies the KEK in a file so wrapped keys survive a\n  restart. See [docs/adapters/filesystem.md](./docs/adapters/filesystem.md)\n  for the (narrow) range of production deployments it fits. Construct it with\n  one of the async factories:\n\n  ```ts\n  import { FileKeyProvider } from '@boolab/crypto-gateway/adapters';\n\n  // First run: mint and persist a KEK (mode 0600; refuses to overwrite).\n  const provider = await FileKeyProvider.create({ path: 'kek.json', masterKey });\n  // Later runs: load it back.\n  const provider = await FileKeyProvider.open({ path: 'kek.json', masterKey });\n  ```\n\n  `masterKey` is a 32-byte key. With it, the KEK is encrypted at rest\n  (AES-256-GCM), so reading the file alone does not reveal it. **Without it,\n  the KEK is written in plaintext**, and anyone who can read the file can\n  unwrap every data key. Either way the KEK becomes reachable to this process,\n  so `FileKeyProvider` is a single-node convenience, not a real key system.\n\n## Development\n\n```sh\nnpm install\nnpm run typecheck\nnpm test            # unit tests; no network, no Docker\nnpm run build\n```\n\n### Integration tests\n\n`npm run test:integration` runs the production adapters against real services\nspun up with [Testcontainers](https://testcontainers.com/), so it **requires a\nrunning Docker daemon**. It is kept separate from `npm test` and is not part of\nthe default gate.\n\n- **Vault** — `hashicorp/vault` dev server.\n- **AWS KMS** — LocalStack (`ReEncrypt` is skipped there; the emulator doesn't\n  implement it, but real KMS does).\n- **Azure Key Vault** — [`lowkey-vault`](https://github.com/nagyesta/lowkey-vault),\n  an emulator (binds host port 8443, which must be free).\n- **GCP KMS** has no emulator, so it has no integration test; its adapter logic\n  is covered by the unit tests via an injected fake.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}