{"_id":"@coderifts/capability-express","name":"@coderifts/capability-express","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@coderifts/capability-express","version":"1.0.0","description":"Express middleware that verifies cr.exec.v1 execution grants OFFLINE against a pinned Ed25519 public key.","license":"MIT","main":"src/index.js","type":"commonjs","engines":{"node":">=20"},"scripts":{"test":"node --test \"test/*.test.js\"","prepublishOnly":"npm test"},"repository":{"type":"git","url":"git+https://github.com/coderifts/capability-demo.git","directory":"packages/middleware"},"homepage":"https://github.com/coderifts/capability-demo/tree/main/packages/middleware","bugs":{"url":"https://github.com/coderifts/capability-demo/issues"},"keywords":["express","middleware","ed25519","authorization","offline-verification"],"publishConfig":{"access":"public"},"gitHead":"85afc0b905569ba4704227c20c21ffcecbb406cf","_id":"@coderifts/capability-express@1.0.0","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-e6EcHi4MaCzviv0eGFvjzU6KuIDQyu2lXCqBcYPGhfN/H8GE/F+oMJt/TN3EA7u1crRMp7tuEJqWztiZV9Wcjg==","shasum":"2d144a57f22ecc7b790cd23e2d40bc1a895b1050","tarball":"https://registry.npmjs.org/@coderifts/capability-express/-/capability-express-1.0.0.tgz","fileCount":7,"unpackedSize":43176,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDodPkDuir+/rhuY7A27fdzVk0CEhYL6AkC5iT4zBb29gIgDdghCMr/WQG2xMlZHr4azD7L9XH1jbP51/vFiu5t+Ig="}]},"_npmUser":{"name":"zsobpeter","email":"zsobpeter@gmail.com"},"directories":{},"maintainers":[{"name":"zsobpeter","email":"zsobpeter@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/capability-express_1.0.0_1788329842305_0.04718690690209115"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-02T06:17:22.024Z","1.0.0":"2026-09-02T06:17:22.441Z","modified":"2026-09-02T06:17:22.908Z"},"maintainers":[{"name":"zsobpeter","email":"zsobpeter@gmail.com"}],"description":"Express middleware that verifies cr.exec.v1 execution grants OFFLINE against a pinned Ed25519 public key.","homepage":"https://github.com/coderifts/capability-demo/tree/main/packages/middleware","keywords":["express","middleware","ed25519","authorization","offline-verification"],"repository":{"type":"git","url":"git+https://github.com/coderifts/capability-demo.git","directory":"packages/middleware"},"bugs":{"url":"https://github.com/coderifts/capability-demo/issues"},"license":"MIT","readme":"# @coderifts/capability-express\n\nExpress middleware that verifies `cr.exec.v1` execution grants **offline**, against a public key you\npin. No network call on the request path.\n\nZero runtime dependencies — Node builtins only. Express is a peer of your app, not of this package.\n\n## Install\n\n```bash\nnpm install @coderifts/capability-express\n```\n\n## Quickstart\n\n```js\nconst express = require('express');\nconst { requireExecutionGrant, captureRawBody } = require('@coderifts/capability-express');\n\nconst app = express();\n\nconst guard = requireExecutionGrant({\n  // One of these two. keysFile is a registry document: { keys: [{ kid, public_key_pem, status }] }\n  keysFile: '/etc/coderifts/keys/executor-keys.json',\n  // publicKeyPem: fs.readFileSync('executor-public.pem', 'utf8'),\n\n  // Which operation each route represents. A route that is not in this map is REFUSED:\n  // an unmapped mutation is not an authorized mutation.\n  operationMap: {\n    'POST /articles': 'publish',\n    'DELETE /articles/:id': 'deploy',\n  },\n});\n\n// captureRawBody must run BEFORE the guard: the raw request body IS the after-payload the\n// grant is bound to, and a re-serialized body is different bytes.\napp.post('/articles', captureRawBody(), guard, (req, res) => {\n  res.status(201).json({ created: true, jti: req.coderifts.payload.jti });\n});\n```\n\nA request without a valid grant gets `403` and never reaches your handler.\n\n## What a refusal looks like\n\n```json\n{\n  \"error\": \"execution_grant_required\",\n  \"status\": \"MALFORMED\",\n  \"reason\": \"missing_grant_header\",\n  \"remedy\": {\n    \"error\": \"CODERIFTS_GRANT_REQUIRED\",\n    \"target\": \"POST /articles\",\n    \"fingerprint\": \"sha256:…\",\n    \"action_required\": { \"tool\": \"preflight_change_set\", \"mode\": \"authorize\", \"args_shape\": { } },\n    \"does_not_promise\": \"a grant does not guarantee execution (CAS may still fail)\"\n  }\n}\n```\n\n`remedy` is present only when the refusal maps to one of three grant error classes\n(`CODERIFTS_GRANT_REQUIRED` / `_INVALID` / `_MISMATCH`). A refusal outside them — an unmapped route,\nfor instance — carries no remedy, because no grant the caller could obtain would change the answer.\n\n## Options\n\n| option | required | meaning |\n|---|---|---|\n| `keysFile` | one of | Path to a registry document `{ keys: [{ kid, public_key_pem, status }] }` |\n| `publicKeyPem` | one of | A single PEM, for the air-gapped case |\n| `kid` | no | Select a specific key from `keysFile` |\n| `operationMap` | yes | `'<METHOD> <route path>' -> operation`. Unmapped routes are refused |\n| `audience` | no | Grants must be bound to this audience when set |\n| `targetId` | no | `(req) => string` — what the grant is bound to. Defaults to `req.params.id` |\n| `header` | no | Grant header name. Default `coderifts-execution-grant` |\n| `now` | no | Clock injection, for tests |\n\nKey material is resolved **once at construction**. There is no request-time key I/O.\n\n## Exports\n\n`requireExecutionGrant`, `captureRawBody`, `verifyExecutionGrant`, `computeScopeHash`,\n`DEFAULT_HEADER`.\n\n## What this does and does not prove\n\nIt proves that a request carried a grant that verifies against the key you pinned, is bound to this\noperation and target, and covers these exact after-payload bytes.\n\nIt does not prove the write happened, that it happened atomically, or that anything downstream\nhonoured the decision. A grant is permission to attempt, not evidence of a result.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-380f84399fd794f7ee5d9e7c5a662f61"}