@nestm/storage 0.1.0-alpha.5 → 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +158 -92
  3. package/SECURITY.md +40 -0
  4. package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +63 -0
  5. package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -0
  6. package/dist/ai-sdk/ai-sdk-workspace-tools.js +383 -0
  7. package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -0
  8. package/dist/ai-sdk/index.d.ts +2 -0
  9. package/dist/ai-sdk/index.d.ts.map +1 -0
  10. package/dist/ai-sdk/index.js +2 -0
  11. package/dist/ai-sdk/index.js.map +1 -0
  12. package/dist/files-sdk/files-sdk.driver.d.ts +16 -2
  13. package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
  14. package/dist/files-sdk/files-sdk.driver.js +159 -0
  15. package/dist/files-sdk/files-sdk.driver.js.map +1 -1
  16. package/dist/files-sdk/fs/index.d.ts +21 -2
  17. package/dist/files-sdk/fs/index.d.ts.map +1 -1
  18. package/dist/files-sdk/fs/index.js +600 -1
  19. package/dist/files-sdk/fs/index.js.map +1 -1
  20. package/dist/files-sdk/index.d.ts +1 -1
  21. package/dist/files-sdk/index.d.ts.map +1 -1
  22. package/dist/files-sdk/index.js.map +1 -1
  23. package/dist/files-sdk/provider/index.d.ts.map +1 -1
  24. package/dist/files-sdk/provider/index.js +7 -0
  25. package/dist/files-sdk/provider/index.js.map +1 -1
  26. package/dist/files-sdk/s3/index.d.ts +12 -3
  27. package/dist/files-sdk/s3/index.d.ts.map +1 -1
  28. package/dist/files-sdk/s3/index.js +203 -37
  29. package/dist/files-sdk/s3/index.js.map +1 -1
  30. package/dist/storage.client.d.ts +5 -1
  31. package/dist/storage.client.d.ts.map +1 -1
  32. package/dist/storage.client.js +49 -0
  33. package/dist/storage.client.js.map +1 -1
  34. package/dist/storage.driver.d.ts +3 -1
  35. package/dist/storage.driver.d.ts.map +1 -1
  36. package/dist/storage.driver.js.map +1 -1
  37. package/dist/storage.service.d.ts.map +1 -1
  38. package/dist/storage.service.js +28 -3
  39. package/dist/storage.service.js.map +1 -1
  40. package/dist/storage.types.d.ts +36 -0
  41. package/dist/storage.types.d.ts.map +1 -1
  42. package/dist/storage.types.js.map +1 -1
  43. package/dist/workspace/index.d.ts +4 -0
  44. package/dist/workspace/index.d.ts.map +1 -0
  45. package/dist/workspace/index.js +4 -0
  46. package/dist/workspace/index.js.map +1 -0
  47. package/dist/workspace/storage-workspace.cursor.d.ts +6 -0
  48. package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -0
  49. package/dist/workspace/storage-workspace.cursor.js +45 -0
  50. package/dist/workspace/storage-workspace.cursor.js.map +1 -0
  51. package/dist/workspace/storage-workspace.d.ts +6 -0
  52. package/dist/workspace/storage-workspace.d.ts.map +1 -0
  53. package/dist/workspace/storage-workspace.error.d.ts +28 -0
  54. package/dist/workspace/storage-workspace.error.d.ts.map +1 -0
  55. package/dist/workspace/storage-workspace.error.js +67 -0
  56. package/dist/workspace/storage-workspace.error.js.map +1 -0
  57. package/dist/workspace/storage-workspace.js +747 -0
  58. package/dist/workspace/storage-workspace.js.map +1 -0
  59. package/dist/workspace/storage-workspace.path.d.ts +9 -0
  60. package/dist/workspace/storage-workspace.path.d.ts.map +1 -0
  61. package/dist/workspace/storage-workspace.path.js +68 -0
  62. package/dist/workspace/storage-workspace.path.js.map +1 -0
  63. package/dist/workspace/storage-workspace.types.d.ts +97 -0
  64. package/dist/workspace/storage-workspace.types.d.ts.map +1 -0
  65. package/dist/workspace/storage-workspace.types.js +20 -0
  66. package/dist/workspace/storage-workspace.types.js.map +1 -0
  67. package/package.json +23 -20
  68. package/dist/artifacts/artifact-storage.d.ts +0 -161
  69. package/dist/artifacts/artifact-storage.d.ts.map +0 -1
  70. package/dist/artifacts/artifact-storage.js +0 -452
  71. package/dist/artifacts/artifact-storage.js.map +0 -1
  72. package/dist/artifacts/crypto/codec.d.ts +0 -44
  73. package/dist/artifacts/crypto/codec.d.ts.map +0 -1
  74. package/dist/artifacts/crypto/codec.js +0 -273
  75. package/dist/artifacts/crypto/codec.js.map +0 -1
  76. package/dist/artifacts/crypto/context.d.ts +0 -48
  77. package/dist/artifacts/crypto/context.d.ts.map +0 -1
  78. package/dist/artifacts/crypto/context.js +0 -37
  79. package/dist/artifacts/crypto/context.js.map +0 -1
  80. package/dist/artifacts/crypto/dek-cache.d.ts +0 -38
  81. package/dist/artifacts/crypto/dek-cache.d.ts.map +0 -1
  82. package/dist/artifacts/crypto/dek-cache.js +0 -137
  83. package/dist/artifacts/crypto/dek-cache.js.map +0 -1
  84. package/dist/artifacts/crypto/index.d.ts +0 -25
  85. package/dist/artifacts/crypto/index.d.ts.map +0 -1
  86. package/dist/artifacts/crypto/index.js +0 -29
  87. package/dist/artifacts/crypto/index.js.map +0 -1
  88. package/dist/artifacts/crypto/key-provider.d.ts +0 -44
  89. package/dist/artifacts/crypto/key-provider.d.ts.map +0 -1
  90. package/dist/artifacts/crypto/key-provider.js +0 -36
  91. package/dist/artifacts/crypto/key-provider.js.map +0 -1
  92. package/dist/artifacts/crypto/kms-provider.d.ts +0 -16
  93. package/dist/artifacts/crypto/kms-provider.d.ts.map +0 -1
  94. package/dist/artifacts/crypto/kms-provider.js +0 -90
  95. package/dist/artifacts/crypto/kms-provider.js.map +0 -1
  96. package/dist/artifacts/crypto/local-provider.d.ts +0 -17
  97. package/dist/artifacts/crypto/local-provider.d.ts.map +0 -1
  98. package/dist/artifacts/crypto/local-provider.js +0 -58
  99. package/dist/artifacts/crypto/local-provider.js.map +0 -1
  100. package/dist/artifacts/env-config.d.ts +0 -15
  101. package/dist/artifacts/env-config.d.ts.map +0 -1
  102. package/dist/artifacts/env-config.js +0 -95
  103. package/dist/artifacts/env-config.js.map +0 -1
  104. package/dist/artifacts/index.d.ts +0 -6
  105. package/dist/artifacts/index.d.ts.map +0 -1
  106. package/dist/artifacts/index.js +0 -6
  107. package/dist/artifacts/index.js.map +0 -1
  108. package/dist/artifacts/nest/index.d.ts +0 -29
  109. package/dist/artifacts/nest/index.d.ts.map +0 -1
  110. package/dist/artifacts/nest/index.js +0 -129
  111. package/dist/artifacts/nest/index.js.map +0 -1
  112. package/dist/artifacts/object-store.d.ts +0 -27
  113. package/dist/artifacts/object-store.d.ts.map +0 -1
  114. package/dist/artifacts/object-store.js +0 -198
  115. package/dist/artifacts/object-store.js.map +0 -1
  116. package/dist/artifacts/storage-driver.d.ts +0 -48
  117. package/dist/artifacts/storage-driver.d.ts.map +0 -1
  118. package/dist/artifacts/storage-driver.js +0 -89
  119. package/dist/artifacts/storage-driver.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"artifact-storage.js","sourceRoot":"","sources":["../../src/artifacts/artifact-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n StorageErrorCode,\n isStorageError,\n type StorageClient,\n} from '../core/index.js';\nimport AdmZip from 'adm-zip';\nimport { extname } from 'node:path';\n\nimport {\n ENVELOPE_MAX_OVERHEAD_BYTES,\n open,\n seal,\n SCOPE_FROM_HEADER,\n type EnvelopeContext,\n type StorageCrypto,\n} from './crypto/index.js';\nimport {\n createArtifactStorageClient,\n DEFAULT_OBJECT_NAMESPACES,\n resolveObjectNamespaces,\n type ArtifactStorageConfig,\n} from './storage-driver.js';\n\nexport {\n assertStorageProvider,\n defaultFsRoot,\n type ArtifactStorageConfig,\n} from './storage-driver.js';\n\n/**\n * Storage for uploaded artifact files, keyed `<artifactId>/<path>` (e.g. `<id>/index.html`,\n * `<id>/assets/app.js`). The api writes; the sandbox reads. The `fs` provider keeps both pointed\n * at one directory (fine for a single host / shared volume); any object-store provider — S3, GCS,\n * Azure, R2, and the rest — lets the two services share storage without a shared volume, which is\n * what running them on separate hosts (as on ECS) requires.\n *\n * Every object is stored as a CAE1 encryption envelope (ADR-0001, SEC-03): callers must say who\n * owns what they write (`ScopeRef`) and what they expect to read (`ReadRef`) — the codec binds\n * ciphertext to `{scope, artifactId, version, path}` so a swapped or replayed object fails closed.\n */\nexport interface ArtifactStorage {\n /** Single self-contained HTML artifact → `<id>/index.html`. */\n writeHtml(artifactId: string, buffer: Buffer, ref: ScopeRef): Promise<void>;\n /**\n * Pre-built SPA bundle (a zip whose root contains index.html). Encrypted per extracted entry.\n * With `opts.versionId` (VER-03), entries land under `v/<versionId>/…` and are sealed with\n * `ctx.version = versionId` (the caller's `ref.version` is overridden to match — the path⇔version\n * biconditional is derived, never trusted). Reserved-namespace screening applies to the zip\n * ENTRY name in both modes: a bundle shipping `v/…` or `__…` files has those skipped, not nested.\n */\n writeBundle(\n artifactId: string,\n zipBuffer: Buffer,\n ref: ScopeRef,\n opts?: WriteBundleOptions,\n ): Promise<void>;\n /**\n * Write an arbitrary file within the artifact (e.g. `__vendor/<hash>.js`, `__meta.json`).\n * Rejects any `relPath` escaping the artifact directory. `contentType` rides the envelope\n * header (authenticated) on both backends.\n */\n writeFile(\n artifactId: string,\n relPath: string,\n buffer: Buffer,\n ref: ScopeRef,\n contentType?: string,\n ): Promise<void>;\n /** Server-rendered PNG preview → reserved key `<id>/__thumb.png`. */\n writeThumbnail(\n artifactId: string,\n pngBuffer: Buffer,\n ref: ScopeRef,\n ): Promise<void>;\n /** Delete every file under the artifact (includes the thumbnail). */\n remove(artifactId: string): Promise<void>;\n /**\n * Read a file within the artifact; `relPath === \"\"` resolves to index.html. `null` if absent.\n * Throws `EnvelopeError` on any decrypt failure — a corrupt or swapped object must never be\n * mistaken for a missing one (no SPA fallback over tampering).\n */\n read(\n artifactId: string,\n relPath: string,\n ref: ReadRef,\n options?: ArtifactReadOptions,\n ): Promise<Buffer | null>;\n /**\n * Like `read`, but also returns the envelope's AUTHENTICATED content type (VER-03). This is the\n * read primitive for publish/CoW copy loops: a decrypt→re-encrypt copy must re-seal with the\n * same `ct` it opened (the type is AAD-bound and steers serving), and `read()` discards it.\n * `contentType` is undefined only for legacy plaintext pass-throughs (nothing was verified).\n */\n readWithInfo(\n artifactId: string,\n relPath: string,\n ref: ReadRef,\n options?: ArtifactReadOptions,\n ): Promise<StoredObject | null>;\n /**\n * Delete specific files under the artifact (VER-03: publish prunes root paths removed by the\n * newly-materialized version). Paths escaping the artifact directory and missing paths are\n * ignored (idempotent — publish repair re-runs the same prune).\n */\n deleteFiles(artifactId: string, relPaths: string[]): Promise<void>;\n /**\n * List stored objects under the artifact (optionally under `subPrefix`, e.g. `v/<versionId>/`).\n * Returns artifact-relative forward-slash paths + last-modified stamps, sorted by path. Reads\n * no object bodies and needs no crypto — it serves the sweepers/invariants (version-prefix\n * existence, orphan-prefix age, canonical bundle enumeration), never the serving plane.\n */\n listFiles(artifactId: string, subPrefix?: string): Promise<StoredFileInfo[]>;\n}\n\n/** One listed storage object (metadata only — the body stays sealed). */\nexport interface StoredFileInfo {\n /** Artifact-relative path, forward slashes (e.g. `index.html`, `v/<vid>/index.html`). */\n path: string;\n lastModified: Date;\n}\n\n/** A decrypted object plus the authenticated content type it was sealed with (see readWithInfo). */\nexport interface StoredObject {\n plain: Buffer;\n /** From the envelope header (`ct`, AAD-bound); undefined for legacy plaintext pass-throughs. */\n contentType?: string;\n}\n\n/** Bounds decrypted data while also bounding the opaque envelope downloaded before decryption. */\nexport interface ArtifactReadOptions {\n readonly maxPlainBytes?: number;\n}\n\n/** Permanent, address-independent read rejection safe for callers to map to a bounded 4xx. */\nexport class ArtifactReadLimitError extends Error {\n constructor(readonly maxPlainBytes: number) {\n super(`artifact plaintext exceeds the ${maxPlainBytes} byte read limit`);\n this.name = 'ArtifactReadLimitError';\n }\n}\n\n/** Permanent rejection for a ZIP whose declared expansion exceeds the public protocol limits. */\nexport class ArtifactBundleLimitError extends Error {\n constructor(readonly reason: 'entries' | 'entry-bytes' | 'total-bytes') {\n super(`artifact bundle exceeds the ${reason} limit`);\n this.name = 'ArtifactBundleLimitError';\n }\n}\n\nexport const ARTIFACT_BUNDLE_MAX_ENTRIES = 1_000;\nexport const ARTIFACT_BUNDLE_MAX_ENTRY_BYTES = 64 * 1024 * 1024;\nexport const ARTIFACT_BUNDLE_MAX_TOTAL_BYTES = 256 * 1024 * 1024;\n\n/** Reserved storage path for the generated PNG preview, served by the sandbox as `<id>/__thumb.png`. */\nexport const THUMBNAIL_KEY = '__thumb.png';\n\n/** Options for writeBundle (VER-03 versioned bundle writes). */\nexport interface WriteBundleOptions {\n /** Write entries under `v/<versionId>/…` with `ctx.version = versionId` instead of the root. */\n versionId?: string;\n /** Optional stricter limits; values may lower, but never raise, the protocol hard limits. */\n limits?: {\n maxEntries?: number;\n maxEntryBytes?: number;\n maxTotalBytes?: number;\n };\n}\n\n/** Who owns an object being written; `version` stays null until VER-02 introduces versions. */\nexport interface ScopeRef {\n /** `org:<organizationId>` | `user:<ownerUserId>` | `upload:<uploadId>` — see artifactScope(). */\n scope: string;\n version?: string | null;\n}\n\n/**\n * What the reader independently knows. The api asserts the scope it fetched from the DB; the\n * sandbox — which only knows the artifactId — passes SCOPE_FROM_HEADER and relies on the wrap\n * layer to enforce scope cryptographically (see crypto/context.ts).\n */\nexport interface ReadRef {\n // `string` absorbs the literal for the checker, but the sentinel is domain vocabulary\n // here — collapsing the union would erase what @from-header means to a reader.\n // eslint-disable-next-line typescript/no-redundant-type-constituents\n scope: string | typeof SCOPE_FROM_HEADER;\n version?: string | null;\n}\n\nexport async function createArtifactStorage(\n cfg: ArtifactStorageConfig,\n crypto: StorageCrypto,\n): Promise<ArtifactStorage> {\n return createArtifactStorageWithClient(\n cfg,\n crypto,\n await createArtifactStorageClient(cfg),\n );\n}\n\n/** Framework-neutral domain adapter used by both direct consumers and the Nest composition entry. */\nexport function createArtifactStorageWithClient(\n cfg: ArtifactStorageConfig,\n crypto: StorageCrypto,\n client: StorageClient,\n): ArtifactStorage {\n return new ClientArtifactStorage(\n crypto,\n client,\n resolveObjectNamespaces(cfg.objectNamespaces ?? DEFAULT_OBJECT_NAMESPACES),\n cfg.provider === 'fs',\n );\n}\n\n/**\n * Storage path prefix for a version's objects (AP-002): `\"\"` for a backfilled `canonical_v1`\n * version (its bytes ARE the canonical root objects) and `v/<versionId>/` for a `versioned` one.\n */\nexport function versionPrefix(\n layout: 'canonical_v1' | 'versioned',\n versionId: string,\n): string {\n assertVersionId(versionId);\n return layout === 'canonical_v1' ? '' : `v/${versionId}/`;\n}\n\n/**\n * Envelope `ctx.version` for a version's objects (AP-002): `null` for `canonical_v1` (root AAD —\n * the serving plane is version-free forever) and the version UUID, byte-identical to the path\n * segment, for `versioned`.\n */\nexport function versionAad(\n layout: 'canonical_v1' | 'versioned',\n versionId: string,\n): string | null {\n assertVersionId(versionId);\n return layout === 'canonical_v1' ? null : versionId;\n}\n\n/** Artifact ids are one storage segment; `_objects` is the filesystem object-store namespace. */\nexport function assertArtifactId(artifactId: string): void {\n assertStorageSegment(artifactId, 'artifact id');\n if (artifactId.toLowerCase() === '_objects') {\n throw new Error('storage: artifact id \"_objects\" is reserved');\n }\n}\n\n/** Version ids are one storage segment because they are interpolated under `v/<id>/`. */\nexport function assertVersionId(versionId: string): void {\n assertStorageSegment(versionId, 'version id');\n}\n\nfunction assertStorageSegment(value: string, label: string): void {\n if (\n value === '' ||\n value === '.' ||\n value === '..' ||\n value.includes('/') ||\n value.includes('\\\\') ||\n value.includes('\\0') ||\n Buffer.byteLength(value, 'utf8') > 255\n ) {\n throw new Error(`storage: invalid ${label} ${JSON.stringify(value)}`);\n }\n}\n\nfunction normalizeRelativePath(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\nfunction isUnsafeRelativePath(\n path: string,\n allowEmpty = false,\n allowTrailingSlash = false,\n): boolean {\n if (path === '') return !allowEmpty;\n if (path.startsWith('/') || path.includes('\\0')) {\n return true;\n }\n const segments = path.split('/');\n if (allowTrailingSlash && segments.at(-1) === '') segments.pop();\n return segments.some(\n (segment) => segment === '' || segment === '.' || segment === '..',\n );\n}\n\n/**\n * Is this artifact-relative path inside the namespaces VER-02 reserves for the platform — the\n * `v/` version prefix and the `__`-internal families (meta/thumb/source/snapshot/overflow) —\n * with the one serving exemption, top-level single-segment `__vendor/<name>` pool assets?\n * User-supplied bundle entries matching this are SKIPPED at write time (writeBundle), exactly\n * like zip-slip entries: the sandbox deny-guard will never serve them, and letting them through\n * to the seal chokepoint would turn a hostile/quirky zip into a mid-write throw.\n */\nexport function isReservedArtifactPath(relPath: string): boolean {\n const segments = relPath.split('/');\n if (segments[0]?.toLowerCase() === 'v') return true;\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n if (seg === undefined || !seg.startsWith('__')) continue;\n if (\n i === 0 &&\n seg === '__vendor' &&\n segments.length === 2 &&\n segments[1] !== ''\n )\n continue;\n return true;\n }\n return false;\n}\n\n/**\n * AP-002 storage rule 2: `path.startsWith(\"v/\" + version + \"/\") ⇔ version !== null`. Root objects\n * never live under the reserved `v/` namespace and versioned objects never live anywhere else —\n * asserted at the single ctx chokepoint (covers reads and writes, both backends), so a\n * mis-addressed artifact object is impossible rather than unlikely. Throws (fail-closed): this is\n * a programming error, never user input — every USER-named path (zip entries) is screened by\n * `isReservedArtifactPath` before it can reach this assertion.\n */\nexport function assertVersionPath(path: string, version: string | null): void {\n if (isUnsafeRelativePath(path)) {\n throw new Error(`storage: invalid artifact path ${JSON.stringify(path)}`);\n }\n if (version !== null) {\n assertVersionId(version);\n if (!path.startsWith(`v/${version}/`)) {\n throw new Error(\n `storage: versioned object path \"${path}\" must live under \"v/${version}/\" (AP-002 rule 2)`,\n );\n }\n } else if (path.split('/', 1)[0]?.toLowerCase() === 'v') {\n throw new Error(\n `storage: root object path \"${path}\" is inside the reserved v/ namespace (AP-002 rule 2)`,\n );\n }\n}\n\ntype RawRead =\n | { kind: 'hit'; raw: Buffer; rel: string }\n | { kind: 'miss' }\n | { kind: 'error'; error: unknown };\n\ninterface PreparedBundleEntry {\n name: string;\n body: Buffer;\n}\n\nfunction bundleLimit(\n requested: number | undefined,\n hardLimit: number,\n name: string,\n): number {\n const value = requested ?? hardLimit;\n if (!Number.isSafeInteger(value) || value < 1 || value > hardLimit) {\n throw new RangeError(\n `${name} must be an integer between 1 and ${hardLimit}`,\n );\n }\n return value;\n}\n\nfunction prepareBundle(\n zipBuffer: Buffer,\n limits: WriteBundleOptions['limits'],\n): PreparedBundleEntry[] {\n const maxEntries = bundleLimit(\n limits?.maxEntries,\n ARTIFACT_BUNDLE_MAX_ENTRIES,\n 'maxEntries',\n );\n const maxEntryBytes = bundleLimit(\n limits?.maxEntryBytes,\n ARTIFACT_BUNDLE_MAX_ENTRY_BYTES,\n 'maxEntryBytes',\n );\n const maxTotalBytes = bundleLimit(\n limits?.maxTotalBytes,\n ARTIFACT_BUNDLE_MAX_TOTAL_BYTES,\n 'maxTotalBytes',\n );\n const entries = new AdmZip(zipBuffer).getEntries();\n if (entries.length > maxEntries) {\n throw new ArtifactBundleLimitError('entries');\n }\n\n const accepted: Array<{ entry: (typeof entries)[number]; name: string }> = [];\n const names = new Set<string>();\n let declaredTotal = 0;\n for (const entry of entries) {\n if (entry.isDirectory) continue;\n const name = normalizeRelativePath(entry.entryName).replace(/^\\/+/, '');\n if (isUnsafeRelativePath(name) || isReservedArtifactPath(name)) {\n continue;\n }\n const portableName = name.toLowerCase();\n if (names.has(portableName)) {\n throw new Error(`artifact bundle contains duplicate entry \"${name}\"`);\n }\n names.add(portableName);\n const declaredSize = entry.header.size;\n if (\n !Number.isSafeInteger(declaredSize) ||\n declaredSize < 0 ||\n declaredSize > maxEntryBytes\n ) {\n throw new ArtifactBundleLimitError('entry-bytes');\n }\n declaredTotal += declaredSize;\n if (!Number.isSafeInteger(declaredTotal) || declaredTotal > maxTotalBytes) {\n throw new ArtifactBundleLimitError('total-bytes');\n }\n accepted.push({ entry, name });\n }\n\n // Inflate and validate every accepted entry before the first storage write. This bounds peak\n // memory by maxTotalBytes and prevents a late malformed/oversized entry from leaving a partial\n // bundle behind.\n const prepared: PreparedBundleEntry[] = [];\n let actualTotal = 0;\n for (const { entry, name } of accepted) {\n const body = entry.getData();\n if (body.length > maxEntryBytes) {\n throw new ArtifactBundleLimitError('entry-bytes');\n }\n actualTotal += body.length;\n if (!Number.isSafeInteger(actualTotal) || actualTotal > maxTotalBytes) {\n throw new ArtifactBundleLimitError('total-bytes');\n }\n prepared.push({ name, body });\n }\n return prepared;\n}\n\n/** Envelope context for an artifact-relative path. */\nfunction artifactCtx(\n artifactId: string,\n path: string,\n ref: ScopeRef | ReadRef,\n): EnvelopeContext {\n assertArtifactId(artifactId);\n const version = ref.version ?? null;\n assertVersionPath(path, version);\n return { scope: ref.scope as string, artifactId, version, path };\n}\n\n/** Structured, greppable trace of a legacy plaintext pass-through (SEC-04 migration window). */\nexport function warnLegacyRead(where: string, key: string): void {\n // `console` is deliberate: this package is consumed by apps/sandbox as well as the Nest API,\n // so a framework logger is not available here. It is the only portable sink.\n // eslint-disable-next-line no-console\n console.warn(\n `[storage] legacy plaintext read (${where}): ${key} — pending SEC-04 backfill`,\n );\n}\n\n// ── storage-client backend ───────────────────────────────────────────────────\n/**\n * The one implementation, for every provider. It reaches storage only through `StorageClient`,\n * so filesystem, S3, GCS, Azure, R2 and the rest differ by driver, not by code path here. The\n * filesystem once had a parallel implementation that resolved real paths and probed with\n * `existsSync`; it is gone, and with it the chance of the two drifting on reserved namespaces,\n * version prefixes, or traversal guards.\n */\nclass ClientArtifactStorage implements ArtifactStorage {\n constructor(\n private readonly crypto: StorageCrypto,\n private readonly client: StorageClient,\n private readonly objectNamespaces: ReadonlySet<string>,\n private readonly filesystemLayout: boolean,\n ) {}\n\n private assertArtifactId(artifactId: string): void {\n assertArtifactId(artifactId);\n if (this.objectNamespaces.has(artifactId.toLowerCase())) {\n throw new Error(\n `storage: artifact id ${JSON.stringify(artifactId)} is a reserved object namespace`,\n );\n }\n }\n\n async writeHtml(\n artifactId: string,\n buffer: Buffer,\n ref: ScopeRef,\n ): Promise<void> {\n await this.put(\n artifactId,\n 'index.html',\n buffer,\n ref,\n 'text/html; charset=utf-8',\n );\n }\n\n async writeBundle(\n artifactId: string,\n zipBuffer: Buffer,\n ref: ScopeRef,\n opts?: WriteBundleOptions,\n ): Promise<void> {\n this.assertArtifactId(artifactId);\n // Versioned mode (VER-03): mirror the fs backend — prefix + derived ctx.version, entry-name\n // screening unchanged.\n const versionId = opts?.versionId;\n if (versionId !== undefined) assertVersionId(versionId);\n const prefix = versionId === undefined ? '' : `v/${versionId}/`;\n const effRef: ScopeRef =\n versionId === undefined ? ref : { ...ref, version: versionId };\n const entries = prepareBundle(zipBuffer, opts?.limits);\n for (const { name, body } of entries) {\n // `__vendor/<name>` stays ROOT-scoped in versioned mode — see the fs backend.\n if (prefix !== '' && /^__vendor\\/[^/]+$/.test(name)) {\n await this.put(\n artifactId,\n name,\n body,\n { scope: ref.scope },\n contentTypeFor(name),\n );\n continue;\n }\n await this.put(\n artifactId,\n prefix + name,\n body,\n effRef,\n contentTypeFor(name),\n );\n }\n }\n\n async writeFile(\n artifactId: string,\n relPath: string,\n buffer: Buffer,\n ref: ScopeRef,\n contentType?: string,\n ): Promise<void> {\n const name = normalizeRelativePath(relPath);\n // containment guard at the key level: reject absolute paths, empty, or any traversal segment.\n if (isUnsafeRelativePath(name)) {\n throw new Error(`writeFile: path escapes artifact directory: ${relPath}`);\n }\n await this.put(\n artifactId,\n name,\n buffer,\n ref,\n contentType ?? contentTypeFor(name),\n );\n }\n\n async writeThumbnail(\n artifactId: string,\n pngBuffer: Buffer,\n ref: ScopeRef,\n ): Promise<void> {\n await this.put(artifactId, THUMBNAIL_KEY, pngBuffer, ref, 'image/png');\n }\n\n async remove(artifactId: string): Promise<void> {\n this.assertArtifactId(artifactId);\n for await (const object of this.client.listAll({\n prefix: `${artifactId}/`,\n })) {\n await this.client.delete(object.key);\n }\n }\n\n async listFiles(\n artifactId: string,\n subPrefix = '',\n ): Promise<StoredFileInfo[]> {\n this.assertArtifactId(artifactId);\n const normalizedPrefix = normalizeRelativePath(subPrefix);\n if (isUnsafeRelativePath(normalizedPrefix, true, true)) {\n throw new Error(\n `storage: invalid artifact list prefix ${JSON.stringify(subPrefix)}`,\n );\n }\n const base = `${artifactId}/`;\n const out: StoredFileInfo[] = [];\n for await (const object of this.client.listAll({\n prefix: `${base}${normalizedPrefix}`,\n })) {\n if (!object.key.startsWith(base)) continue;\n out.push({\n path: object.key.slice(base.length),\n lastModified: object.lastModified ?? new Date(0),\n });\n }\n return out.sort((a, b) => (a.path < b.path ? -1 : 1));\n }\n\n async read(\n artifactId: string,\n relPath: string,\n ref: ReadRef,\n options?: ArtifactReadOptions,\n ): Promise<Buffer | null> {\n const out = await this.readWithInfo(artifactId, relPath, ref, options);\n return out === null ? null : out.plain;\n }\n\n /**\n * Raw bytes for one artifact-relative path. Reports a plain miss and a provider failure\n * separately so the caller can retry a directory-shaped key without ever turning a genuine\n * backend failure into a 404.\n */\n private async fetchRaw(\n artifactId: string,\n rel: string,\n options: ArtifactReadOptions | undefined,\n preflight = true,\n ): Promise<RawRead> {\n const key = `${artifactId}/${rel}`;\n if (preflight && !(await preflightRawRead(this.client, key, options)))\n return { kind: 'miss' };\n try {\n const bytes = await this.client.downloadBytes(key, {\n maxBytes: rawReadLimit(options),\n });\n return { kind: 'hit', raw: Buffer.from(bytes), rel };\n } catch (error) {\n if (isStorageError(error) && error.code === StorageErrorCode.NOT_FOUND) {\n return { kind: 'miss' };\n }\n // A read limit is the caller's own bound, not a backend condition: it must surface as\n // itself rather than as a miss the index.html rewrite would paper over.\n if (\n isStorageError(error) &&\n error.code === StorageErrorCode.LIMIT_EXCEEDED\n ) {\n throw new ArtifactReadLimitError(requirePlainReadLimit(options));\n }\n return { kind: 'error', error };\n }\n }\n\n async readWithInfo(\n artifactId: string,\n relPath: string,\n ref: ReadRef,\n options?: ArtifactReadOptions,\n ): Promise<StoredObject | null> {\n this.assertArtifactId(artifactId);\n const normalized = normalizeRelativePath(relPath)\n .replace(/\\/{2,}/g, '/')\n .replace(/^\\/+|\\/+$/g, '');\n const rel = normalized === '' ? 'index.html' : normalized;\n if (isUnsafeRelativePath(rel)) return null;\n\n // A directory-shaped request (\"\" or \"docs\") serves that directory's index.html. Providers\n // disagree on how a directory key fails — an object store has no such key at all, while the\n // filesystem reports reading a directory as its own error — so the rewrite is driven by the\n // miss, not by probing the backend. The AAD binds to the path actually read, never to the\n // one requested (ADR D6.1).\n // A filesystem HEAD cannot distinguish a file from a directory candidate. Skip that one\n // preflight so a directory inode size cannot trigger the caller's plaintext limit before we\n // try its bounded `index.html`; downloadBytes still enforces the raw-byte ceiling.\n const direct = await this.fetchRaw(\n artifactId,\n rel,\n options,\n !this.filesystemLayout,\n );\n if (direct.kind === 'error' && !this.filesystemLayout) {\n throw direct.error;\n }\n let resolved = direct;\n if (resolved.kind !== 'hit') {\n const fallback = await this.fetchRaw(\n artifactId,\n `${rel}/index.html`,\n options,\n );\n if (fallback.kind === 'error') throw fallback.error;\n if (fallback.kind === 'miss') {\n if (direct.kind === 'error') throw direct.error;\n return null;\n }\n resolved = fallback;\n }\n\n const out = await open(\n resolved.raw,\n artifactCtx(artifactId, resolved.rel, ref),\n this.crypto.keyProvider,\n this.crypto.allowLegacyPlaintext === undefined\n ? {}\n : { allowLegacyPlaintext: this.crypto.allowLegacyPlaintext },\n );\n enforcePlainReadLimit(out.plain, options);\n if (out.legacy) warnLegacyRead('storage', `${artifactId}/${resolved.rel}`);\n return {\n plain: out.plain,\n ...(out.contentType === undefined\n ? {}\n : { contentType: out.contentType }),\n };\n }\n\n async deleteFiles(artifactId: string, relPaths: string[]): Promise<void> {\n this.assertArtifactId(artifactId);\n for (const relPath of relPaths) {\n const name = normalizeRelativePath(relPath);\n // containment at the key level: reject absolute/empty paths and traversal segments.\n if (isUnsafeRelativePath(name)) {\n continue;\n }\n await this.client.delete(`${artifactId}/${name}`);\n }\n }\n\n /** The single s3 write primitive; enveloped objects are opaque octet streams to the bucket. */\n private async put(\n artifactId: string,\n relPath: string,\n body: Buffer,\n ref: ScopeRef,\n contentType: string,\n ): Promise<void> {\n this.assertArtifactId(artifactId);\n const sealed = await seal(\n body,\n artifactCtx(artifactId, relPath, ref),\n this.crypto.keyProvider,\n contentType,\n );\n await this.client.upload(`${artifactId}/${relPath}`, sealed, {\n // The real content type is authenticated inside CAE1 and must not leak through S3 metadata.\n contentType: 'application/octet-stream',\n });\n }\n}\n\nfunction requirePlainReadLimit(\n options: ArtifactReadOptions | undefined,\n): number {\n const limit = options?.maxPlainBytes;\n if (limit === undefined || !Number.isSafeInteger(limit) || limit < 0) {\n throw new Error('maxPlainBytes must be a non-negative safe integer');\n }\n return limit;\n}\n\nfunction rawReadLimit(options: ArtifactReadOptions | undefined): number {\n if (options?.maxPlainBytes === undefined) return Infinity;\n return requirePlainReadLimit(options) + ENVELOPE_MAX_OVERHEAD_BYTES;\n}\n\nasync function preflightRawRead(\n client: StorageClient,\n key: string,\n options: ArtifactReadOptions | undefined,\n): Promise<boolean> {\n if (options?.maxPlainBytes === undefined) return true;\n try {\n const metadata = await client.head(key);\n if (metadata.size > rawReadLimit(options)) {\n throw new ArtifactReadLimitError(requirePlainReadLimit(options));\n }\n return true;\n } catch (error) {\n if (isStorageError(error) && error.code === StorageErrorCode.NOT_FOUND)\n return false;\n throw error;\n }\n}\n\nfunction enforcePlainReadLimit(\n plain: Buffer,\n options: ArtifactReadOptions | undefined,\n): void {\n if (options?.maxPlainBytes === undefined) return;\n const limit = requirePlainReadLimit(options);\n if (plain.length > limit) throw new ArtifactReadLimitError(limit);\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.map': 'application/json; charset=utf-8',\n '.wasm': 'application/wasm',\n};\n\n/** content-type for an artifact file path, falling back to octet-stream. */\nexport function contentTypeFor(path: string): string {\n return MIME[extname(path).toLowerCase()] ?? 'application/octet-stream';\n}\n"]}
@@ -1,44 +0,0 @@
1
- import { type EnvelopeContext, type ExpectedContext } from './context.js';
2
- import type { KeyProvider } from './key-provider.js';
3
- /** Maximum bytes added around plaintext by a valid CAE1 envelope. Useful for bounded raw reads. */
4
- export declare const ENVELOPE_MAX_OVERHEAD_BYTES: number;
5
- export declare const ENVELOPE_WRITE_VERSION = 1;
6
- export declare const ENVELOPE_SUPPORTED_VERSIONS: readonly number[];
7
- export type EnvelopeFailureReason = 'bad-magic' | 'truncated' | 'malformed-header' | 'unsupported-version' | 'unsupported-alg' | 'context-mismatch' | 'key-unavailable' | 'auth-failed';
8
- /**
9
- * Structured decrypt failure. Carries only address metadata, never key material or plaintext —
10
- * safe to log as-is (the failure-reason counter in the runbook keys off `reason`).
11
- */
12
- export declare class EnvelopeError extends Error {
13
- readonly reason: EnvelopeFailureReason;
14
- readonly artifactId: string | null;
15
- readonly path: string;
16
- constructor(reason: EnvelopeFailureReason, artifactId: string | null, path: string, detail?: string);
17
- }
18
- export interface OpenResult {
19
- plain: Buffer;
20
- ctx: EnvelopeContext;
21
- contentType?: string;
22
- /** True only when legacy plaintext passed through under the explicit migration flag. */
23
- legacy: boolean;
24
- }
25
- export interface OpenOptions {
26
- /**
27
- * SEC-04's migration-window escape hatch, default off. Consulted ONLY when the magic bytes
28
- * are absent (a pre-envelope plaintext object) — never for a valid-magic object that fails
29
- * validation, so the flag cannot downgrade a tampered envelope to a plaintext read.
30
- */
31
- allowLegacyPlaintext?: boolean;
32
- }
33
- /** Encrypt one object. Mints a fresh DEK per write; the DEK never outlives the call. */
34
- export declare function seal(plain: Buffer, ctx: EnvelopeContext, provider: KeyProvider, contentType?: string): Promise<Buffer>;
35
- /** True when the bytes carry the envelope magic (cheap pre-check; open() revalidates). */
36
- export declare function isEnvelope(bytes: Buffer): boolean;
37
- /**
38
- * Decrypt one object, fail-closed (ADR-0001 D2/D6). Order matters and is normative:
39
- * frame → header shape → CONTEXT ASSERTION → key unwrap → tag check. The context assertion runs
40
- * before any key operation so a swapped object costs no KMS call and leaks no oracle; `scope`
41
- * alone may come from the header (SCOPE_FROM_HEADER) because the wrap layer binds it.
42
- */
43
- export declare function open(bytes: Buffer, expect: ExpectedContext, provider: KeyProvider, opts?: OpenOptions): Promise<OpenResult>;
44
- //# sourceMappingURL=codec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../../src/artifacts/crypto/codec.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkBrD,mGAAmG;AACnG,eAAO,MAAM,2BAA2B,QAA+B,CAAC;AAExE,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAExD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,WAAW,GACX,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,aAAa,CAAC;AAElB;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAEpC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAHvB,YACW,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,IAAI,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,EAMhB;CACF;AAYD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,eAAe,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAoBD,wFAAwF;AACxF,wBAAsB,IAAI,CACxB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,WAAW,EACrB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CA0DjB;AAED,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKjD;AAED;;;;;GAKG;AACH,wBAAsB,IAAI,CACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,WAAW,EACrB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,UAAU,CAAC,CAwFrB"}
@@ -1,273 +0,0 @@
1
- import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
2
- import { canonicalJson, SCOPE_FROM_HEADER, } from './context.js';
3
- /**
4
- * Envelope v1 framing (ADR-0001 D2, normative):
5
- *
6
- * bytes 0-3 magic "CAE1"
7
- * bytes 4-7 u32 BE headerLen
8
- * bytes 8-… headerJSON (UTF-8, exactly headerLen bytes)
9
- * rest AES-256-GCM ciphertext ‖ 16-byte auth tag
10
- *
11
- * The GCM AAD is canonicalJson(ctx); ctx is duplicated in the header for operability (rewrap
12
- * jobs, debugging) but the BINDING is the AAD — a tampered header fails the tag check.
13
- */
14
- const MAGIC = Buffer.from('CAE1');
15
- const TAG_LEN = 16;
16
- const IV_LEN = 12;
17
- /** Headers are small (~400 bytes); anything larger is hostile or corrupt. */
18
- const MAX_HEADER_LEN = 64 * 1024;
19
- /** Maximum bytes added around plaintext by a valid CAE1 envelope. Useful for bounded raw reads. */
20
- export const ENVELOPE_MAX_OVERHEAD_BYTES = 8 + MAX_HEADER_LEN + TAG_LEN;
21
- export const ENVELOPE_WRITE_VERSION = 1;
22
- export const ENVELOPE_SUPPORTED_VERSIONS = [
23
- ENVELOPE_WRITE_VERSION,
24
- ];
25
- /**
26
- * Structured decrypt failure. Carries only address metadata, never key material or plaintext —
27
- * safe to log as-is (the failure-reason counter in the runbook keys off `reason`).
28
- */
29
- export class EnvelopeError extends Error {
30
- reason;
31
- artifactId;
32
- path;
33
- constructor(reason, artifactId, path, detail) {
34
- super(`envelope ${reason} (artifact=${artifactId ?? '-'} path=${path})${detail ? `: ${detail}` : ''}`);
35
- this.reason = reason;
36
- this.artifactId = artifactId;
37
- this.path = path;
38
- this.name = 'EnvelopeError';
39
- }
40
- }
41
- /**
42
- * Payload AAD. ADR-0001 D2 specifies canonicalJson(ctx); we additionally bind `ct` (deliberate
43
- * strengthening, called out in the PR): the content type steers how bytes are SERVED, and it is
44
- * stable across SEC-04's rewrap (which rewrites only kid/wdk — those stay unbound so rewrap
45
- * never re-encrypts payloads; their integrity comes from the context-bound wrap itself). If a
46
- * future v2 coexists with v1, bind `v` too — two live versions make downgrade flips meaningful.
47
- */
48
- function aadFor(ctx, contentType) {
49
- // NUL separator: canonicalJson is self-delimiting JSON and NUL cannot appear in a JSON string
50
- // or a content type, so distinct (ctx, ct) pairs can never collide into one AAD. FROZEN — the
51
- // golden-vector spec pins these exact bytes; changing them bricks every stored object.
52
- return Buffer.from(contentType === undefined
53
- ? canonicalJson(ctx)
54
- : `${canonicalJson(ctx)}\0${contentType}`);
55
- }
56
- /** Encrypt one object. Mints a fresh DEK per write; the DEK never outlives the call. */
57
- export async function seal(plain, ctx, provider, contentType) {
58
- validateContextShape(ctx, (detail) => {
59
- throw new Error(`seal: invalid context (${detail})`);
60
- });
61
- if (contentType !== undefined &&
62
- (typeof contentType !== 'string' || contentType.length === 0)) {
63
- throw new Error('seal: content type must be non-empty when provided');
64
- }
65
- // Reject obviously impossible user metadata before asking KMS to mint a data key. The exact
66
- // serialized header is checked again once provider-owned kid/wdk fields are available.
67
- const userMetadataLength = Buffer.byteLength(JSON.stringify({ ctx, ...(contentType ? { ct: contentType } : {}) }), 'utf8');
68
- if (userMetadataLength > MAX_HEADER_LEN) {
69
- throw new Error(`seal: envelope header exceeds ${MAX_HEADER_LEN} bytes`);
70
- }
71
- const generated = await provider.generateDataKey(ctx);
72
- const { dek, wdk, kid } = generated;
73
- try {
74
- if (!Buffer.isBuffer(dek) || dek.length !== 32) {
75
- throw new Error('seal: key provider returned an invalid 32-byte DEK');
76
- }
77
- if (!Buffer.isBuffer(wdk) || wdk.length === 0) {
78
- throw new Error('seal: key provider returned an empty wrapped data key');
79
- }
80
- if (typeof kid !== 'string' || kid.length === 0) {
81
- throw new Error('seal: key provider returned an empty key id');
82
- }
83
- const iv = randomBytes(IV_LEN);
84
- const header = {
85
- v: ENVELOPE_WRITE_VERSION,
86
- alg: 'A256GCM',
87
- kid,
88
- wdk: wdk.toString('base64'),
89
- iv: iv.toString('base64'),
90
- ctx,
91
- ...(contentType ? { ct: contentType } : {}),
92
- };
93
- const headerJson = Buffer.from(JSON.stringify(header), 'utf8');
94
- if (headerJson.length > MAX_HEADER_LEN) {
95
- throw new Error(`seal: envelope header exceeds ${MAX_HEADER_LEN} bytes`);
96
- }
97
- const cipher = createCipheriv('aes-256-gcm', dek, iv);
98
- cipher.setAAD(aadFor(ctx, contentType));
99
- const body = Buffer.concat([
100
- cipher.update(plain),
101
- cipher.final(),
102
- cipher.getAuthTag(),
103
- ]);
104
- const lenBuf = Buffer.alloc(4);
105
- lenBuf.writeUInt32BE(headerJson.length, 0);
106
- return Buffer.concat([MAGIC, lenBuf, headerJson, body]);
107
- }
108
- finally {
109
- if (Buffer.isBuffer(dek))
110
- dek.fill(0);
111
- }
112
- }
113
- /** True when the bytes carry the envelope magic (cheap pre-check; open() revalidates). */
114
- export function isEnvelope(bytes) {
115
- return (bytes.length >= MAGIC.length &&
116
- bytes.subarray(0, MAGIC.length).equals(MAGIC));
117
- }
118
- /**
119
- * Decrypt one object, fail-closed (ADR-0001 D2/D6). Order matters and is normative:
120
- * frame → header shape → CONTEXT ASSERTION → key unwrap → tag check. The context assertion runs
121
- * before any key operation so a swapped object costs no KMS call and leaks no oracle; `scope`
122
- * alone may come from the header (SCOPE_FROM_HEADER) because the wrap layer binds it.
123
- */
124
- export async function open(bytes, expect, provider, opts = {}) {
125
- const fail = (reason, detail) => {
126
- throw new EnvelopeError(reason, expect.artifactId, expect.path, detail);
127
- };
128
- if (!isEnvelope(bytes)) {
129
- if (opts.allowLegacyPlaintext)
130
- return { plain: bytes, ctx: legacyCtx(expect), legacy: true };
131
- fail('bad-magic');
132
- }
133
- if (bytes.length < 8)
134
- fail('truncated', 'no header length');
135
- const headerLen = bytes.readUInt32BE(4);
136
- if (headerLen > MAX_HEADER_LEN)
137
- fail('malformed-header', 'header length out of range');
138
- if (8 + headerLen + TAG_LEN > bytes.length)
139
- fail('truncated', 'body shorter than header + tag');
140
- let header;
141
- try {
142
- header = JSON.parse(bytes.subarray(8, 8 + headerLen).toString('utf8'));
143
- }
144
- catch {
145
- return fail('malformed-header', 'header is not valid JSON');
146
- }
147
- validateHeaderShape(header, fail);
148
- if (header.v !== ENVELOPE_WRITE_VERSION) {
149
- // Reads must support every version in ENVELOPE_SUPPORTED_VERSIONS; with only v1 defined,
150
- // anything else is unsupported. A future v2 branches here — writes stay on the newest
151
- // version and never silently downgrade.
152
- fail('unsupported-version', `v=${String(header.v)}`);
153
- }
154
- if (header.alg !== 'A256GCM')
155
- fail('unsupported-alg', header.alg);
156
- const ctx = header.ctx;
157
- // The anti-swap assertion: every field the reader independently knows must match the header
158
- // BEFORE any key operation. Only `scope` may be delegated to the header (sandbox), where the
159
- // wrap binding enforces it instead.
160
- if (ctx.artifactId !== expect.artifactId)
161
- fail('context-mismatch', 'artifactId');
162
- if (ctx.path !== expect.path)
163
- fail('context-mismatch', 'path');
164
- if (ctx.version !== expect.version)
165
- fail('context-mismatch', 'version');
166
- if (expect.scope !== SCOPE_FROM_HEADER && ctx.scope !== expect.scope) {
167
- fail('context-mismatch', 'scope');
168
- }
169
- const iv = Buffer.from(header.iv, 'base64');
170
- if (iv.length !== IV_LEN)
171
- fail('malformed-header', 'iv must be 12 bytes');
172
- const wdk = Buffer.from(header.wdk, 'base64');
173
- if (wdk.length === 0)
174
- fail('malformed-header', 'empty wrapped data key');
175
- let unwrapped;
176
- try {
177
- unwrapped = await provider.unwrapDataKey(wdk, header.kid, ctx);
178
- }
179
- catch {
180
- // Wrong wrap context, foreign kid, or an unavailable key service — indistinguishable by
181
- // design; serving plaintext because the key layer failed is the vulnerability (ADR D7).
182
- return fail('key-unavailable');
183
- }
184
- if (!Buffer.isBuffer(unwrapped) || unwrapped.length !== 32) {
185
- if (Buffer.isBuffer(unwrapped))
186
- unwrapped.fill(0);
187
- return fail('key-unavailable');
188
- }
189
- const dek = unwrapped;
190
- try {
191
- const body = bytes.subarray(8 + headerLen);
192
- const tag = body.subarray(body.length - TAG_LEN);
193
- const decipher = createDecipheriv('aes-256-gcm', dek, iv);
194
- decipher.setAAD(aadFor(ctx, header.ct));
195
- decipher.setAuthTag(tag);
196
- let plain;
197
- try {
198
- plain = Buffer.concat([
199
- decipher.update(body.subarray(0, body.length - TAG_LEN)),
200
- decipher.final(),
201
- ]);
202
- }
203
- catch {
204
- return fail('auth-failed');
205
- }
206
- return {
207
- plain,
208
- ctx,
209
- ...(header.ct === undefined ? {} : { contentType: header.ct }),
210
- legacy: false,
211
- };
212
- }
213
- finally {
214
- dek.fill(0);
215
- }
216
- }
217
- /** Header-shape validation: exact key sets, exact types. Any surplus key is hostile (ADR D2). */
218
- function validateHeaderShape(header, fail) {
219
- if (typeof header !== 'object' || header === null)
220
- fail('malformed-header', 'not an object');
221
- const allowed = new Set(['v', 'alg', 'kid', 'wdk', 'iv', 'ctx', 'ct']);
222
- for (const key of Object.keys(header)) {
223
- if (!allowed.has(key))
224
- fail('malformed-header', `unexpected header key "${key}"`);
225
- }
226
- if (typeof header.v !== 'number')
227
- fail('malformed-header', 'v');
228
- if (typeof header.alg !== 'string')
229
- fail('malformed-header', 'alg');
230
- if (typeof header.kid !== 'string' || header.kid === '')
231
- fail('malformed-header', 'kid');
232
- if (typeof header.wdk !== 'string' || header.wdk === '')
233
- fail('malformed-header', 'wdk');
234
- if (typeof header.iv !== 'string')
235
- fail('malformed-header', 'iv');
236
- if (header.ct !== undefined &&
237
- (typeof header.ct !== 'string' || header.ct.length === 0))
238
- fail('malformed-header', 'ct');
239
- validateContextShape(header.ctx, (detail) => fail('malformed-header', detail));
240
- }
241
- function validateContextShape(ctx, fail) {
242
- if (typeof ctx !== 'object' || ctx === null)
243
- fail('ctx');
244
- const ctxKeys = Object.keys(ctx);
245
- const expected = ['scope', 'artifactId', 'version', 'path'];
246
- if (ctxKeys.length !== expected.length ||
247
- expected.some((k) => !ctxKeys.includes(k))) {
248
- // Exactly these four keys: a surplus field could otherwise ride the AAD unexamined.
249
- fail('ctx key set');
250
- }
251
- const c = ctx;
252
- if (typeof c.scope !== 'string' ||
253
- c.scope === '' ||
254
- c.scope === SCOPE_FROM_HEADER) {
255
- fail('ctx.scope');
256
- }
257
- if (c.artifactId !== null && typeof c.artifactId !== 'string')
258
- fail('ctx.artifactId');
259
- if (c.version !== null && typeof c.version !== 'string')
260
- fail('ctx.version');
261
- if (typeof c.path !== 'string')
262
- fail('ctx.path');
263
- }
264
- /** Descriptive ctx for a legacy plaintext pass-through (nothing was verified). */
265
- function legacyCtx(expect) {
266
- return {
267
- scope: expect.scope === SCOPE_FROM_HEADER ? 'legacy:unverified' : expect.scope,
268
- artifactId: expect.artifactId,
269
- version: expect.version,
270
- path: expect.path,
271
- };
272
- }
273
- //# sourceMappingURL=codec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"codec.js","sourceRoot":"","sources":["../../../src/artifacts/crypto/codec.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\n\nimport {\n canonicalJson,\n SCOPE_FROM_HEADER,\n type EnvelopeContext,\n type ExpectedContext,\n} from './context.js';\nimport type { KeyProvider } from './key-provider.js';\n\n/**\n * Envelope v1 framing (ADR-0001 D2, normative):\n *\n * bytes 0-3 magic \"CAE1\"\n * bytes 4-7 u32 BE headerLen\n * bytes 8-… headerJSON (UTF-8, exactly headerLen bytes)\n * rest AES-256-GCM ciphertext ‖ 16-byte auth tag\n *\n * The GCM AAD is canonicalJson(ctx); ctx is duplicated in the header for operability (rewrap\n * jobs, debugging) but the BINDING is the AAD — a tampered header fails the tag check.\n */\nconst MAGIC = Buffer.from('CAE1');\nconst TAG_LEN = 16;\nconst IV_LEN = 12;\n/** Headers are small (~400 bytes); anything larger is hostile or corrupt. */\nconst MAX_HEADER_LEN = 64 * 1024;\n/** Maximum bytes added around plaintext by a valid CAE1 envelope. Useful for bounded raw reads. */\nexport const ENVELOPE_MAX_OVERHEAD_BYTES = 8 + MAX_HEADER_LEN + TAG_LEN;\n\nexport const ENVELOPE_WRITE_VERSION = 1;\nexport const ENVELOPE_SUPPORTED_VERSIONS: readonly number[] = [\n ENVELOPE_WRITE_VERSION,\n];\n\nexport type EnvelopeFailureReason =\n | 'bad-magic'\n | 'truncated'\n | 'malformed-header'\n | 'unsupported-version'\n | 'unsupported-alg'\n | 'context-mismatch'\n | 'key-unavailable'\n | 'auth-failed';\n\n/**\n * Structured decrypt failure. Carries only address metadata, never key material or plaintext —\n * safe to log as-is (the failure-reason counter in the runbook keys off `reason`).\n */\nexport class EnvelopeError extends Error {\n constructor(\n readonly reason: EnvelopeFailureReason,\n readonly artifactId: string | null,\n readonly path: string,\n detail?: string,\n ) {\n super(\n `envelope ${reason} (artifact=${artifactId ?? '-'} path=${path})${detail ? `: ${detail}` : ''}`,\n );\n this.name = 'EnvelopeError';\n }\n}\n\ninterface EnvelopeHeader {\n v: number;\n alg: string;\n kid: string;\n wdk: string;\n iv: string;\n ctx: EnvelopeContext;\n ct?: string;\n}\n\nexport interface OpenResult {\n plain: Buffer;\n ctx: EnvelopeContext;\n contentType?: string;\n /** True only when legacy plaintext passed through under the explicit migration flag. */\n legacy: boolean;\n}\n\nexport interface OpenOptions {\n /**\n * SEC-04's migration-window escape hatch, default off. Consulted ONLY when the magic bytes\n * are absent (a pre-envelope plaintext object) — never for a valid-magic object that fails\n * validation, so the flag cannot downgrade a tampered envelope to a plaintext read.\n */\n allowLegacyPlaintext?: boolean;\n}\n\n/**\n * Payload AAD. ADR-0001 D2 specifies canonicalJson(ctx); we additionally bind `ct` (deliberate\n * strengthening, called out in the PR): the content type steers how bytes are SERVED, and it is\n * stable across SEC-04's rewrap (which rewrites only kid/wdk — those stay unbound so rewrap\n * never re-encrypts payloads; their integrity comes from the context-bound wrap itself). If a\n * future v2 coexists with v1, bind `v` too — two live versions make downgrade flips meaningful.\n */\nfunction aadFor(ctx: EnvelopeContext, contentType?: string): Buffer {\n // NUL separator: canonicalJson is self-delimiting JSON and NUL cannot appear in a JSON string\n // or a content type, so distinct (ctx, ct) pairs can never collide into one AAD. FROZEN — the\n // golden-vector spec pins these exact bytes; changing them bricks every stored object.\n return Buffer.from(\n contentType === undefined\n ? canonicalJson(ctx)\n : `${canonicalJson(ctx)}\\0${contentType}`,\n );\n}\n\n/** Encrypt one object. Mints a fresh DEK per write; the DEK never outlives the call. */\nexport async function seal(\n plain: Buffer,\n ctx: EnvelopeContext,\n provider: KeyProvider,\n contentType?: string,\n): Promise<Buffer> {\n validateContextShape(ctx, (detail) => {\n throw new Error(`seal: invalid context (${detail})`);\n });\n if (\n contentType !== undefined &&\n (typeof contentType !== 'string' || contentType.length === 0)\n ) {\n throw new Error('seal: content type must be non-empty when provided');\n }\n // Reject obviously impossible user metadata before asking KMS to mint a data key. The exact\n // serialized header is checked again once provider-owned kid/wdk fields are available.\n const userMetadataLength = Buffer.byteLength(\n JSON.stringify({ ctx, ...(contentType ? { ct: contentType } : {}) }),\n 'utf8',\n );\n if (userMetadataLength > MAX_HEADER_LEN) {\n throw new Error(`seal: envelope header exceeds ${MAX_HEADER_LEN} bytes`);\n }\n const generated = await provider.generateDataKey(ctx);\n const { dek, wdk, kid } = generated;\n try {\n if (!Buffer.isBuffer(dek) || dek.length !== 32) {\n throw new Error('seal: key provider returned an invalid 32-byte DEK');\n }\n if (!Buffer.isBuffer(wdk) || wdk.length === 0) {\n throw new Error('seal: key provider returned an empty wrapped data key');\n }\n if (typeof kid !== 'string' || kid.length === 0) {\n throw new Error('seal: key provider returned an empty key id');\n }\n const iv = randomBytes(IV_LEN);\n const header: EnvelopeHeader = {\n v: ENVELOPE_WRITE_VERSION,\n alg: 'A256GCM',\n kid,\n wdk: wdk.toString('base64'),\n iv: iv.toString('base64'),\n ctx,\n ...(contentType ? { ct: contentType } : {}),\n };\n const headerJson = Buffer.from(JSON.stringify(header), 'utf8');\n if (headerJson.length > MAX_HEADER_LEN) {\n throw new Error(`seal: envelope header exceeds ${MAX_HEADER_LEN} bytes`);\n }\n const cipher = createCipheriv('aes-256-gcm', dek, iv);\n cipher.setAAD(aadFor(ctx, contentType));\n const body = Buffer.concat([\n cipher.update(plain),\n cipher.final(),\n cipher.getAuthTag(),\n ]);\n const lenBuf = Buffer.alloc(4);\n lenBuf.writeUInt32BE(headerJson.length, 0);\n return Buffer.concat([MAGIC, lenBuf, headerJson, body]);\n } finally {\n if (Buffer.isBuffer(dek)) dek.fill(0);\n }\n}\n\n/** True when the bytes carry the envelope magic (cheap pre-check; open() revalidates). */\nexport function isEnvelope(bytes: Buffer): boolean {\n return (\n bytes.length >= MAGIC.length &&\n bytes.subarray(0, MAGIC.length).equals(MAGIC)\n );\n}\n\n/**\n * Decrypt one object, fail-closed (ADR-0001 D2/D6). Order matters and is normative:\n * frame → header shape → CONTEXT ASSERTION → key unwrap → tag check. The context assertion runs\n * before any key operation so a swapped object costs no KMS call and leaks no oracle; `scope`\n * alone may come from the header (SCOPE_FROM_HEADER) because the wrap layer binds it.\n */\nexport async function open(\n bytes: Buffer,\n expect: ExpectedContext,\n provider: KeyProvider,\n opts: OpenOptions = {},\n): Promise<OpenResult> {\n const fail = (reason: EnvelopeFailureReason, detail?: string): never => {\n throw new EnvelopeError(reason, expect.artifactId, expect.path, detail);\n };\n\n if (!isEnvelope(bytes)) {\n if (opts.allowLegacyPlaintext)\n return { plain: bytes, ctx: legacyCtx(expect), legacy: true };\n fail('bad-magic');\n }\n if (bytes.length < 8) fail('truncated', 'no header length');\n const headerLen = bytes.readUInt32BE(4);\n if (headerLen > MAX_HEADER_LEN)\n fail('malformed-header', 'header length out of range');\n if (8 + headerLen + TAG_LEN > bytes.length)\n fail('truncated', 'body shorter than header + tag');\n\n let header: EnvelopeHeader;\n try {\n header = JSON.parse(\n bytes.subarray(8, 8 + headerLen).toString('utf8'),\n ) as EnvelopeHeader;\n } catch {\n return fail('malformed-header', 'header is not valid JSON');\n }\n validateHeaderShape(header, fail);\n if (header.v !== ENVELOPE_WRITE_VERSION) {\n // Reads must support every version in ENVELOPE_SUPPORTED_VERSIONS; with only v1 defined,\n // anything else is unsupported. A future v2 branches here — writes stay on the newest\n // version and never silently downgrade.\n fail('unsupported-version', `v=${String(header.v)}`);\n }\n if (header.alg !== 'A256GCM') fail('unsupported-alg', header.alg);\n\n const ctx = header.ctx;\n // The anti-swap assertion: every field the reader independently knows must match the header\n // BEFORE any key operation. Only `scope` may be delegated to the header (sandbox), where the\n // wrap binding enforces it instead.\n if (ctx.artifactId !== expect.artifactId)\n fail('context-mismatch', 'artifactId');\n if (ctx.path !== expect.path) fail('context-mismatch', 'path');\n if (ctx.version !== expect.version) fail('context-mismatch', 'version');\n if (expect.scope !== SCOPE_FROM_HEADER && ctx.scope !== expect.scope) {\n fail('context-mismatch', 'scope');\n }\n\n const iv = Buffer.from(header.iv, 'base64');\n if (iv.length !== IV_LEN) fail('malformed-header', 'iv must be 12 bytes');\n const wdk = Buffer.from(header.wdk, 'base64');\n if (wdk.length === 0) fail('malformed-header', 'empty wrapped data key');\n\n let unwrapped: unknown;\n try {\n unwrapped = await provider.unwrapDataKey(wdk, header.kid, ctx);\n } catch {\n // Wrong wrap context, foreign kid, or an unavailable key service — indistinguishable by\n // design; serving plaintext because the key layer failed is the vulnerability (ADR D7).\n return fail('key-unavailable');\n }\n if (!Buffer.isBuffer(unwrapped) || unwrapped.length !== 32) {\n if (Buffer.isBuffer(unwrapped)) unwrapped.fill(0);\n return fail('key-unavailable');\n }\n const dek = unwrapped;\n try {\n const body = bytes.subarray(8 + headerLen);\n const tag = body.subarray(body.length - TAG_LEN);\n const decipher = createDecipheriv('aes-256-gcm', dek, iv);\n decipher.setAAD(aadFor(ctx, header.ct));\n decipher.setAuthTag(tag);\n let plain: Buffer;\n try {\n plain = Buffer.concat([\n decipher.update(body.subarray(0, body.length - TAG_LEN)),\n decipher.final(),\n ]);\n } catch {\n return fail('auth-failed');\n }\n return {\n plain,\n ctx,\n ...(header.ct === undefined ? {} : { contentType: header.ct }),\n legacy: false,\n };\n } finally {\n dek.fill(0);\n }\n}\n\n/** Header-shape validation: exact key sets, exact types. Any surplus key is hostile (ADR D2). */\nfunction validateHeaderShape(\n header: EnvelopeHeader,\n fail: (reason: EnvelopeFailureReason, detail?: string) => never,\n): void {\n if (typeof header !== 'object' || header === null)\n fail('malformed-header', 'not an object');\n const allowed = new Set(['v', 'alg', 'kid', 'wdk', 'iv', 'ctx', 'ct']);\n for (const key of Object.keys(header)) {\n if (!allowed.has(key))\n fail('malformed-header', `unexpected header key \"${key}\"`);\n }\n if (typeof header.v !== 'number') fail('malformed-header', 'v');\n if (typeof header.alg !== 'string') fail('malformed-header', 'alg');\n if (typeof header.kid !== 'string' || header.kid === '')\n fail('malformed-header', 'kid');\n if (typeof header.wdk !== 'string' || header.wdk === '')\n fail('malformed-header', 'wdk');\n if (typeof header.iv !== 'string') fail('malformed-header', 'iv');\n if (\n header.ct !== undefined &&\n (typeof header.ct !== 'string' || header.ct.length === 0)\n )\n fail('malformed-header', 'ct');\n validateContextShape(header.ctx, (detail) =>\n fail('malformed-header', detail),\n );\n}\n\nfunction validateContextShape(\n ctx: unknown,\n fail: (detail: string) => never,\n): asserts ctx is EnvelopeContext {\n if (typeof ctx !== 'object' || ctx === null) fail('ctx');\n const ctxKeys = Object.keys(ctx);\n const expected = ['scope', 'artifactId', 'version', 'path'];\n if (\n ctxKeys.length !== expected.length ||\n expected.some((k) => !ctxKeys.includes(k))\n ) {\n // Exactly these four keys: a surplus field could otherwise ride the AAD unexamined.\n fail('ctx key set');\n }\n const c = ctx as Record<string, unknown>;\n if (\n typeof c.scope !== 'string' ||\n c.scope === '' ||\n c.scope === SCOPE_FROM_HEADER\n ) {\n fail('ctx.scope');\n }\n if (c.artifactId !== null && typeof c.artifactId !== 'string')\n fail('ctx.artifactId');\n if (c.version !== null && typeof c.version !== 'string') fail('ctx.version');\n if (typeof c.path !== 'string') fail('ctx.path');\n}\n\n/** Descriptive ctx for a legacy plaintext pass-through (nothing was verified). */\nfunction legacyCtx(expect: ExpectedContext): EnvelopeContext {\n return {\n scope:\n expect.scope === SCOPE_FROM_HEADER ? 'legacy:unverified' : expect.scope,\n artifactId: expect.artifactId,\n version: expect.version,\n path: expect.path,\n };\n}\n"]}
@@ -1,48 +0,0 @@
1
- /**
2
- * The encryption context that binds every stored object to its address (ADR-0001 D2). It is the
3
- * GCM AAD of the payload AND the wrap context of the data key, so a ciphertext moved to another
4
- * scope/artifact/path fails closed at decrypt — object swapping and cross-artifact replay are
5
- * structurally impossible, not policy-checked.
6
- */
7
- export interface EnvelopeContext {
8
- /** Owning tenant: `org:<organizationId>` | `user:<ownerUserId>` | `upload:<uploadId>`. */
9
- scope: string;
10
- /** The artifact the object belongs to; `null` for non-artifact ObjectStore objects. */
11
- artifactId: string | null;
12
- /** Immutable version id; `null` until VER-02 introduces versions. */
13
- version: string | null;
14
- /** Storage-relative path AS READ (e.g. `index.html` on the fs directory rewrite — ADR D6.1). */
15
- path: string;
16
- }
17
- /**
18
- * Read-side sentinel for callers that cannot independently know the owning scope — the sandbox,
19
- * which has only the artifactId from the URL and no DB. The reader still asserts every field it
20
- * DOES know (artifactId, path, version) against the cleartext header before any key operation;
21
- * `scope` is then taken from the header and enforced cryptographically by the wrap layer: the
22
- * KMS EncryptionContext / local wrap-AAD includes it, so a forged header scope fails at
23
- * `unwrapDataKey`, not silently.
24
- */
25
- export declare const SCOPE_FROM_HEADER = "@from-header";
26
- /** What a reader knows independently about the object it expects (the anti-swap assertion). */
27
- export interface ExpectedContext {
28
- scope: string | typeof SCOPE_FROM_HEADER;
29
- artifactId: string | null;
30
- version: string | null;
31
- path: string;
32
- }
33
- /** Scope for an artifact: the owning org when there is one, else the owning user. */
34
- export declare function artifactScope(owner: {
35
- organizationId?: string | null;
36
- ownerUserId: string;
37
- }): string;
38
- /** Scope for organization-owned non-artifact objects (logos). */
39
- export declare function orgScope(organizationId: string): string;
40
- /** Scope for staged upload objects (`_staging/*`) — short-lived but still customer plaintext. */
41
- export declare function uploadScope(uploadId: string): string;
42
- /**
43
- * Canonical serialization used as the payload AAD and the local wrap AAD: fixed sorted key
44
- * order, no whitespace. Rebuilt from parsed values so header key ORDER never matters, while the
45
- * key SET is pinned by the codec's strict header validation.
46
- */
47
- export declare function canonicalJson(ctx: EnvelopeContext): string;
48
- //# sourceMappingURL=context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/artifacts/crypto/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,+FAA+F;AAC/F,MAAM,WAAW,eAAe;IAI9B,KAAK,EAAE,MAAM,GAAG,OAAO,iBAAiB,CAAC;IACzC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAIT;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAO1D"}
@@ -1,37 +0,0 @@
1
- /**
2
- * Read-side sentinel for callers that cannot independently know the owning scope — the sandbox,
3
- * which has only the artifactId from the URL and no DB. The reader still asserts every field it
4
- * DOES know (artifactId, path, version) against the cleartext header before any key operation;
5
- * `scope` is then taken from the header and enforced cryptographically by the wrap layer: the
6
- * KMS EncryptionContext / local wrap-AAD includes it, so a forged header scope fails at
7
- * `unwrapDataKey`, not silently.
8
- */
9
- export const SCOPE_FROM_HEADER = '@from-header';
10
- /** Scope for an artifact: the owning org when there is one, else the owning user. */
11
- export function artifactScope(owner) {
12
- return owner.organizationId
13
- ? `org:${owner.organizationId}`
14
- : `user:${owner.ownerUserId}`;
15
- }
16
- /** Scope for organization-owned non-artifact objects (logos). */
17
- export function orgScope(organizationId) {
18
- return `org:${organizationId}`;
19
- }
20
- /** Scope for staged upload objects (`_staging/*`) — short-lived but still customer plaintext. */
21
- export function uploadScope(uploadId) {
22
- return `upload:${uploadId}`;
23
- }
24
- /**
25
- * Canonical serialization used as the payload AAD and the local wrap AAD: fixed sorted key
26
- * order, no whitespace. Rebuilt from parsed values so header key ORDER never matters, while the
27
- * key SET is pinned by the codec's strict header validation.
28
- */
29
- export function canonicalJson(ctx) {
30
- return JSON.stringify({
31
- artifactId: ctx.artifactId,
32
- path: ctx.path,
33
- scope: ctx.scope,
34
- version: ctx.version,
35
- });
36
- }
37
- //# sourceMappingURL=context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/artifacts/crypto/context.ts"],"names":[],"mappings":"AAiBA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAahD,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,KAG7B;IACC,OAAO,KAAK,CAAC,cAAc;QACzB,CAAC,CAAC,OAAO,KAAK,CAAC,cAAc,EAAE;QAC/B,CAAC,CAAC,QAAQ,KAAK,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,QAAQ,CAAC,cAAsB;IAC7C,OAAO,OAAO,cAAc,EAAE,CAAC;AACjC,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,OAAO,UAAU,QAAQ,EAAE,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAoB;IAChD,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The encryption context that binds every stored object to its address (ADR-0001 D2). It is the\n * GCM AAD of the payload AND the wrap context of the data key, so a ciphertext moved to another\n * scope/artifact/path fails closed at decrypt — object swapping and cross-artifact replay are\n * structurally impossible, not policy-checked.\n */\nexport interface EnvelopeContext {\n /** Owning tenant: `org:<organizationId>` | `user:<ownerUserId>` | `upload:<uploadId>`. */\n scope: string;\n /** The artifact the object belongs to; `null` for non-artifact ObjectStore objects. */\n artifactId: string | null;\n /** Immutable version id; `null` until VER-02 introduces versions. */\n version: string | null;\n /** Storage-relative path AS READ (e.g. `index.html` on the fs directory rewrite — ADR D6.1). */\n path: string;\n}\n\n/**\n * Read-side sentinel for callers that cannot independently know the owning scope — the sandbox,\n * which has only the artifactId from the URL and no DB. The reader still asserts every field it\n * DOES know (artifactId, path, version) against the cleartext header before any key operation;\n * `scope` is then taken from the header and enforced cryptographically by the wrap layer: the\n * KMS EncryptionContext / local wrap-AAD includes it, so a forged header scope fails at\n * `unwrapDataKey`, not silently.\n */\nexport const SCOPE_FROM_HEADER = '@from-header';\n\n/** What a reader knows independently about the object it expects (the anti-swap assertion). */\nexport interface ExpectedContext {\n // `string` absorbs the literal for the checker, but the sentinel is domain vocabulary\n // here — collapsing the union would erase what @from-header means to a reader.\n // eslint-disable-next-line typescript/no-redundant-type-constituents\n scope: string | typeof SCOPE_FROM_HEADER;\n artifactId: string | null;\n version: string | null;\n path: string;\n}\n\n/** Scope for an artifact: the owning org when there is one, else the owning user. */\nexport function artifactScope(owner: {\n organizationId?: string | null;\n ownerUserId: string;\n}): string {\n return owner.organizationId\n ? `org:${owner.organizationId}`\n : `user:${owner.ownerUserId}`;\n}\n\n/** Scope for organization-owned non-artifact objects (logos). */\nexport function orgScope(organizationId: string): string {\n return `org:${organizationId}`;\n}\n\n/** Scope for staged upload objects (`_staging/*`) — short-lived but still customer plaintext. */\nexport function uploadScope(uploadId: string): string {\n return `upload:${uploadId}`;\n}\n\n/**\n * Canonical serialization used as the payload AAD and the local wrap AAD: fixed sorted key\n * order, no whitespace. Rebuilt from parsed values so header key ORDER never matters, while the\n * key SET is pinned by the codec's strict header validation.\n */\nexport function canonicalJson(ctx: EnvelopeContext): string {\n return JSON.stringify({\n artifactId: ctx.artifactId,\n path: ctx.path,\n scope: ctx.scope,\n version: ctx.version,\n });\n}\n"]}
@@ -1,38 +0,0 @@
1
- import { type EnvelopeContext } from './context.js';
2
- import type { DataKey, KeyProvider } from './key-provider.js';
3
- export interface DekCacheOptions {
4
- /** Max cached unwrapped DEKs (default 1024). */
5
- max?: number;
6
- /** Entry lifetime; ADR-0001 D6.2 caps this at 5 minutes (the default). */
7
- ttlMs?: number;
8
- }
9
- /** Hard policy bounds for cached plaintext data-encryption keys. */
10
- export declare const MAX_DEK_CACHE_ENTRIES = 65536;
11
- export declare const MAX_DEK_CACHE_TTL_MS: number;
12
- /**
13
- * Bounded in-memory LRU of unwrapped DEKs so the sandbox's per-request reads don't turn into a
14
- * KMS Decrypt per object (ADR-0001 D6.2). Never persisted; cleared on shutdown. Deliberate
15
- * T4-shaped trade: a compromised runtime could read this cache, but it can already call unwrap.
16
- *
17
- * Deviation from the ADR's literal wording, on purpose: the key is a length-prefixed hash of
18
- * (kid, wdk, ctx), not sha256(wdk) — keying on wdk alone would let a cache hit bypass the very
19
- * context check (KMS EncryptionContext / local wrap-AAD) that makes header-sourced scope
20
- * trustworthy. Length prefixes are required because kid may contain NUL and wdk is arbitrary
21
- * binary; delimiter concatenation would let distinct tuples collide before hashing.
22
- */
23
- export declare class CachingKeyProvider implements KeyProvider {
24
- private readonly inner;
25
- private readonly cache;
26
- private readonly inFlight;
27
- private readonly max;
28
- private readonly ttlMs;
29
- private generation;
30
- private nextToken;
31
- constructor(inner: KeyProvider, opts?: DekCacheOptions);
32
- generateDataKey(ctx: EnvelopeContext): Promise<DataKey>;
33
- unwrapDataKey(wdk: Buffer, kid: string, ctx: EnvelopeContext): Promise<Buffer>;
34
- private loadAndCache;
35
- clear(): void;
36
- private evict;
37
- }
38
- //# sourceMappingURL=dek-cache.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dek-cache.d.ts","sourceRoot":"","sources":["../../../src/artifacts/crypto/dek-cache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,oEAAoE;AACpE,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C,eAAO,MAAM,oBAAoB,QAAa,CAAC;AAE/C;;;;;;;;;;GAUG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IAoBlD,OAAO,CAAC,QAAQ,CAAC,KAAK;IAnBxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAQlB;IACJ,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAGrB;IACJ,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,SAAS,CAAK;IAEtB,YACmB,KAAK,EAAE,WAAW,EACnC,IAAI,GAAE,eAAoB,EAsB3B;IAED,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtD;IAEK,aAAa,CACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,eAAe,GACnB,OAAO,CAAC,MAAM,CAAC,CAgDjB;YAEa,YAAY;IAmC1B,KAAK,IAAI,IAAI,CAKZ;IAED,OAAO,CAAC,KAAK;CAQd"}