{"_id":"@3maem/ash-node-sdk","name":"@3maem/ash-node-sdk","dist-tags":{"latest":"1.2.0"},"versions":{"1.2.0":{"name":"@3maem/ash-node-sdk","version":"1.2.0","description":"ASH (Application Security Hash) Node.js SDK — HMAC-SHA256 request signing, verification, and Express/Fastify middleware","type":"module","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"}}},"bin":{"ash":"dist/cli.js"},"keywords":["ash","hmac","sha256","request-signing","api-security","anti-tamper","proof-of-integrity","middleware","express","fastify","cli"],"repository":{"type":"git","url":"git+https://github.com/3maem/ashcore.git","directory":"packages/ash-node-sdk"},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","lint":"eslint src/ tests/","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm run typecheck && npm run test"},"engines":{"node":">=18.0.0"},"license":"Apache-2.0","peerDependencies":{"express":">=4.0.0","fastify":">=4.0.0","ioredis":">=5.0.0"},"peerDependenciesMeta":{"express":{"optional":true},"fastify":{"optional":true},"ioredis":{"optional":true}},"devDependencies":{"@types/node":"^20.0.0","@typescript-eslint/eslint-plugin":"^7.0.0","@typescript-eslint/parser":"^7.0.0","eslint":"^9.0.0","globals":"^14.0.0","tsup":"^8.0.0","typescript":"^5.4.0","vitest":"^2.0.0"},"_id":"@3maem/ash-node-sdk@1.2.0","gitHead":"4acd8b8a17e1fee940748e2ba897c188bc6e27c7","bugs":{"url":"https://github.com/3maem/ashcore/issues"},"homepage":"https://github.com/3maem/ashcore#readme","_nodeVersion":"22.16.0","_npmVersion":"11.4.2","dist":{"integrity":"sha512-22Ht+G69r6f/jyy/kNzmmQBLGS9zMIu2b5Go+lcgHfJkClQ9wKa0CvInE2BS+n7A65naurSBNl1l8eh/Wi7wsA==","shasum":"32540c841ed5772c40a16032e6ebd38e10283fc9","tarball":"https://registry.npmjs.org/@3maem/ash-node-sdk/-/ash-node-sdk-1.2.0.tgz","fileCount":10,"unpackedSize":574090,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHNYbjJ0Q6CFlwwsqPHK1wYC/LwSAd4xLlCAG8v6s0IIAiBsDvD15DIVm32pOqOBQ9QgC/+2qzjXY3HhN+5qo0LGNQ=="}]},"_npmUser":{"name":"3maem","email":"3maem2025@gmail.com"},"directories":{},"maintainers":[{"name":"3maem","email":"3maem2025@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ash-node-sdk_1.2.0_1771717873337_0.792415556491989"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-21T23:51:13.194Z","1.2.0":"2026-02-21T23:51:13.526Z","modified":"2026-02-21T23:51:13.775Z"},"maintainers":[{"name":"3maem","email":"3maem2025@gmail.com"}],"description":"ASH (Application Security Hash) Node.js SDK — HMAC-SHA256 request signing, verification, and Express/Fastify middleware","homepage":"https://github.com/3maem/ashcore#readme","keywords":["ash","hmac","sha256","request-signing","api-security","anti-tamper","proof-of-integrity","middleware","express","fastify","cli"],"repository":{"type":"git","url":"git+https://github.com/3maem/ashcore.git","directory":"packages/ash-node-sdk"},"bugs":{"url":"https://github.com/3maem/ashcore/issues"},"license":"Apache-2.0","readme":"# @3maem/ash-node-sdk\r\n\r\nASH (Application Security Hash) Node.js SDK — HMAC-SHA256 request signing and verification for tamper-proof API communication.\r\n\r\n> **⚠️ Beta Notice:** This is v1.0.0-beta. Feature-complete but may undergo internal refinements. Not recommended for production-critical environments yet.\r\n\r\n## Features\r\n\r\n- **Zero runtime dependencies** — uses only Node.js `crypto`\r\n- **134/134 conformance vectors** — byte-identical output to Rust ASH\r\n- **Three proof modes** — basic, scoped (field-level), unified (scoped + request chaining)\r\n- **Express & Fastify middleware** — drop-in server-side verification\r\n- **Context lifecycle** — one-time-use nonce/proof contexts with TTL\r\n- **Scope policy registry** — route-level field enforcement (exact, param, wildcard patterns)\r\n- **Redis adapter** — production-ready context store via `AshRedisStore`\r\n- **CLI tool** — `ash build`, `ash verify`, `ash inspect` from the terminal\r\n- **Debug trace** — step-by-step pipeline inspection with timing\r\n- **CJS + ESM + DTS** — dual build with full TypeScript declarations\r\n- **1490+ tests** — conformance, PT, security audit, QA, fuzz, property-based\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @3maem/ash-node-sdk\r\n```\r\n\r\n## Quick Start\r\n\r\n### Client: Build a proof\r\n\r\n```ts\r\nimport { ashBuildRequest } from '@3maem/ash-node-sdk';\r\n\r\n// After receiving nonce + contextId from the server:\r\nconst result = ashBuildRequest({\r\n  nonce,           // Server-provided nonce (32+ hex chars)\r\n  contextId,       // Server-provided context ID\r\n  method: 'POST',\r\n  path: '/api/orders',\r\n  body: JSON.stringify({ amount: 100, currency: 'USD' }),\r\n});\r\n\r\n// Send request with ASH headers:\r\n// x-ash-ts, x-ash-nonce, x-ash-body-hash, x-ash-proof, x-ash-context-id\r\n\r\nresult.destroy(); // Clear sensitive data (best-effort in JS)\r\n```\r\n\r\n### Server: Express middleware\r\n\r\n```ts\r\nimport express from 'express';\r\nimport { AshMemoryStore, ashExpressMiddleware } from '@3maem/ash-node-sdk';\r\n\r\nconst app = express();\r\nconst store = new AshMemoryStore({ ttlSeconds: 300 });\r\n\r\napp.use('/api', ashExpressMiddleware({ store }));\r\n\r\napp.get('/api/users', (req, res) => {\r\n  // req.ash contains: { verified, contextId, mode, timestamp, binding }\r\n  res.json({ users: [] });\r\n});\r\n```\r\n\r\n### Server: Fastify plugin\r\n\r\n```ts\r\nimport Fastify from 'fastify';\r\nimport { AshMemoryStore, ashFastifyPlugin } from '@3maem/ash-node-sdk';\r\n\r\nconst fastify = Fastify();\r\nconst store = new AshMemoryStore({ ttlSeconds: 300 });\r\n\r\nfastify.register(ashFastifyPlugin, { store });\r\n\r\nfastify.get('/api/users', async (request) => {\r\n  // request.ash contains: { verified, contextId, mode, timestamp, binding }\r\n  return { users: [] };\r\n});\r\n```\r\n\r\n## Full Client → Server Flow\r\n\r\n```\r\n┌──────────┐                          ┌──────────┐\r\n│  Client   │                          │  Server   │\r\n└─────┬────┘                          └─────┬────┘\r\n      │  1. POST /context                    │\r\n      │     { method: \"GET\", path: \"/api/x\" }│\r\n      │─────────────────────────────────────>│\r\n      │                                      │ Creates context:\r\n      │                                      │   nonce, contextId,\r\n      │                                      │   clientSecret, binding\r\n      │                                      │ Stores in AshMemoryStore\r\n      │  { contextId, nonce, expiresAt }     │\r\n      │<─────────────────────────────────────│\r\n      │                                      │\r\n      │  2. ashBuildRequest(...)              │\r\n      │     → proof, bodyHash, timestamp     │\r\n      │                                      │\r\n      │  3. GET /api/x                       │\r\n      │     x-ash-ts: ...             │\r\n      │     x-ash-nonce: ...                 │\r\n      │     x-ash-body-hash: ...             │\r\n      │     x-ash-proof: ...                 │\r\n      │     x-ash-context-id: ...            │\r\n      │─────────────────────────────────────>│\r\n      │                                      │ Middleware:\r\n      │                                      │   consume(contextId)\r\n      │                                      │   ashVerifyRequest(...)\r\n      │                                      │   req.ash = { verified, ... }\r\n      │  { users: [...] }                    │\r\n      │<─────────────────────────────────────│\r\n      │                                      │\r\n      │  4. Replay same request → 452        │\r\n      │─────────────────────────────────────>│\r\n      │  { error: \"ASH_CTX_ALREADY_USED\" }  │\r\n      │<─────────────────────────────────────│\r\n```\r\n\r\n## API Reference\r\n\r\n### Layer 1: Pure Crypto\r\n\r\n#### `ashDeriveClientSecret(nonce, contextId, binding): string`\r\nDerive HMAC-SHA256 client secret from server nonce.\r\n\r\n#### `ashBuildProof(clientSecret, timestamp, binding, bodyHash): string`\r\nBuild basic HMAC-SHA256 proof.\r\n\r\n#### `ashVerifyProof(nonce, contextId, binding, timestamp, bodyHash, clientProof): boolean`\r\nVerify basic proof (re-derives secret, timing-safe compare).\r\n\r\n#### `ashBuildProofScoped(clientSecret, timestamp, binding, payload, scopeFields): ScopedProofResult`\r\nBuild scoped proof with field extraction.\r\n\r\n#### `ashBuildProofUnified(clientSecret, timestamp, binding, payload, scopeFields, previousProof): UnifiedProofResult`\r\nBuild unified proof with scope + request chaining.\r\n\r\n#### `ashNormalizeBinding(method, path, rawQuery): string`\r\nNormalize request binding to `METHOD|PATH|QUERY` format.\r\n\r\n#### `ashHashBody(body): string`\r\nSHA-256 hash of body content (hex).\r\n\r\n#### `ashCanonicalizeJson(json): string`\r\nRFC 8785 JSON canonicalization (JCS).\r\n\r\n#### `ashCanonicalizeQuery(query): string`\r\nQuery string canonicalization with sorted keys and percent encoding.\r\n\r\n#### `ashTimingSafeEqual(a, b): boolean`\r\nConstant-time string comparison.\r\n\r\n#### `ashValidateNonce(nonce): void`\r\nValidate nonce format (32-512 hex chars).\r\n\r\n#### `ashValidateTimestamp(timestamp, maxAge, clockSkew): number`\r\nValidate timestamp freshness. Returns parsed timestamp value.\r\n\r\n### Layer 2: Server Integration\r\n\r\n#### `ashBuildRequest(input): BuildRequestResult`\r\n7-step build orchestrator. Auto-detects mode:\r\n- No scope, no previousProof → **basic**\r\n- Scope present → **scoped**\r\n- previousProof present → **unified**\r\n\r\n```ts\r\ninterface BuildRequestInput {\r\n  nonce: string;\r\n  contextId: string;\r\n  method: string;\r\n  path: string;\r\n  rawQuery?: string;\r\n  body?: string;\r\n  timestamp?: string;     // Auto-generated if omitted\r\n  scope?: string[];\r\n  previousProof?: string;\r\n}\r\n\r\ninterface BuildRequestResult {\r\n  proof: string;\r\n  bodyHash: string;\r\n  binding: string;\r\n  timestamp: string;\r\n  nonce: string;\r\n  scopeHash?: string;\r\n  chainHash?: string;\r\n  destroy(): void;        // Clear sensitive data (best-effort in JS)\r\n}\r\n```\r\n\r\n#### `ashVerifyRequest(input): VerifyResult`\r\n9-step verify orchestrator. Errors returned (not thrown).\r\n\r\n```ts\r\ninterface VerifyRequestInput {\r\n  headers: Record<string, string | string[] | undefined>;\r\n  method: string;\r\n  path: string;\r\n  rawQuery?: string;\r\n  body?: string;\r\n  nonce: string;\r\n  contextId: string;\r\n  scope?: string[];\r\n  previousProof?: string;\r\n  maxAgeSeconds?: number;     // Default: 300\r\n  clockSkewSeconds?: number;  // Default: 30\r\n}\r\n\r\ninterface VerifyResult {\r\n  ok: boolean;\r\n  error?: AshError;\r\n  meta?: { mode: 'basic' | 'scoped' | 'unified'; timestamp: number; binding: string };\r\n}\r\n```\r\n\r\n#### `ashExtractHeaders(headers): AshHeaderBundle`\r\nExtract and validate ASH headers (case-insensitive, control char rejection, length limits).\r\n\r\n#### `AshMemoryStore`\r\nIn-memory context store with TTL and auto-cleanup.\r\n\r\n```ts\r\nconst store = new AshMemoryStore({\r\n  ttlSeconds: 300,              // Default: 300 (5 min)\r\n  cleanupIntervalSeconds: 60,   // Default: 60 (1 min)\r\n});\r\n\r\nawait store.store(ctx);\r\nawait store.get(id);       // Returns AshContext | null\r\nawait store.consume(id);   // Atomic one-time-use (throws on reuse)\r\nawait store.cleanup();     // Manual expired entry removal\r\nstore.destroy();           // Stop timers, clear store (AshMemoryStore only, not on AshContextStore interface)\r\n```\r\n\r\n#### `AshRedisStore`\r\nRedis-backed context store for production deployments. Uses Lua scripting for atomic consume.\r\n\r\n```ts\r\nimport { AshRedisStore } from '@3maem/ash-node-sdk';\r\nimport Redis from 'ioredis';\r\n\r\nconst store = new AshRedisStore({\r\n  client: new Redis(),\r\n  keyPrefix: 'ash:ctx:',  // Default\r\n  ttlSeconds: 300,         // Default: 300\r\n});\r\n```\r\n\r\n#### `AshScopePolicyRegistry`\r\nRoute-level scope field enforcement.\r\n\r\n```ts\r\nconst registry = new AshScopePolicyRegistry();\r\n\r\n// Exact match (required: true rejects requests without scope headers)\r\nregistry.register({ pattern: 'POST /api/orders', fields: ['amount', 'currency'], required: true });\r\n\r\n// Param match\r\nregistry.register({ pattern: 'PUT /api/orders/:id', fields: ['status'] });\r\n\r\n// Wildcard match\r\nregistry.register({ pattern: 'GET /api/*', fields: [] });\r\n\r\n// Match priority: exact (3) > param (2) > wildcard (1)\r\nconst match = registry.match('POST', '/api/orders');\r\n// → { policy: { pattern: 'POST /api/orders', fields: ['amount', 'currency'] }, params: {} }\r\n```\r\n\r\n#### `ashExpressMiddleware(options)`\r\nExpress middleware factory.\r\n\r\n```ts\r\ninterface AshMiddlewareOptions {\r\n  store: AshContextStore;\r\n  scopeRegistry?: AshScopePolicyRegistry;\r\n  maxAgeSeconds?: number;        // Default: 300\r\n  clockSkewSeconds?: number;     // Default: 30\r\n  onError?: (error: AshError, req: unknown, res: unknown) => void;\r\n  extractBody?: (req: unknown) => string | undefined;\r\n}\r\n```\r\n\r\n#### `ashFastifyPlugin(fastify, options)`\r\nFastify plugin with same options as Express middleware.\r\n\r\n### Error Handling\r\n\r\nAll errors use `AshError` with typed codes and HTTP status mapping:\r\n\r\n| Code | HTTP | Description |\r\n|------|------|-------------|\r\n| `ASH_CTX_NOT_FOUND` | 450 | Context not found |\r\n| `ASH_CTX_EXPIRED` | 451 | Context has expired |\r\n| `ASH_CTX_ALREADY_USED` | 452 | Context already consumed |\r\n| `ASH_PROOF_INVALID` | 460 | Proof verification failed |\r\n| `ASH_BINDING_MISMATCH` | 461 | Binding mismatch |\r\n| `ASH_SCOPE_MISMATCH` | 473 | Scope mismatch |\r\n| `ASH_CHAIN_BROKEN` | 474 | Chain broken |\r\n| `ASH_SCOPED_FIELD_MISSING` | 475 | Scoped field missing |\r\n| `ASH_TIMESTAMP_INVALID` | 482 | Timestamp invalid |\r\n| `ASH_PROOF_MISSING` | 483 | Proof missing |\r\n| `ASH_CANONICALIZATION_ERROR` | 484 | Canonicalization error |\r\n| `ASH_VALIDATION_ERROR` | 485 | Validation error |\r\n| `ASH_MODE_VIOLATION` | 486 | Mode violation |\r\n| `ASH_UNSUPPORTED_CONTENT_TYPE` | 415 | Unsupported content type |\r\n| `ASH_INTERNAL_ERROR` | 500 | Internal error |\r\n\r\n### Layer 3: CLI & Debug\r\n\r\n#### CLI Tool\r\n\r\nThe SDK ships with an `ash` CLI for terminal-based proof operations. Zero dependencies — uses Node.js built-in `parseArgs`.\r\n\r\n```bash\r\n# Build a proof\r\nash build --nonce <hex> --context-id <id> --method POST --path /api/orders \\\r\n  --body '{\"amount\":100}' --json\r\n\r\n# Verify a proof\r\nash verify --nonce <hex> --context-id <id> --method POST --path /api/orders \\\r\n  --proof <hex> --body-hash <hex> --timestamp <unix> \\\r\n  --max-age 300 --clock-skew 30 --json\r\n\r\n# Hash operations\r\nash hash body '{\"amount\":100}'       # SHA-256 of canonical body\r\nash hash scope amount currency       # SHA-256 of sorted scope fields\r\nash hash proof <hex>                 # SHA-256 of proof for chaining\r\n\r\n# Derive client secret\r\nash derive --nonce <hex> --context-id <id> --binding \"POST|/api/orders|\"\r\n\r\n# Debug trace (step-by-step pipeline inspection)\r\nash inspect build --nonce <hex> --context-id <id> --method GET --path /api\r\nash inspect verify --nonce <hex> --context-id <id> --method GET --path /api \\\r\n  --proof <hex> --body-hash <hex> --timestamp <unix>\r\n\r\n# Version\r\nash version\r\n# @3maem/ash-node-sdk v1.2.0\r\n```\r\n\r\n**Exit codes:** `0` success, `1` invalid proof, `2` usage error, `3` internal error\r\n\r\nAll commands support `--json` for machine-readable output and `--help` for usage info.\r\n\r\n#### Debug Trace (Programmatic)\r\n\r\n```ts\r\nimport { ashBuildRequestDebug, ashVerifyRequestDebug, ashFormatTrace } from '@3maem/ash-node-sdk';\r\n\r\n// Build with trace\r\nconst result = ashBuildRequestDebug({\r\n  nonce, contextId, method: 'POST', path: '/api/orders',\r\n  body: '{\"amount\":100}',\r\n});\r\n\r\nconsole.log(result.proof);          // Same output as ashBuildRequest()\r\nconsole.log(result.mode);           // 'basic' | 'scoped' | 'unified'\r\nconsole.log(result.totalDurationMs);\r\nconsole.log(ashFormatTrace(result.trace));\r\n// [1/7] validate_nonce .............. OK (0.01ms)\r\n// [2/7] validate_timestamp ......... OK (0.00ms)\r\n// [3/7] normalize_binding .......... OK (0.02ms)\r\n// [4/7] hash_body .................. OK (0.03ms)\r\n// [5/7] derive_secret .............. OK (0.01ms)\r\n//       clientSecret: \"[REDACTED]\"\r\n// [6/7] build_proof ................ OK (0.02ms)\r\n// [7/7] assemble_result ............ OK (0.00ms)\r\n\r\n// Verify with trace\r\nconst verifyResult = ashVerifyRequestDebug({\r\n  headers, method: 'POST', path: '/api/orders',\r\n  body: '{\"amount\":100}', nonce, contextId,\r\n});\r\n\r\nconsole.log(verifyResult.ok);       // true/false\r\nconsole.log(verifyResult.trace);    // 9 steps with timing\r\n```\r\n\r\nSensitive values (client secrets, full proofs) are always REDACTED in trace output.\r\n\r\n### Header Constants\r\n\r\n```ts\r\nimport {\r\n  X_ASH_TIMESTAMP,    // 'x-ash-ts'\r\n  X_ASH_NONCE,        // 'x-ash-nonce'\r\n  X_ASH_BODY_HASH,    // 'x-ash-body-hash'\r\n  X_ASH_PROOF,        // 'x-ash-proof'\r\n  X_ASH_CONTEXT_ID,   // 'x-ash-context-id'\r\n} from '@3maem/ash-node-sdk';\r\n```\r\n\r\n## Examples\r\n\r\nSee [`examples/`](./examples/) for complete working examples:\r\n- `express-example.ts` — Full Express client→server flow\r\n- `fastify-example.ts` — Full Fastify client→server flow with scoped proofs\r\n\r\n## Requirements\r\n\r\n- Node.js >= 18.0.0\r\n\r\n### Peer Dependencies (optional)\r\n\r\n- `express` >= 4.0.0 — for `ashExpressMiddleware()`\r\n- `fastify` >= 4.0.0 — for `ashFastifyPlugin()`\r\n- `ioredis` >= 5.0.0 — for `AshRedisStore`\r\n\r\n## Links\r\n\r\n- [Website](https://ashcore.ai)\r\n- [GitHub](https://github.com/3maem/ashcore)\r\n- [npm](https://www.npmjs.com/package/@3maem/ash-node-sdk)\r\n\r\n## License\r\n\r\nApache-2.0\r\n","readmeFilename":"README.md","_rev":"1-d56246c5780a61684fa899b879e2098c"}