@agimon-ai/po-web-security 0.0.0-alpha.45 → 0.0.0-alpha.47

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["fs","path","randomBytes","generateKeyPairSync","createHash","createPrivateKey","isBundleManifest","createSign","canonicalManifest","attestationBytes","createPublicKey","require_runtime","require_bundleManifest","node_crypto","node_fs","node_path","SIGNING_FILE","SIGN_ALGORITHM","SKIPPED","PROFILES","MIME_TYPES","ensureStateDirectory","stateDir","stat","withStateLock","lockPath","descriptor","attempt","error","operation","atomicWriteState","destination","temporary","state","parseState","value","keyPath","candidate","revision","derived","publicKeyOf","loadOrCreateState","parsed","pair","reserveRevision","expectedPublicKey","next","contentTypeFor","filePath","walk","entries","current","entry","full","root","into","bytes","segment","createBundleSigner","key","privateKey","cache","cacheKeyOf","profile","assetsDir","signFresh","rootStat","assets","left","right","manifest","sign","cacheKey","signed","tag","fields","privateKeyPem"],"sources":["../../../src/services/bundleSigner/index.ts"],"sourcesContent":["import {\n createHash,\n createPrivateKey,\n createPublicKey,\n createSign,\n generateKeyPairSync,\n randomBytes,\n} from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n BUNDLE_MANIFEST_VERSION,\n type BundleAsset,\n type BundleManifest,\n type BundleProfile,\n type SignedBundleManifest,\n attestationBytes,\n canonicalManifest,\n isBundleManifest,\n} from '../../types/bundleManifest';\n\nconst SIGNING_FILE = 'signing.json';\nconst LOCK_FILE = 'signing.lock';\nconst FILE_MODE = 0o600;\nconst DIRECTORY_MODE = 0o700;\nconst CURVE = 'prime256v1';\nconst SIGN_ALGORITHM = 'sha256';\nconst SKIPPED = new Set(['.map']);\nconst PROFILES: readonly BundleProfile[] = ['web', 'native', 'app'];\nconst MIME_TYPES: Readonly<Record<string, string>> = {\n '.css': 'text/css',\n '.gif': 'image/gif',\n '.html': 'text/html',\n '.ico': 'image/x-icon',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.mjs': 'text/javascript',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain',\n '.wasm': 'application/wasm',\n '.webp': 'image/webp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n};\n\nexport interface BundleSigner {\n /** Base64url SPKI for an independently trusted verifier to pin. */\n publicKey(): string;\n /**\n * Hashes and signs once per asset directory and profile. Undefined when the bundle is empty. Throws\n * when the assets do not match the profile.\n */\n sign(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates and immediately rebuilds one cached manifest. */\n refresh(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates one cached directory under every profile, or every cached directory when omitted. */\n invalidate(assetsDir?: string): void;\n /**\n * Base64url IEEE P1363 signature over `attestationBytes(tag, fields)` with the bundle key. Throws on a\n * malformed tag.\n */\n attest(tag: string, fields: readonly string[]): string;\n}\n\ninterface StoredState {\n privateKey: string;\n publicKey: string;\n revision: number;\n}\n\nfunction ensureStateDirectory(stateDir: string): void {\n fs.mkdirSync(stateDir, { recursive: true, mode: DIRECTORY_MODE });\n const stat = fs.lstatSync(stateDir);\n if (!stat.isDirectory() || stat.isSymbolicLink())\n throw new Error(`bundle signing state is not a directory: ${stateDir}`);\n fs.chmodSync(stateDir, DIRECTORY_MODE);\n}\n\nfunction withStateLock<T>(stateDir: string, operation: () => T): T {\n ensureStateDirectory(stateDir);\n const lockPath = path.join(stateDir, LOCK_FILE);\n let descriptor: number | undefined;\n for (let attempt = 0; attempt < 500; attempt += 1) {\n try {\n descriptor = fs.openSync(lockPath, 'wx', FILE_MODE);\n break;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n if (descriptor === undefined) throw new Error(`timed out waiting for bundle signing state lock: ${lockPath}`);\n try {\n return operation();\n } finally {\n fs.closeSync(descriptor);\n fs.rmSync(lockPath, { force: true });\n }\n}\n\nfunction atomicWriteState(stateDir: string, state: StoredState): void {\n const destination = path.join(stateDir, SIGNING_FILE);\n const temporary = path.join(stateDir, `.${SIGNING_FILE}.${process.pid}.${randomBytes(8).toString('hex')}`);\n const descriptor = fs.openSync(temporary, 'wx', FILE_MODE);\n try {\n fs.writeFileSync(descriptor, `${JSON.stringify(state, undefined, 2)}\\n`);\n fs.fsyncSync(descriptor);\n } finally {\n fs.closeSync(descriptor);\n }\n try {\n fs.renameSync(temporary, destination);\n fs.chmodSync(destination, FILE_MODE);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n throw error;\n }\n}\n\nfunction parseState(value: unknown, keyPath: string): StoredState {\n if (typeof value !== 'object' || value === null) throw new Error(`invalid bundle signing state: ${keyPath}`);\n const candidate = value as Partial<StoredState>;\n if (typeof candidate.privateKey !== 'string' || typeof candidate.publicKey !== 'string') {\n throw new Error(`invalid bundle signing state: ${keyPath}`);\n }\n const revision = candidate.revision === undefined ? 0 : candidate.revision;\n if (!Number.isSafeInteger(revision) || revision < 0) throw new Error(`invalid bundle signing revision: ${keyPath}`);\n let derived: string;\n try {\n derived = publicKeyOf(candidate.privateKey);\n } catch {\n throw new Error(`invalid bundle signing private key: ${keyPath}`);\n }\n if (derived !== candidate.publicKey) throw new Error(`bundle signing keys do not match: ${keyPath}`);\n return { privateKey: candidate.privateKey, publicKey: candidate.publicKey, revision };\n}\n\nfunction loadOrCreateState(stateDir: string): StoredState {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n try {\n const stat = fs.lstatSync(keyPath);\n if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`invalid bundle signing state file: ${keyPath}`);\n const parsed: unknown = JSON.parse(fs.readFileSync(keyPath, 'utf8'));\n const state = parseState(parsed, keyPath);\n fs.chmodSync(keyPath, FILE_MODE);\n if ((parsed as Partial<StoredState>).revision === undefined) atomicWriteState(stateDir, state);\n return state;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n\n const pair = generateKeyPairSync('ec', {\n namedCurve: CURVE,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n publicKeyEncoding: { type: 'spki', format: 'der' },\n });\n const state: StoredState = {\n privateKey: pair.privateKey,\n publicKey: Buffer.from(pair.publicKey).toString('base64url'),\n revision: 0,\n };\n atomicWriteState(stateDir, state);\n return state;\n });\n}\n\nfunction reserveRevision(stateDir: string, expectedPublicKey: string): number {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n const state = parseState(JSON.parse(fs.readFileSync(keyPath, 'utf8')) as unknown, keyPath);\n if (state.publicKey !== expectedPublicKey)\n throw new Error('bundle signing key changed while the signer was active');\n if (state.revision === Number.MAX_SAFE_INTEGER) throw new Error('bundle signing revision is exhausted');\n const next = { ...state, revision: state.revision + 1 };\n atomicWriteState(stateDir, next);\n return next.revision;\n });\n}\n\nfunction contentTypeFor(filePath: string): string {\n return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/** Every servable regular file under the asset root, as browser paths. */\nfunction walk(root: string, current: string, into: BundleAsset[]): void {\n const entries = fs.readdirSync(current, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n const stat = fs.lstatSync(full);\n if (stat.isSymbolicLink()) throw new Error(`refusing to sign symbolic link: ${full}`);\n if (stat.isDirectory()) {\n walk(root, full, into);\n continue;\n }\n if (!stat.isFile()) throw new Error(`refusing to omit unsupported asset: ${full}`);\n if (SKIPPED.has(path.extname(entry.name))) continue;\n const bytes = fs.readFileSync(full);\n into.push({\n path: `/${path\n .relative(root, full)\n .split(path.sep)\n .map((segment) => encodeURIComponent(segment))\n .join('/')}`,\n sha256: createHash('sha256').update(bytes).digest('hex'),\n byteLength: bytes.byteLength,\n contentType: contentTypeFor(full),\n });\n }\n}\n\nexport function createBundleSigner(stateDir: string, _onNotice: (message: string) => void = () => {}): BundleSigner {\n const key = loadOrCreateState(stateDir);\n const privateKey = createPrivateKey(key.privateKey);\n const cache = new Map<string, SignedBundleManifest | undefined>();\n const cacheKeyOf = (assetsDir: string, profile: BundleProfile): string => `${profile}:${path.resolve(assetsDir)}`;\n\n const signFresh = (assetsDir: string, profile: BundleProfile): SignedBundleManifest | undefined => {\n const rootStat = fs.lstatSync(assetsDir);\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {\n throw new Error(`refusing to sign an unsafe asset root: ${assetsDir}`);\n }\n const assets: BundleAsset[] = [];\n walk(assetsDir, assetsDir, assets);\n if (assets.length === 0) return undefined;\n assets.sort((left, right) => left.path.localeCompare(right.path));\n const manifest: BundleManifest = {\n version: BUNDLE_MANIFEST_VERSION,\n revision: reserveRevision(stateDir, key.publicKey),\n builtAt: Date.now(),\n assets,\n };\n if (!isBundleManifest(manifest, profile)) throw new Error(`refusing to sign an invalid ${profile} bundle manifest`);\n const signature = createSign(SIGN_ALGORITHM)\n .update(canonicalManifest(manifest))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n return { manifest, signature, publicKey: key.publicKey };\n };\n\n const sign = (assetsDir: string, profile: BundleProfile = 'web'): SignedBundleManifest | undefined => {\n const cacheKey = cacheKeyOf(assetsDir, profile);\n if (cache.has(cacheKey)) return cache.get(cacheKey);\n const signed = signFresh(path.resolve(assetsDir), profile);\n cache.set(cacheKey, signed);\n return signed;\n };\n\n return {\n publicKey: () => key.publicKey,\n sign,\n refresh(assetsDir, profile = 'web') {\n cache.delete(cacheKeyOf(assetsDir, profile));\n return sign(assetsDir, profile);\n },\n invalidate(assetsDir) {\n if (assetsDir === undefined) {\n cache.clear();\n return;\n }\n for (const profile of PROFILES) cache.delete(cacheKeyOf(assetsDir, profile));\n },\n attest(tag, fields) {\n return createSign(SIGN_ALGORITHM)\n .update(attestationBytes(tag, fields))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n },\n };\n}\n\n/** Re-derives the public key from a stored private key. */\nexport function publicKeyOf(privateKeyPem: string): string {\n return Buffer.from(createPublicKey(privateKeyPem).export({ type: 'spki', format: 'der' }) as Buffer).toString(\n 'base64url',\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["fs","path","randomBytes","generateKeyPairSync","createHash","createPrivateKey","isBundleManifest","createSign","canonicalManifest","attestationBytes","createPublicKey","require_runtime","require_bundleManifest","node_crypto","node_fs","node_path","SIGNING_FILE","SIGN_ALGORITHM","SKIPPED","PROFILES","MIME_TYPES","ensureStateDirectory","stateDir","stat","withStateLock","lockPath","descriptor","attempt","error","operation","atomicWriteState","destination","temporary","state","parseState","value","keyPath","candidate","revision","derived","publicKeyOf","loadOrCreateState","parsed","pair","reserveRevision","expectedPublicKey","next","contentTypeFor","filePath","walk","entries","current","entry","full","root","into","bytes","segment","createBundleSigner","key","privateKey","cache","cacheKeyOf","profile","assetsDir","signFresh","rootStat","assets","left","right","manifest","sign","cacheKey","signed","tag","fields","privateKeyPem"],"sources":["../../../src/services/bundleSigner/index.ts"],"sourcesContent":["import {\n createHash,\n createPrivateKey,\n createPublicKey,\n createSign,\n generateKeyPairSync,\n randomBytes,\n} from 'node:crypto';\n// sync: Public createBundleSigner and BundleSigner.sign/refresh contracts must remain synchronous until phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n BUNDLE_MANIFEST_VERSION,\n type BundleAsset,\n type BundleManifest,\n type BundleProfile,\n type SignedBundleManifest,\n attestationBytes,\n canonicalManifest,\n isBundleManifest,\n} from '../../types/bundleManifest';\n\nconst SIGNING_FILE = 'signing.json';\nconst LOCK_FILE = 'signing.lock';\nconst FILE_MODE = 0o600;\nconst DIRECTORY_MODE = 0o700;\nconst CURVE = 'prime256v1';\nconst SIGN_ALGORITHM = 'sha256';\nconst SKIPPED = new Set(['.map']);\nconst PROFILES: readonly BundleProfile[] = ['web', 'native', 'app'];\nconst MIME_TYPES: Readonly<Record<string, string>> = {\n '.css': 'text/css',\n '.gif': 'image/gif',\n '.html': 'text/html',\n '.ico': 'image/x-icon',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.mjs': 'text/javascript',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain',\n '.wasm': 'application/wasm',\n '.webp': 'image/webp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n};\n\nexport interface BundleSigner {\n /** Base64url SPKI for an independently trusted verifier to pin. */\n publicKey(): string;\n /**\n * Hashes and signs once per asset directory and profile. Undefined when the bundle is empty. Throws\n * when the assets do not match the profile.\n */\n sign(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates and immediately rebuilds one cached manifest. */\n refresh(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates one cached directory under every profile, or every cached directory when omitted. */\n invalidate(assetsDir?: string): void;\n /**\n * Base64url IEEE P1363 signature over `attestationBytes(tag, fields)` with the bundle key. Throws on a\n * malformed tag.\n */\n attest(tag: string, fields: readonly string[]): string;\n}\n\ninterface StoredState {\n privateKey: string;\n publicKey: string;\n revision: number;\n}\n\nfunction ensureStateDirectory(stateDir: string): void {\n fs.mkdirSync(stateDir, { recursive: true, mode: DIRECTORY_MODE });\n const stat = fs.lstatSync(stateDir);\n if (!stat.isDirectory() || stat.isSymbolicLink())\n throw new Error(`bundle signing state is not a directory: ${stateDir}`);\n fs.chmodSync(stateDir, DIRECTORY_MODE);\n}\n\nfunction withStateLock<T>(stateDir: string, operation: () => T): T {\n ensureStateDirectory(stateDir);\n const lockPath = path.join(stateDir, LOCK_FILE);\n let descriptor: number | undefined;\n for (let attempt = 0; attempt < 500; attempt += 1) {\n try {\n descriptor = fs.openSync(lockPath, 'wx', FILE_MODE);\n break;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n if (descriptor === undefined) throw new Error(`timed out waiting for bundle signing state lock: ${lockPath}`);\n try {\n return operation();\n } finally {\n fs.closeSync(descriptor);\n fs.rmSync(lockPath, { force: true });\n }\n}\n\nfunction atomicWriteState(stateDir: string, state: StoredState): void {\n const destination = path.join(stateDir, SIGNING_FILE);\n const temporary = path.join(stateDir, `.${SIGNING_FILE}.${process.pid}.${randomBytes(8).toString('hex')}`);\n const descriptor = fs.openSync(temporary, 'wx', FILE_MODE);\n try {\n fs.writeFileSync(descriptor, `${JSON.stringify(state, undefined, 2)}\\n`);\n fs.fsyncSync(descriptor);\n } finally {\n fs.closeSync(descriptor);\n }\n try {\n fs.renameSync(temporary, destination);\n fs.chmodSync(destination, FILE_MODE);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n throw error;\n }\n}\n\nfunction parseState(value: unknown, keyPath: string): StoredState {\n if (typeof value !== 'object' || value === null) throw new Error(`invalid bundle signing state: ${keyPath}`);\n const candidate = value as Partial<StoredState>;\n if (typeof candidate.privateKey !== 'string' || typeof candidate.publicKey !== 'string') {\n throw new Error(`invalid bundle signing state: ${keyPath}`);\n }\n const revision = candidate.revision === undefined ? 0 : candidate.revision;\n if (!Number.isSafeInteger(revision) || revision < 0) throw new Error(`invalid bundle signing revision: ${keyPath}`);\n let derived: string;\n try {\n derived = publicKeyOf(candidate.privateKey);\n } catch {\n throw new Error(`invalid bundle signing private key: ${keyPath}`);\n }\n if (derived !== candidate.publicKey) throw new Error(`bundle signing keys do not match: ${keyPath}`);\n return { privateKey: candidate.privateKey, publicKey: candidate.publicKey, revision };\n}\n\nfunction loadOrCreateState(stateDir: string): StoredState {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n try {\n const stat = fs.lstatSync(keyPath);\n if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`invalid bundle signing state file: ${keyPath}`);\n const parsed: unknown = JSON.parse(fs.readFileSync(keyPath, 'utf8'));\n const state = parseState(parsed, keyPath);\n fs.chmodSync(keyPath, FILE_MODE);\n if ((parsed as Partial<StoredState>).revision === undefined) atomicWriteState(stateDir, state);\n return state;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n\n const pair = generateKeyPairSync('ec', {\n namedCurve: CURVE,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n publicKeyEncoding: { type: 'spki', format: 'der' },\n });\n const state: StoredState = {\n privateKey: pair.privateKey,\n publicKey: Buffer.from(pair.publicKey).toString('base64url'),\n revision: 0,\n };\n atomicWriteState(stateDir, state);\n return state;\n });\n}\n\nfunction reserveRevision(stateDir: string, expectedPublicKey: string): number {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n const state = parseState(JSON.parse(fs.readFileSync(keyPath, 'utf8')) as unknown, keyPath);\n if (state.publicKey !== expectedPublicKey)\n throw new Error('bundle signing key changed while the signer was active');\n if (state.revision === Number.MAX_SAFE_INTEGER) throw new Error('bundle signing revision is exhausted');\n const next = { ...state, revision: state.revision + 1 };\n atomicWriteState(stateDir, next);\n return next.revision;\n });\n}\n\nfunction contentTypeFor(filePath: string): string {\n return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/** Every servable regular file under the asset root, as browser paths. */\nfunction walk(root: string, current: string, into: BundleAsset[]): void {\n const entries = fs.readdirSync(current, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n const stat = fs.lstatSync(full);\n if (stat.isSymbolicLink()) throw new Error(`refusing to sign symbolic link: ${full}`);\n if (stat.isDirectory()) {\n walk(root, full, into);\n continue;\n }\n if (!stat.isFile()) throw new Error(`refusing to omit unsupported asset: ${full}`);\n if (SKIPPED.has(path.extname(entry.name))) continue;\n const bytes = fs.readFileSync(full);\n into.push({\n path: `/${path\n .relative(root, full)\n .split(path.sep)\n .map((segment) => encodeURIComponent(segment))\n .join('/')}`,\n sha256: createHash('sha256').update(bytes).digest('hex'),\n byteLength: bytes.byteLength,\n contentType: contentTypeFor(full),\n });\n }\n}\n\nexport function createBundleSigner(stateDir: string, _onNotice: (message: string) => void = () => {}): BundleSigner {\n const key = loadOrCreateState(stateDir);\n const privateKey = createPrivateKey(key.privateKey);\n const cache = new Map<string, SignedBundleManifest | undefined>();\n const cacheKeyOf = (assetsDir: string, profile: BundleProfile): string => `${profile}:${path.resolve(assetsDir)}`;\n\n const signFresh = (assetsDir: string, profile: BundleProfile): SignedBundleManifest | undefined => {\n const rootStat = fs.lstatSync(assetsDir);\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {\n throw new Error(`refusing to sign an unsafe asset root: ${assetsDir}`);\n }\n const assets: BundleAsset[] = [];\n walk(assetsDir, assetsDir, assets);\n if (assets.length === 0) return undefined;\n assets.sort((left, right) => left.path.localeCompare(right.path));\n const manifest: BundleManifest = {\n version: BUNDLE_MANIFEST_VERSION,\n revision: reserveRevision(stateDir, key.publicKey),\n builtAt: Date.now(),\n assets,\n };\n if (!isBundleManifest(manifest, profile)) throw new Error(`refusing to sign an invalid ${profile} bundle manifest`);\n const signature = createSign(SIGN_ALGORITHM)\n .update(canonicalManifest(manifest))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n return { manifest, signature, publicKey: key.publicKey };\n };\n\n const sign = (assetsDir: string, profile: BundleProfile = 'web'): SignedBundleManifest | undefined => {\n const cacheKey = cacheKeyOf(assetsDir, profile);\n if (cache.has(cacheKey)) return cache.get(cacheKey);\n const signed = signFresh(path.resolve(assetsDir), profile);\n cache.set(cacheKey, signed);\n return signed;\n };\n\n return {\n publicKey: () => key.publicKey,\n sign,\n refresh(assetsDir, profile = 'web') {\n cache.delete(cacheKeyOf(assetsDir, profile));\n return sign(assetsDir, profile);\n },\n invalidate(assetsDir) {\n if (assetsDir === undefined) {\n cache.clear();\n return;\n }\n for (const profile of PROFILES) cache.delete(cacheKeyOf(assetsDir, profile));\n },\n attest(tag, fields) {\n return createSign(SIGN_ALGORITHM)\n .update(attestationBytes(tag, fields))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n },\n };\n}\n\n/** Re-derives the public key from a stored private key. */\nexport function publicKeyOf(privateKeyPem: string): string {\n return Buffer.from(createPublicKey(privateKeyPem).export({ type: 'spki', format: 'der' }) as Buffer).toString(\n 'base64url',\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/bundleSigner/index.ts"],"mappings":";;iBAiDiB;;EAEf;;;;;EAKA,KAAK,mBAAmB,UAAU,gBAAgB;;EAElD,QAAQ,mBAAmB,UAAU,gBAAgB;;EAErD,WAAW;;;;;EAKX,OAAO,aAAa;;wBAsJN,mBAAmB,kBAAkB,aAAY,2BAAsC;;wBA6DvF,YAAY"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/bundleSigner/index.ts"],"mappings":";;iBAkDiB;;EAEf;;;;;EAKA,KAAK,mBAAmB,UAAU,gBAAgB;;EAElD,QAAQ,mBAAmB,UAAU,gBAAgB;;EAErD,WAAW;;;;;EAKX,OAAO,aAAa;;wBAsJN,mBAAmB,kBAAkB,aAAY,2BAAsC;;wBA6DvF,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fs","path","SIGNING_FILE","SIGN_ALGORITHM","SKIPPED","PROFILES","MIME_TYPES","ensureStateDirectory","stateDir","stat","withStateLock","lockPath","descriptor","attempt","error","operation","atomicWriteState","destination","temporary","randomBytes","state","parseState","value","keyPath","candidate","revision","derived","publicKeyOf","loadOrCreateState","parsed","pair","generateKeyPairSync","reserveRevision","expectedPublicKey","next","contentTypeFor","filePath","walk","entries","current","entry","full","root","into","bytes","segment","createHash","createBundleSigner","key","privateKey","createPrivateKey","cache","cacheKeyOf","profile","assetsDir","signFresh","rootStat","assets","left","right","manifest","isBundleManifest","createSign","canonicalManifest","sign","cacheKey","signed","attestationBytes","tag","fields","createPublicKey","privateKeyPem"],"sources":["../../../src/services/bundleSigner/index.ts"],"sourcesContent":["import {\n createHash,\n createPrivateKey,\n createPublicKey,\n createSign,\n generateKeyPairSync,\n randomBytes,\n} from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n BUNDLE_MANIFEST_VERSION,\n type BundleAsset,\n type BundleManifest,\n type BundleProfile,\n type SignedBundleManifest,\n attestationBytes,\n canonicalManifest,\n isBundleManifest,\n} from '../../types/bundleManifest';\n\nconst SIGNING_FILE = 'signing.json';\nconst LOCK_FILE = 'signing.lock';\nconst FILE_MODE = 0o600;\nconst DIRECTORY_MODE = 0o700;\nconst CURVE = 'prime256v1';\nconst SIGN_ALGORITHM = 'sha256';\nconst SKIPPED = new Set(['.map']);\nconst PROFILES: readonly BundleProfile[] = ['web', 'native', 'app'];\nconst MIME_TYPES: Readonly<Record<string, string>> = {\n '.css': 'text/css',\n '.gif': 'image/gif',\n '.html': 'text/html',\n '.ico': 'image/x-icon',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.mjs': 'text/javascript',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain',\n '.wasm': 'application/wasm',\n '.webp': 'image/webp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n};\n\nexport interface BundleSigner {\n /** Base64url SPKI for an independently trusted verifier to pin. */\n publicKey(): string;\n /**\n * Hashes and signs once per asset directory and profile. Undefined when the bundle is empty. Throws\n * when the assets do not match the profile.\n */\n sign(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates and immediately rebuilds one cached manifest. */\n refresh(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates one cached directory under every profile, or every cached directory when omitted. */\n invalidate(assetsDir?: string): void;\n /**\n * Base64url IEEE P1363 signature over `attestationBytes(tag, fields)` with the bundle key. Throws on a\n * malformed tag.\n */\n attest(tag: string, fields: readonly string[]): string;\n}\n\ninterface StoredState {\n privateKey: string;\n publicKey: string;\n revision: number;\n}\n\nfunction ensureStateDirectory(stateDir: string): void {\n fs.mkdirSync(stateDir, { recursive: true, mode: DIRECTORY_MODE });\n const stat = fs.lstatSync(stateDir);\n if (!stat.isDirectory() || stat.isSymbolicLink())\n throw new Error(`bundle signing state is not a directory: ${stateDir}`);\n fs.chmodSync(stateDir, DIRECTORY_MODE);\n}\n\nfunction withStateLock<T>(stateDir: string, operation: () => T): T {\n ensureStateDirectory(stateDir);\n const lockPath = path.join(stateDir, LOCK_FILE);\n let descriptor: number | undefined;\n for (let attempt = 0; attempt < 500; attempt += 1) {\n try {\n descriptor = fs.openSync(lockPath, 'wx', FILE_MODE);\n break;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n if (descriptor === undefined) throw new Error(`timed out waiting for bundle signing state lock: ${lockPath}`);\n try {\n return operation();\n } finally {\n fs.closeSync(descriptor);\n fs.rmSync(lockPath, { force: true });\n }\n}\n\nfunction atomicWriteState(stateDir: string, state: StoredState): void {\n const destination = path.join(stateDir, SIGNING_FILE);\n const temporary = path.join(stateDir, `.${SIGNING_FILE}.${process.pid}.${randomBytes(8).toString('hex')}`);\n const descriptor = fs.openSync(temporary, 'wx', FILE_MODE);\n try {\n fs.writeFileSync(descriptor, `${JSON.stringify(state, undefined, 2)}\\n`);\n fs.fsyncSync(descriptor);\n } finally {\n fs.closeSync(descriptor);\n }\n try {\n fs.renameSync(temporary, destination);\n fs.chmodSync(destination, FILE_MODE);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n throw error;\n }\n}\n\nfunction parseState(value: unknown, keyPath: string): StoredState {\n if (typeof value !== 'object' || value === null) throw new Error(`invalid bundle signing state: ${keyPath}`);\n const candidate = value as Partial<StoredState>;\n if (typeof candidate.privateKey !== 'string' || typeof candidate.publicKey !== 'string') {\n throw new Error(`invalid bundle signing state: ${keyPath}`);\n }\n const revision = candidate.revision === undefined ? 0 : candidate.revision;\n if (!Number.isSafeInteger(revision) || revision < 0) throw new Error(`invalid bundle signing revision: ${keyPath}`);\n let derived: string;\n try {\n derived = publicKeyOf(candidate.privateKey);\n } catch {\n throw new Error(`invalid bundle signing private key: ${keyPath}`);\n }\n if (derived !== candidate.publicKey) throw new Error(`bundle signing keys do not match: ${keyPath}`);\n return { privateKey: candidate.privateKey, publicKey: candidate.publicKey, revision };\n}\n\nfunction loadOrCreateState(stateDir: string): StoredState {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n try {\n const stat = fs.lstatSync(keyPath);\n if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`invalid bundle signing state file: ${keyPath}`);\n const parsed: unknown = JSON.parse(fs.readFileSync(keyPath, 'utf8'));\n const state = parseState(parsed, keyPath);\n fs.chmodSync(keyPath, FILE_MODE);\n if ((parsed as Partial<StoredState>).revision === undefined) atomicWriteState(stateDir, state);\n return state;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n\n const pair = generateKeyPairSync('ec', {\n namedCurve: CURVE,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n publicKeyEncoding: { type: 'spki', format: 'der' },\n });\n const state: StoredState = {\n privateKey: pair.privateKey,\n publicKey: Buffer.from(pair.publicKey).toString('base64url'),\n revision: 0,\n };\n atomicWriteState(stateDir, state);\n return state;\n });\n}\n\nfunction reserveRevision(stateDir: string, expectedPublicKey: string): number {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n const state = parseState(JSON.parse(fs.readFileSync(keyPath, 'utf8')) as unknown, keyPath);\n if (state.publicKey !== expectedPublicKey)\n throw new Error('bundle signing key changed while the signer was active');\n if (state.revision === Number.MAX_SAFE_INTEGER) throw new Error('bundle signing revision is exhausted');\n const next = { ...state, revision: state.revision + 1 };\n atomicWriteState(stateDir, next);\n return next.revision;\n });\n}\n\nfunction contentTypeFor(filePath: string): string {\n return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/** Every servable regular file under the asset root, as browser paths. */\nfunction walk(root: string, current: string, into: BundleAsset[]): void {\n const entries = fs.readdirSync(current, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n const stat = fs.lstatSync(full);\n if (stat.isSymbolicLink()) throw new Error(`refusing to sign symbolic link: ${full}`);\n if (stat.isDirectory()) {\n walk(root, full, into);\n continue;\n }\n if (!stat.isFile()) throw new Error(`refusing to omit unsupported asset: ${full}`);\n if (SKIPPED.has(path.extname(entry.name))) continue;\n const bytes = fs.readFileSync(full);\n into.push({\n path: `/${path\n .relative(root, full)\n .split(path.sep)\n .map((segment) => encodeURIComponent(segment))\n .join('/')}`,\n sha256: createHash('sha256').update(bytes).digest('hex'),\n byteLength: bytes.byteLength,\n contentType: contentTypeFor(full),\n });\n }\n}\n\nexport function createBundleSigner(stateDir: string, _onNotice: (message: string) => void = () => {}): BundleSigner {\n const key = loadOrCreateState(stateDir);\n const privateKey = createPrivateKey(key.privateKey);\n const cache = new Map<string, SignedBundleManifest | undefined>();\n const cacheKeyOf = (assetsDir: string, profile: BundleProfile): string => `${profile}:${path.resolve(assetsDir)}`;\n\n const signFresh = (assetsDir: string, profile: BundleProfile): SignedBundleManifest | undefined => {\n const rootStat = fs.lstatSync(assetsDir);\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {\n throw new Error(`refusing to sign an unsafe asset root: ${assetsDir}`);\n }\n const assets: BundleAsset[] = [];\n walk(assetsDir, assetsDir, assets);\n if (assets.length === 0) return undefined;\n assets.sort((left, right) => left.path.localeCompare(right.path));\n const manifest: BundleManifest = {\n version: BUNDLE_MANIFEST_VERSION,\n revision: reserveRevision(stateDir, key.publicKey),\n builtAt: Date.now(),\n assets,\n };\n if (!isBundleManifest(manifest, profile)) throw new Error(`refusing to sign an invalid ${profile} bundle manifest`);\n const signature = createSign(SIGN_ALGORITHM)\n .update(canonicalManifest(manifest))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n return { manifest, signature, publicKey: key.publicKey };\n };\n\n const sign = (assetsDir: string, profile: BundleProfile = 'web'): SignedBundleManifest | undefined => {\n const cacheKey = cacheKeyOf(assetsDir, profile);\n if (cache.has(cacheKey)) return cache.get(cacheKey);\n const signed = signFresh(path.resolve(assetsDir), profile);\n cache.set(cacheKey, signed);\n return signed;\n };\n\n return {\n publicKey: () => key.publicKey,\n sign,\n refresh(assetsDir, profile = 'web') {\n cache.delete(cacheKeyOf(assetsDir, profile));\n return sign(assetsDir, profile);\n },\n invalidate(assetsDir) {\n if (assetsDir === undefined) {\n cache.clear();\n return;\n }\n for (const profile of PROFILES) cache.delete(cacheKeyOf(assetsDir, profile));\n },\n attest(tag, fields) {\n return createSign(SIGN_ALGORITHM)\n .update(attestationBytes(tag, fields))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n },\n };\n}\n\n/** Re-derives the public key from a stored private key. */\nexport function publicKeyOf(privateKeyPem: string): string {\n return Buffer.from(createPublicKey(privateKeyPem).export({ type: 'spki', format: 'der' }) as Buffer).toString(\n 'base64url',\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["fs","path","SIGNING_FILE","SIGN_ALGORITHM","SKIPPED","PROFILES","MIME_TYPES","ensureStateDirectory","stateDir","stat","withStateLock","lockPath","descriptor","attempt","error","operation","atomicWriteState","destination","temporary","randomBytes","state","parseState","value","keyPath","candidate","revision","derived","publicKeyOf","loadOrCreateState","parsed","pair","generateKeyPairSync","reserveRevision","expectedPublicKey","next","contentTypeFor","filePath","walk","entries","current","entry","full","root","into","bytes","segment","createHash","createBundleSigner","key","privateKey","createPrivateKey","cache","cacheKeyOf","profile","assetsDir","signFresh","rootStat","assets","left","right","manifest","isBundleManifest","createSign","canonicalManifest","sign","cacheKey","signed","attestationBytes","tag","fields","createPublicKey","privateKeyPem"],"sources":["../../../src/services/bundleSigner/index.ts"],"sourcesContent":["import {\n createHash,\n createPrivateKey,\n createPublicKey,\n createSign,\n generateKeyPairSync,\n randomBytes,\n} from 'node:crypto';\n// sync: Public createBundleSigner and BundleSigner.sign/refresh contracts must remain synchronous until phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n BUNDLE_MANIFEST_VERSION,\n type BundleAsset,\n type BundleManifest,\n type BundleProfile,\n type SignedBundleManifest,\n attestationBytes,\n canonicalManifest,\n isBundleManifest,\n} from '../../types/bundleManifest';\n\nconst SIGNING_FILE = 'signing.json';\nconst LOCK_FILE = 'signing.lock';\nconst FILE_MODE = 0o600;\nconst DIRECTORY_MODE = 0o700;\nconst CURVE = 'prime256v1';\nconst SIGN_ALGORITHM = 'sha256';\nconst SKIPPED = new Set(['.map']);\nconst PROFILES: readonly BundleProfile[] = ['web', 'native', 'app'];\nconst MIME_TYPES: Readonly<Record<string, string>> = {\n '.css': 'text/css',\n '.gif': 'image/gif',\n '.html': 'text/html',\n '.ico': 'image/x-icon',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.mjs': 'text/javascript',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain',\n '.wasm': 'application/wasm',\n '.webp': 'image/webp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n};\n\nexport interface BundleSigner {\n /** Base64url SPKI for an independently trusted verifier to pin. */\n publicKey(): string;\n /**\n * Hashes and signs once per asset directory and profile. Undefined when the bundle is empty. Throws\n * when the assets do not match the profile.\n */\n sign(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates and immediately rebuilds one cached manifest. */\n refresh(assetsDir: string, profile?: BundleProfile): SignedBundleManifest | undefined;\n /** Invalidates one cached directory under every profile, or every cached directory when omitted. */\n invalidate(assetsDir?: string): void;\n /**\n * Base64url IEEE P1363 signature over `attestationBytes(tag, fields)` with the bundle key. Throws on a\n * malformed tag.\n */\n attest(tag: string, fields: readonly string[]): string;\n}\n\ninterface StoredState {\n privateKey: string;\n publicKey: string;\n revision: number;\n}\n\nfunction ensureStateDirectory(stateDir: string): void {\n fs.mkdirSync(stateDir, { recursive: true, mode: DIRECTORY_MODE });\n const stat = fs.lstatSync(stateDir);\n if (!stat.isDirectory() || stat.isSymbolicLink())\n throw new Error(`bundle signing state is not a directory: ${stateDir}`);\n fs.chmodSync(stateDir, DIRECTORY_MODE);\n}\n\nfunction withStateLock<T>(stateDir: string, operation: () => T): T {\n ensureStateDirectory(stateDir);\n const lockPath = path.join(stateDir, LOCK_FILE);\n let descriptor: number | undefined;\n for (let attempt = 0; attempt < 500; attempt += 1) {\n try {\n descriptor = fs.openSync(lockPath, 'wx', FILE_MODE);\n break;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n if (descriptor === undefined) throw new Error(`timed out waiting for bundle signing state lock: ${lockPath}`);\n try {\n return operation();\n } finally {\n fs.closeSync(descriptor);\n fs.rmSync(lockPath, { force: true });\n }\n}\n\nfunction atomicWriteState(stateDir: string, state: StoredState): void {\n const destination = path.join(stateDir, SIGNING_FILE);\n const temporary = path.join(stateDir, `.${SIGNING_FILE}.${process.pid}.${randomBytes(8).toString('hex')}`);\n const descriptor = fs.openSync(temporary, 'wx', FILE_MODE);\n try {\n fs.writeFileSync(descriptor, `${JSON.stringify(state, undefined, 2)}\\n`);\n fs.fsyncSync(descriptor);\n } finally {\n fs.closeSync(descriptor);\n }\n try {\n fs.renameSync(temporary, destination);\n fs.chmodSync(destination, FILE_MODE);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n throw error;\n }\n}\n\nfunction parseState(value: unknown, keyPath: string): StoredState {\n if (typeof value !== 'object' || value === null) throw new Error(`invalid bundle signing state: ${keyPath}`);\n const candidate = value as Partial<StoredState>;\n if (typeof candidate.privateKey !== 'string' || typeof candidate.publicKey !== 'string') {\n throw new Error(`invalid bundle signing state: ${keyPath}`);\n }\n const revision = candidate.revision === undefined ? 0 : candidate.revision;\n if (!Number.isSafeInteger(revision) || revision < 0) throw new Error(`invalid bundle signing revision: ${keyPath}`);\n let derived: string;\n try {\n derived = publicKeyOf(candidate.privateKey);\n } catch {\n throw new Error(`invalid bundle signing private key: ${keyPath}`);\n }\n if (derived !== candidate.publicKey) throw new Error(`bundle signing keys do not match: ${keyPath}`);\n return { privateKey: candidate.privateKey, publicKey: candidate.publicKey, revision };\n}\n\nfunction loadOrCreateState(stateDir: string): StoredState {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n try {\n const stat = fs.lstatSync(keyPath);\n if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`invalid bundle signing state file: ${keyPath}`);\n const parsed: unknown = JSON.parse(fs.readFileSync(keyPath, 'utf8'));\n const state = parseState(parsed, keyPath);\n fs.chmodSync(keyPath, FILE_MODE);\n if ((parsed as Partial<StoredState>).revision === undefined) atomicWriteState(stateDir, state);\n return state;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n\n const pair = generateKeyPairSync('ec', {\n namedCurve: CURVE,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n publicKeyEncoding: { type: 'spki', format: 'der' },\n });\n const state: StoredState = {\n privateKey: pair.privateKey,\n publicKey: Buffer.from(pair.publicKey).toString('base64url'),\n revision: 0,\n };\n atomicWriteState(stateDir, state);\n return state;\n });\n}\n\nfunction reserveRevision(stateDir: string, expectedPublicKey: string): number {\n return withStateLock(stateDir, () => {\n const keyPath = path.join(stateDir, SIGNING_FILE);\n const state = parseState(JSON.parse(fs.readFileSync(keyPath, 'utf8')) as unknown, keyPath);\n if (state.publicKey !== expectedPublicKey)\n throw new Error('bundle signing key changed while the signer was active');\n if (state.revision === Number.MAX_SAFE_INTEGER) throw new Error('bundle signing revision is exhausted');\n const next = { ...state, revision: state.revision + 1 };\n atomicWriteState(stateDir, next);\n return next.revision;\n });\n}\n\nfunction contentTypeFor(filePath: string): string {\n return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/** Every servable regular file under the asset root, as browser paths. */\nfunction walk(root: string, current: string, into: BundleAsset[]): void {\n const entries = fs.readdirSync(current, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n const stat = fs.lstatSync(full);\n if (stat.isSymbolicLink()) throw new Error(`refusing to sign symbolic link: ${full}`);\n if (stat.isDirectory()) {\n walk(root, full, into);\n continue;\n }\n if (!stat.isFile()) throw new Error(`refusing to omit unsupported asset: ${full}`);\n if (SKIPPED.has(path.extname(entry.name))) continue;\n const bytes = fs.readFileSync(full);\n into.push({\n path: `/${path\n .relative(root, full)\n .split(path.sep)\n .map((segment) => encodeURIComponent(segment))\n .join('/')}`,\n sha256: createHash('sha256').update(bytes).digest('hex'),\n byteLength: bytes.byteLength,\n contentType: contentTypeFor(full),\n });\n }\n}\n\nexport function createBundleSigner(stateDir: string, _onNotice: (message: string) => void = () => {}): BundleSigner {\n const key = loadOrCreateState(stateDir);\n const privateKey = createPrivateKey(key.privateKey);\n const cache = new Map<string, SignedBundleManifest | undefined>();\n const cacheKeyOf = (assetsDir: string, profile: BundleProfile): string => `${profile}:${path.resolve(assetsDir)}`;\n\n const signFresh = (assetsDir: string, profile: BundleProfile): SignedBundleManifest | undefined => {\n const rootStat = fs.lstatSync(assetsDir);\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {\n throw new Error(`refusing to sign an unsafe asset root: ${assetsDir}`);\n }\n const assets: BundleAsset[] = [];\n walk(assetsDir, assetsDir, assets);\n if (assets.length === 0) return undefined;\n assets.sort((left, right) => left.path.localeCompare(right.path));\n const manifest: BundleManifest = {\n version: BUNDLE_MANIFEST_VERSION,\n revision: reserveRevision(stateDir, key.publicKey),\n builtAt: Date.now(),\n assets,\n };\n if (!isBundleManifest(manifest, profile)) throw new Error(`refusing to sign an invalid ${profile} bundle manifest`);\n const signature = createSign(SIGN_ALGORITHM)\n .update(canonicalManifest(manifest))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n return { manifest, signature, publicKey: key.publicKey };\n };\n\n const sign = (assetsDir: string, profile: BundleProfile = 'web'): SignedBundleManifest | undefined => {\n const cacheKey = cacheKeyOf(assetsDir, profile);\n if (cache.has(cacheKey)) return cache.get(cacheKey);\n const signed = signFresh(path.resolve(assetsDir), profile);\n cache.set(cacheKey, signed);\n return signed;\n };\n\n return {\n publicKey: () => key.publicKey,\n sign,\n refresh(assetsDir, profile = 'web') {\n cache.delete(cacheKeyOf(assetsDir, profile));\n return sign(assetsDir, profile);\n },\n invalidate(assetsDir) {\n if (assetsDir === undefined) {\n cache.clear();\n return;\n }\n for (const profile of PROFILES) cache.delete(cacheKeyOf(assetsDir, profile));\n },\n attest(tag, fields) {\n return createSign(SIGN_ALGORITHM)\n .update(attestationBytes(tag, fields))\n .sign({ key: privateKey, dsaEncoding: 'ieee-p1363' })\n .toString('base64url');\n },\n };\n}\n\n/** Re-derives the public key from a stored private key. */\nexport function publicKeyOf(privateKeyPem: string): string {\n return Buffer.from(createPublicKey(privateKeyPem).export({ type: 'spki', format: 'der' }) as Buffer).toString(\n 'base64url',\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-web-security",
3
- "version": "0.0.0-alpha.45",
3
+ "version": "0.0.0-alpha.47",
4
4
  "description": "Shared security primitives for the Po web cockpit: sealed channels and signed bundle manifests for independently trusted verifiers.",
5
5
  "keywords": [
6
6
  "ai",