@stackwright-pro/mcp 0.2.0-alpha.52 → 0.2.0-alpha.54
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.
- package/dist/integrity.js +3 -3
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +3 -3
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +4 -4
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +4 -4
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/integrity.js
CHANGED
|
@@ -45,7 +45,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
45
45
|
],
|
|
46
46
|
[
|
|
47
47
|
"stackwright-pro-data-otter.json",
|
|
48
|
-
"
|
|
48
|
+
"3bdf0da3b90282a72598c242507fa6806dee9ac589ccdae5555dc1254a697373"
|
|
49
49
|
],
|
|
50
50
|
[
|
|
51
51
|
"stackwright-pro-designer-otter.json",
|
|
@@ -57,11 +57,11 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
57
57
|
],
|
|
58
58
|
[
|
|
59
59
|
"stackwright-pro-page-otter.json",
|
|
60
|
-
"
|
|
60
|
+
"9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626"
|
|
61
61
|
],
|
|
62
62
|
[
|
|
63
63
|
"stackwright-pro-theme-otter.json",
|
|
64
|
-
"
|
|
64
|
+
"08bb04009fdfb8743b10ac4d503cbaddaf8d7c804ba9b606aaed9cc516fd8e93"
|
|
65
65
|
],
|
|
66
66
|
[
|
|
67
67
|
"stackwright-pro-workflow-otter.json",
|
package/dist/integrity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/integrity.ts"],"sourcesContent":["/**\n * Otter Integrity Verification\n * ============================\n * Protects the Pro Otter Raft from disk-based prompt injection / jailbreak attacks.\n *\n * TypeScript port of python/src/stackwright_pro/raft/integrity.py — this lets\n * the MCP package verify otter files without a Python dependency.\n *\n * Certificate-pinned canonical checksums — hardcoded in the MCP package.\n *\n * These are NOT read from disk. An attacker who modifies otter JSON files\n * in @stackwright-pro/otters cannot also modify these constants without\n * compromising the separately-published @stackwright-pro/mcp package.\n *\n * To update: node scripts/sync-mcp-checksums.cjs\n * (reads from packages/otters/src/checksums.json, writes this file)\n */\nimport { createHash, timingSafeEqual } from 'crypto';\nimport { readFileSync, readdirSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n// ---------------------------------------------------------------------------\n// Certificate-pinned canonical checksums — frozen Map, immutable by design.\n// DO NOT read these from disk — that would defeat the entire purpose.\n// Object.freeze prevents property mutation at runtime; ReadonlyMap prevents\n// .set() / .delete() at compile time (belt-and-suspenders).\n// ---------------------------------------------------------------------------\n\nconst _checksums = new Map<string, string>([\n [\n 'stackwright-pro-api-otter.json',\n '9fbaed0ce6116b82d0289f24432037d04637c89b8e73062ed946e5d49b294734',\n ],\n [\n 'stackwright-pro-auth-otter.json',\n 'bf0e66e35d15ba818ba6ff1a007df34975565bacbb35cc0c80151fb1da13e573',\n ],\n [\n 'stackwright-pro-dashboard-otter.json',\n '89e81ac161147ab532034b40e4f7863dcd7d03ef33fd94c97d19c3e56fb91a9e',\n ],\n [\n 'stackwright-pro-data-otter.json',\n '4769c3fa9b358609159ef2f613ac9d40dc7cb439e916c3344b9500a75d6afa82',\n ],\n [\n 'stackwright-pro-designer-otter.json',\n 'af09ac8f06385bdbac63e2820daa2ff7d38b8ff1ff383c161f07e3fb9d9359c5',\n ],\n [\n 'stackwright-pro-foreman-otter.json',\n '582a26766a5bc80ef9f3aef53e82794c7f47f618bd28e4e70583bfc2b569398c',\n ],\n [\n 'stackwright-pro-page-otter.json',\n '2fd8fe6f799e3fb4659979b1c958c64c276632fcfe18f70371543dc8145baa1b',\n ],\n [\n 'stackwright-pro-theme-otter.json',\n 'bc5695ef28164516b3f677cab201de781663c354d187cea28cc41884f1d89414',\n ],\n [\n 'stackwright-pro-workflow-otter.json',\n 'c90d6773b2287aa9a640c2715ca0e75f44c13e99fddcfb89ced36603f38930ce',\n ],\n]);\nObject.freeze(_checksums);\nconst CANONICAL_CHECKSUMS: ReadonlyMap<string, string> = _checksums;\n\n// ---------------------------------------------------------------------------\n// Import-time format validation — malformed constants are a packaging bug,\n// not a runtime surprise. Fail fast, fail loud.\n// ---------------------------------------------------------------------------\n\nconst SHA256_HEX_RE = /^[0-9a-f]{64}$/;\n\nfor (const [name, digest] of CANONICAL_CHECKSUMS) {\n if (!SHA256_HEX_RE.test(digest)) {\n throw new Error(\n `Malformed SHA-256 in CANONICAL_CHECKSUMS for \"${name}\": ` +\n `expected 64 hex chars, got ${digest.length}: \"${digest}\"`\n );\n }\n}\n\n// 1 MB — generous headroom for agent definitions; anything larger is suspicious.\nconst MAX_OTTER_BYTES = 1 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Core functions (exported for direct testing — no MCP server needed)\n// ---------------------------------------------------------------------------\n\n/** Compute the hex-encoded SHA-256 digest of raw bytes. Pure, no I/O. */\nexport function computeSha256(data: Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/** Constant-time comparison of two hex digest strings. */\nfunction safeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));\n}\n\n// ---------------------------------------------------------------------------\n// Single-file verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyOtterFileResult {\n verified: boolean;\n filename: string;\n error?: string;\n}\n\n/**\n * Read a single otter JSON file, check its size, compute its SHA-256,\n * and constant-time compare against the canonical checksum.\n *\n * Single read → hash → decode. No TOCTOU window.\n */\nexport function verifyOtterFile(filePath: string): VerifyOtterFileResult {\n const filename = basename(filePath);\n\n // Fast-fail on unknown filenames before any I/O\n const expected = CANONICAL_CHECKSUMS.get(filename);\n if (expected === undefined) {\n return { verified: false, filename, error: `Unknown otter file: not in canonical set` };\n }\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based swaps)\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot stat file: ${msg}` };\n }\n\n if (stat.isSymbolicLink()) {\n return { verified: false, filename, error: 'Refusing to verify symlink' };\n }\n\n // Stat-based size pre-check — don't materialise oversized payloads\n const size = stat.size;\n\n if (size > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${size.toLocaleString()})`,\n };\n }\n\n // Single read — used for hashing and UTF-8 validation (zero TOCTOU window)\n let raw: Buffer;\n try {\n raw = readFileSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot read file: ${msg}` };\n }\n\n // Belt-and-suspenders: re-check length after read in case of a race\n if (raw.length > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit after read (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${raw.length.toLocaleString()})`,\n };\n }\n\n // Hash the raw bytes\n const actual = computeSha256(raw);\n\n // Constant-time comparison prevents timing-oracle attacks\n if (!safeEqual(actual, expected)) {\n return {\n verified: false,\n filename,\n error: `SHA-256 mismatch: expected ${expected.substring(0, 8)}…, got ${actual.substring(0, 8)}…`,\n };\n }\n\n // UTF-8 validation — binary injection guard\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n decoder.decode(raw);\n } catch {\n return {\n verified: false,\n filename,\n error: 'File is not valid UTF-8 — may be corrupted or contain binary injection',\n };\n }\n\n return { verified: true, filename };\n}\n\n// ---------------------------------------------------------------------------\n// Directory-level verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyAllOttersResult {\n verified: string[];\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Scan a directory for `*-otter.json` files, verify each one against\n * canonical checksums. Returns lists of verified, failed, and unknown files.\n */\nexport function verifyAllOtters(otterDir: string): VerifyAllOttersResult {\n // ---------------------------------------------------------------------------\n // Path traversal guard — reject any input containing \"..\" sequences before\n // any I/O. An attacker controlling this parameter could otherwise scan\n // directories outside the expected otter install locations.\n // ---------------------------------------------------------------------------\n if (/(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/.test(otterDir) || otterDir.includes('..')) {\n return {\n verified: [],\n failed: [\n {\n filename: '<directory>',\n error: `Security: path traversal sequence detected in otter directory parameter`,\n },\n ],\n unknown: [],\n };\n }\n\n const verified: string[] = [];\n const failed: Array<{ filename: string; error: string }> = [];\n const unknown: string[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(otterDir);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n verified: [],\n failed: [{ filename: '<directory>', error: `Cannot read directory: ${msg}` }],\n unknown: [],\n };\n }\n\n const otterFiles = entries.filter((f) => f.endsWith('-otter.json'));\n\n for (const filename of otterFiles) {\n const filePath = join(otterDir, filename);\n\n // Skip symlinks at the directory-scan level too\n try {\n if (lstatSync(filePath).isSymbolicLink()) {\n failed.push({ filename, error: 'Skipped: symlink' });\n continue;\n }\n } catch {\n // verifyOtterFile will handle stat errors\n }\n\n const result = verifyOtterFile(filePath);\n\n if (result.verified) {\n verified.push(result.filename);\n } else if (result.error?.startsWith('Unknown otter file')) {\n unknown.push(result.filename);\n } else {\n failed.push({ filename: result.filename, error: result.error ?? 'Unknown error' });\n }\n }\n\n // Check for missing canonical files — ones we expect but didn't find on disk\n for (const canonicalName of CANONICAL_CHECKSUMS.keys()) {\n if (!otterFiles.includes(canonicalName)) {\n failed.push({ filename: canonicalName, error: 'Missing from directory' });\n }\n }\n\n return { verified, failed, unknown };\n}\n\n// ---------------------------------------------------------------------------\n// Otter directory resolution\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SEARCH_PATHS = ['node_modules/@stackwright-pro/otters/src/', 'packages/otters/src/'];\n\nfunction resolveOtterDir(): string | null {\n const cwd = process.cwd();\n for (const relative of DEFAULT_SEARCH_PATHS) {\n const candidate = join(cwd, relative);\n try {\n lstatSync(candidate);\n return candidate;\n } catch {\n // Not found, try next\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Audit stream — SOC2 / FedRAMP / DoD ATO compliance\n// ---------------------------------------------------------------------------\n\n/**\n * Structured audit event parameters for an integrity failure.\n * Kept as a plain interface so callers can pass partial results without\n * constructing the full VerifyAllOttersResult.\n */\nexport interface IntegrityAuditEvent {\n otterDir: string;\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Emit a structured INTEGRITY_FAIL audit event to stderr.\n *\n * Writes a single line to process.stderr in the format:\n * INTEGRITY_FAIL {\"level\":\"AUDIT\",\"event\":\"INTEGRITY_FAIL\",...}\n *\n * The line prefix \"INTEGRITY_FAIL\" (without JSON) allows log shippers\n * (FluentBit, syslog, CloudWatch Logs, Splunk) to match and route the\n * event to a dedicated audit stream using a simple string filter, even\n * before attempting JSON parsing.\n *\n * Exported for unit testing. Do not call directly in production code —\n * use registerIntegrityTools() which calls this automatically.\n */\nexport function emitIntegrityAuditEvent(params: IntegrityAuditEvent): void {\n const record = JSON.stringify({\n level: 'AUDIT',\n event: 'INTEGRITY_FAIL',\n timestamp: new Date().toISOString(),\n source: 'stackwright_pro_verify_otter_integrity',\n otterDir: params.otterDir,\n failedCount: params.failed.length,\n unknownCount: params.unknown.length,\n failures: params.failed,\n unknown: params.unknown,\n });\n process.stderr.write(`INTEGRITY_FAIL ${record}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// MCP tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerIntegrityTools(server: McpServer): void {\n server.tool(\n 'stackwright_pro_verify_otter_integrity',\n 'Verify SHA-256 integrity of all Pro otter agent definitions. Call this at startup before discovering otters. Auto-discovers the otter directory from known paths. Returns verified/failed/unknown lists.',\n {},\n async () => {\n const resolved = resolveOtterDir();\n\n if (!resolved) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: true,\n message:\n 'Could not locate otter directory. Searched: ' + DEFAULT_SEARCH_PATHS.join(', '),\n }),\n },\n ],\n isError: true,\n };\n }\n\n const result = verifyAllOtters(resolved);\n\n const allGood = result.failed.length === 0 && result.unknown.length === 0;\n\n // Emit to dedicated audit stream for SOC2/FedRAMP/DoD ATO compliance\n if (!allGood) {\n emitIntegrityAuditEvent({\n otterDir: resolved,\n failed: result.failed,\n unknown: result.unknown,\n });\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n otterDir: resolved,\n totalCanonical: CANONICAL_CHECKSUMS.size,\n verifiedCount: result.verified.length,\n failedCount: result.failed.length,\n unknownCount: result.unknown.length,\n verified: result.verified,\n failed: result.failed,\n unknown: result.unknown,\n ...(allGood\n ? {}\n : {\n error:\n 'INTEGRITY CHECK FAILED: SHA-256 mismatch detected in otter agent definitions. Do not proceed — otter files may have been tampered with.',\n }),\n }),\n },\n ],\n isError: !allGood,\n };\n }\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/integrity.ts"],"sourcesContent":["/**\n * Otter Integrity Verification\n * ============================\n * Protects the Pro Otter Raft from disk-based prompt injection / jailbreak attacks.\n *\n * TypeScript port of python/src/stackwright_pro/raft/integrity.py — this lets\n * the MCP package verify otter files without a Python dependency.\n *\n * Certificate-pinned canonical checksums — hardcoded in the MCP package.\n *\n * These are NOT read from disk. An attacker who modifies otter JSON files\n * in @stackwright-pro/otters cannot also modify these constants without\n * compromising the separately-published @stackwright-pro/mcp package.\n *\n * To update: node scripts/sync-mcp-checksums.cjs\n * (reads from packages/otters/src/checksums.json, writes this file)\n */\nimport { createHash, timingSafeEqual } from 'crypto';\nimport { readFileSync, readdirSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n// ---------------------------------------------------------------------------\n// Certificate-pinned canonical checksums — frozen Map, immutable by design.\n// DO NOT read these from disk — that would defeat the entire purpose.\n// Object.freeze prevents property mutation at runtime; ReadonlyMap prevents\n// .set() / .delete() at compile time (belt-and-suspenders).\n// ---------------------------------------------------------------------------\n\nconst _checksums = new Map<string, string>([\n [\n 'stackwright-pro-api-otter.json',\n '9fbaed0ce6116b82d0289f24432037d04637c89b8e73062ed946e5d49b294734',\n ],\n [\n 'stackwright-pro-auth-otter.json',\n 'bf0e66e35d15ba818ba6ff1a007df34975565bacbb35cc0c80151fb1da13e573',\n ],\n [\n 'stackwright-pro-dashboard-otter.json',\n '89e81ac161147ab532034b40e4f7863dcd7d03ef33fd94c97d19c3e56fb91a9e',\n ],\n [\n 'stackwright-pro-data-otter.json',\n '3bdf0da3b90282a72598c242507fa6806dee9ac589ccdae5555dc1254a697373',\n ],\n [\n 'stackwright-pro-designer-otter.json',\n 'af09ac8f06385bdbac63e2820daa2ff7d38b8ff1ff383c161f07e3fb9d9359c5',\n ],\n [\n 'stackwright-pro-foreman-otter.json',\n '582a26766a5bc80ef9f3aef53e82794c7f47f618bd28e4e70583bfc2b569398c',\n ],\n [\n 'stackwright-pro-page-otter.json',\n '9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626',\n ],\n [\n 'stackwright-pro-theme-otter.json',\n '08bb04009fdfb8743b10ac4d503cbaddaf8d7c804ba9b606aaed9cc516fd8e93',\n ],\n [\n 'stackwright-pro-workflow-otter.json',\n 'c90d6773b2287aa9a640c2715ca0e75f44c13e99fddcfb89ced36603f38930ce',\n ],\n]);\nObject.freeze(_checksums);\nconst CANONICAL_CHECKSUMS: ReadonlyMap<string, string> = _checksums;\n\n// ---------------------------------------------------------------------------\n// Import-time format validation — malformed constants are a packaging bug,\n// not a runtime surprise. Fail fast, fail loud.\n// ---------------------------------------------------------------------------\n\nconst SHA256_HEX_RE = /^[0-9a-f]{64}$/;\n\nfor (const [name, digest] of CANONICAL_CHECKSUMS) {\n if (!SHA256_HEX_RE.test(digest)) {\n throw new Error(\n `Malformed SHA-256 in CANONICAL_CHECKSUMS for \"${name}\": ` +\n `expected 64 hex chars, got ${digest.length}: \"${digest}\"`\n );\n }\n}\n\n// 1 MB — generous headroom for agent definitions; anything larger is suspicious.\nconst MAX_OTTER_BYTES = 1 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Core functions (exported for direct testing — no MCP server needed)\n// ---------------------------------------------------------------------------\n\n/** Compute the hex-encoded SHA-256 digest of raw bytes. Pure, no I/O. */\nexport function computeSha256(data: Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/** Constant-time comparison of two hex digest strings. */\nfunction safeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));\n}\n\n// ---------------------------------------------------------------------------\n// Single-file verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyOtterFileResult {\n verified: boolean;\n filename: string;\n error?: string;\n}\n\n/**\n * Read a single otter JSON file, check its size, compute its SHA-256,\n * and constant-time compare against the canonical checksum.\n *\n * Single read → hash → decode. No TOCTOU window.\n */\nexport function verifyOtterFile(filePath: string): VerifyOtterFileResult {\n const filename = basename(filePath);\n\n // Fast-fail on unknown filenames before any I/O\n const expected = CANONICAL_CHECKSUMS.get(filename);\n if (expected === undefined) {\n return { verified: false, filename, error: `Unknown otter file: not in canonical set` };\n }\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based swaps)\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot stat file: ${msg}` };\n }\n\n if (stat.isSymbolicLink()) {\n return { verified: false, filename, error: 'Refusing to verify symlink' };\n }\n\n // Stat-based size pre-check — don't materialise oversized payloads\n const size = stat.size;\n\n if (size > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${size.toLocaleString()})`,\n };\n }\n\n // Single read — used for hashing and UTF-8 validation (zero TOCTOU window)\n let raw: Buffer;\n try {\n raw = readFileSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot read file: ${msg}` };\n }\n\n // Belt-and-suspenders: re-check length after read in case of a race\n if (raw.length > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit after read (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${raw.length.toLocaleString()})`,\n };\n }\n\n // Hash the raw bytes\n const actual = computeSha256(raw);\n\n // Constant-time comparison prevents timing-oracle attacks\n if (!safeEqual(actual, expected)) {\n return {\n verified: false,\n filename,\n error: `SHA-256 mismatch: expected ${expected.substring(0, 8)}…, got ${actual.substring(0, 8)}…`,\n };\n }\n\n // UTF-8 validation — binary injection guard\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n decoder.decode(raw);\n } catch {\n return {\n verified: false,\n filename,\n error: 'File is not valid UTF-8 — may be corrupted or contain binary injection',\n };\n }\n\n return { verified: true, filename };\n}\n\n// ---------------------------------------------------------------------------\n// Directory-level verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyAllOttersResult {\n verified: string[];\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Scan a directory for `*-otter.json` files, verify each one against\n * canonical checksums. Returns lists of verified, failed, and unknown files.\n */\nexport function verifyAllOtters(otterDir: string): VerifyAllOttersResult {\n // ---------------------------------------------------------------------------\n // Path traversal guard — reject any input containing \"..\" sequences before\n // any I/O. An attacker controlling this parameter could otherwise scan\n // directories outside the expected otter install locations.\n // ---------------------------------------------------------------------------\n if (/(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/.test(otterDir) || otterDir.includes('..')) {\n return {\n verified: [],\n failed: [\n {\n filename: '<directory>',\n error: `Security: path traversal sequence detected in otter directory parameter`,\n },\n ],\n unknown: [],\n };\n }\n\n const verified: string[] = [];\n const failed: Array<{ filename: string; error: string }> = [];\n const unknown: string[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(otterDir);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n verified: [],\n failed: [{ filename: '<directory>', error: `Cannot read directory: ${msg}` }],\n unknown: [],\n };\n }\n\n const otterFiles = entries.filter((f) => f.endsWith('-otter.json'));\n\n for (const filename of otterFiles) {\n const filePath = join(otterDir, filename);\n\n // Skip symlinks at the directory-scan level too\n try {\n if (lstatSync(filePath).isSymbolicLink()) {\n failed.push({ filename, error: 'Skipped: symlink' });\n continue;\n }\n } catch {\n // verifyOtterFile will handle stat errors\n }\n\n const result = verifyOtterFile(filePath);\n\n if (result.verified) {\n verified.push(result.filename);\n } else if (result.error?.startsWith('Unknown otter file')) {\n unknown.push(result.filename);\n } else {\n failed.push({ filename: result.filename, error: result.error ?? 'Unknown error' });\n }\n }\n\n // Check for missing canonical files — ones we expect but didn't find on disk\n for (const canonicalName of CANONICAL_CHECKSUMS.keys()) {\n if (!otterFiles.includes(canonicalName)) {\n failed.push({ filename: canonicalName, error: 'Missing from directory' });\n }\n }\n\n return { verified, failed, unknown };\n}\n\n// ---------------------------------------------------------------------------\n// Otter directory resolution\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SEARCH_PATHS = ['node_modules/@stackwright-pro/otters/src/', 'packages/otters/src/'];\n\nfunction resolveOtterDir(): string | null {\n const cwd = process.cwd();\n for (const relative of DEFAULT_SEARCH_PATHS) {\n const candidate = join(cwd, relative);\n try {\n lstatSync(candidate);\n return candidate;\n } catch {\n // Not found, try next\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Audit stream — SOC2 / FedRAMP / DoD ATO compliance\n// ---------------------------------------------------------------------------\n\n/**\n * Structured audit event parameters for an integrity failure.\n * Kept as a plain interface so callers can pass partial results without\n * constructing the full VerifyAllOttersResult.\n */\nexport interface IntegrityAuditEvent {\n otterDir: string;\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Emit a structured INTEGRITY_FAIL audit event to stderr.\n *\n * Writes a single line to process.stderr in the format:\n * INTEGRITY_FAIL {\"level\":\"AUDIT\",\"event\":\"INTEGRITY_FAIL\",...}\n *\n * The line prefix \"INTEGRITY_FAIL\" (without JSON) allows log shippers\n * (FluentBit, syslog, CloudWatch Logs, Splunk) to match and route the\n * event to a dedicated audit stream using a simple string filter, even\n * before attempting JSON parsing.\n *\n * Exported for unit testing. Do not call directly in production code —\n * use registerIntegrityTools() which calls this automatically.\n */\nexport function emitIntegrityAuditEvent(params: IntegrityAuditEvent): void {\n const record = JSON.stringify({\n level: 'AUDIT',\n event: 'INTEGRITY_FAIL',\n timestamp: new Date().toISOString(),\n source: 'stackwright_pro_verify_otter_integrity',\n otterDir: params.otterDir,\n failedCount: params.failed.length,\n unknownCount: params.unknown.length,\n failures: params.failed,\n unknown: params.unknown,\n });\n process.stderr.write(`INTEGRITY_FAIL ${record}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// MCP tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerIntegrityTools(server: McpServer): void {\n server.tool(\n 'stackwright_pro_verify_otter_integrity',\n 'Verify SHA-256 integrity of all Pro otter agent definitions. Call this at startup before discovering otters. Auto-discovers the otter directory from known paths. Returns verified/failed/unknown lists.',\n {},\n async () => {\n const resolved = resolveOtterDir();\n\n if (!resolved) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: true,\n message:\n 'Could not locate otter directory. Searched: ' + DEFAULT_SEARCH_PATHS.join(', '),\n }),\n },\n ],\n isError: true,\n };\n }\n\n const result = verifyAllOtters(resolved);\n\n const allGood = result.failed.length === 0 && result.unknown.length === 0;\n\n // Emit to dedicated audit stream for SOC2/FedRAMP/DoD ATO compliance\n if (!allGood) {\n emitIntegrityAuditEvent({\n otterDir: resolved,\n failed: result.failed,\n unknown: result.unknown,\n });\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n otterDir: resolved,\n totalCanonical: CANONICAL_CHECKSUMS.size,\n verifiedCount: result.verified.length,\n failedCount: result.failed.length,\n unknownCount: result.unknown.length,\n verified: result.verified,\n failed: result.failed,\n unknown: result.unknown,\n ...(allGood\n ? {}\n : {\n error:\n 'INTEGRITY CHECK FAILED: SHA-256 mismatch detected in otter agent definitions. Do not proceed — otter files may have been tampered with.',\n }),\n }),\n },\n ],\n isError: !allGood,\n };\n }\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/integrity.mjs
CHANGED
|
@@ -17,7 +17,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
17
17
|
],
|
|
18
18
|
[
|
|
19
19
|
"stackwright-pro-data-otter.json",
|
|
20
|
-
"
|
|
20
|
+
"3bdf0da3b90282a72598c242507fa6806dee9ac589ccdae5555dc1254a697373"
|
|
21
21
|
],
|
|
22
22
|
[
|
|
23
23
|
"stackwright-pro-designer-otter.json",
|
|
@@ -29,11 +29,11 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
29
29
|
],
|
|
30
30
|
[
|
|
31
31
|
"stackwright-pro-page-otter.json",
|
|
32
|
-
"
|
|
32
|
+
"9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626"
|
|
33
33
|
],
|
|
34
34
|
[
|
|
35
35
|
"stackwright-pro-theme-otter.json",
|
|
36
|
-
"
|
|
36
|
+
"08bb04009fdfb8743b10ac4d503cbaddaf8d7c804ba9b606aaed9cc516fd8e93"
|
|
37
37
|
],
|
|
38
38
|
[
|
|
39
39
|
"stackwright-pro-workflow-otter.json",
|
package/dist/integrity.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/integrity.ts"],"sourcesContent":["/**\n * Otter Integrity Verification\n * ============================\n * Protects the Pro Otter Raft from disk-based prompt injection / jailbreak attacks.\n *\n * TypeScript port of python/src/stackwright_pro/raft/integrity.py — this lets\n * the MCP package verify otter files without a Python dependency.\n *\n * Certificate-pinned canonical checksums — hardcoded in the MCP package.\n *\n * These are NOT read from disk. An attacker who modifies otter JSON files\n * in @stackwright-pro/otters cannot also modify these constants without\n * compromising the separately-published @stackwright-pro/mcp package.\n *\n * To update: node scripts/sync-mcp-checksums.cjs\n * (reads from packages/otters/src/checksums.json, writes this file)\n */\nimport { createHash, timingSafeEqual } from 'crypto';\nimport { readFileSync, readdirSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n// ---------------------------------------------------------------------------\n// Certificate-pinned canonical checksums — frozen Map, immutable by design.\n// DO NOT read these from disk — that would defeat the entire purpose.\n// Object.freeze prevents property mutation at runtime; ReadonlyMap prevents\n// .set() / .delete() at compile time (belt-and-suspenders).\n// ---------------------------------------------------------------------------\n\nconst _checksums = new Map<string, string>([\n [\n 'stackwright-pro-api-otter.json',\n '9fbaed0ce6116b82d0289f24432037d04637c89b8e73062ed946e5d49b294734',\n ],\n [\n 'stackwright-pro-auth-otter.json',\n 'bf0e66e35d15ba818ba6ff1a007df34975565bacbb35cc0c80151fb1da13e573',\n ],\n [\n 'stackwright-pro-dashboard-otter.json',\n '89e81ac161147ab532034b40e4f7863dcd7d03ef33fd94c97d19c3e56fb91a9e',\n ],\n [\n 'stackwright-pro-data-otter.json',\n '4769c3fa9b358609159ef2f613ac9d40dc7cb439e916c3344b9500a75d6afa82',\n ],\n [\n 'stackwright-pro-designer-otter.json',\n 'af09ac8f06385bdbac63e2820daa2ff7d38b8ff1ff383c161f07e3fb9d9359c5',\n ],\n [\n 'stackwright-pro-foreman-otter.json',\n '582a26766a5bc80ef9f3aef53e82794c7f47f618bd28e4e70583bfc2b569398c',\n ],\n [\n 'stackwright-pro-page-otter.json',\n '2fd8fe6f799e3fb4659979b1c958c64c276632fcfe18f70371543dc8145baa1b',\n ],\n [\n 'stackwright-pro-theme-otter.json',\n 'bc5695ef28164516b3f677cab201de781663c354d187cea28cc41884f1d89414',\n ],\n [\n 'stackwright-pro-workflow-otter.json',\n 'c90d6773b2287aa9a640c2715ca0e75f44c13e99fddcfb89ced36603f38930ce',\n ],\n]);\nObject.freeze(_checksums);\nconst CANONICAL_CHECKSUMS: ReadonlyMap<string, string> = _checksums;\n\n// ---------------------------------------------------------------------------\n// Import-time format validation — malformed constants are a packaging bug,\n// not a runtime surprise. Fail fast, fail loud.\n// ---------------------------------------------------------------------------\n\nconst SHA256_HEX_RE = /^[0-9a-f]{64}$/;\n\nfor (const [name, digest] of CANONICAL_CHECKSUMS) {\n if (!SHA256_HEX_RE.test(digest)) {\n throw new Error(\n `Malformed SHA-256 in CANONICAL_CHECKSUMS for \"${name}\": ` +\n `expected 64 hex chars, got ${digest.length}: \"${digest}\"`\n );\n }\n}\n\n// 1 MB — generous headroom for agent definitions; anything larger is suspicious.\nconst MAX_OTTER_BYTES = 1 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Core functions (exported for direct testing — no MCP server needed)\n// ---------------------------------------------------------------------------\n\n/** Compute the hex-encoded SHA-256 digest of raw bytes. Pure, no I/O. */\nexport function computeSha256(data: Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/** Constant-time comparison of two hex digest strings. */\nfunction safeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));\n}\n\n// ---------------------------------------------------------------------------\n// Single-file verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyOtterFileResult {\n verified: boolean;\n filename: string;\n error?: string;\n}\n\n/**\n * Read a single otter JSON file, check its size, compute its SHA-256,\n * and constant-time compare against the canonical checksum.\n *\n * Single read → hash → decode. No TOCTOU window.\n */\nexport function verifyOtterFile(filePath: string): VerifyOtterFileResult {\n const filename = basename(filePath);\n\n // Fast-fail on unknown filenames before any I/O\n const expected = CANONICAL_CHECKSUMS.get(filename);\n if (expected === undefined) {\n return { verified: false, filename, error: `Unknown otter file: not in canonical set` };\n }\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based swaps)\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot stat file: ${msg}` };\n }\n\n if (stat.isSymbolicLink()) {\n return { verified: false, filename, error: 'Refusing to verify symlink' };\n }\n\n // Stat-based size pre-check — don't materialise oversized payloads\n const size = stat.size;\n\n if (size > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${size.toLocaleString()})`,\n };\n }\n\n // Single read — used for hashing and UTF-8 validation (zero TOCTOU window)\n let raw: Buffer;\n try {\n raw = readFileSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot read file: ${msg}` };\n }\n\n // Belt-and-suspenders: re-check length after read in case of a race\n if (raw.length > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit after read (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${raw.length.toLocaleString()})`,\n };\n }\n\n // Hash the raw bytes\n const actual = computeSha256(raw);\n\n // Constant-time comparison prevents timing-oracle attacks\n if (!safeEqual(actual, expected)) {\n return {\n verified: false,\n filename,\n error: `SHA-256 mismatch: expected ${expected.substring(0, 8)}…, got ${actual.substring(0, 8)}…`,\n };\n }\n\n // UTF-8 validation — binary injection guard\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n decoder.decode(raw);\n } catch {\n return {\n verified: false,\n filename,\n error: 'File is not valid UTF-8 — may be corrupted or contain binary injection',\n };\n }\n\n return { verified: true, filename };\n}\n\n// ---------------------------------------------------------------------------\n// Directory-level verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyAllOttersResult {\n verified: string[];\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Scan a directory for `*-otter.json` files, verify each one against\n * canonical checksums. Returns lists of verified, failed, and unknown files.\n */\nexport function verifyAllOtters(otterDir: string): VerifyAllOttersResult {\n // ---------------------------------------------------------------------------\n // Path traversal guard — reject any input containing \"..\" sequences before\n // any I/O. An attacker controlling this parameter could otherwise scan\n // directories outside the expected otter install locations.\n // ---------------------------------------------------------------------------\n if (/(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/.test(otterDir) || otterDir.includes('..')) {\n return {\n verified: [],\n failed: [\n {\n filename: '<directory>',\n error: `Security: path traversal sequence detected in otter directory parameter`,\n },\n ],\n unknown: [],\n };\n }\n\n const verified: string[] = [];\n const failed: Array<{ filename: string; error: string }> = [];\n const unknown: string[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(otterDir);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n verified: [],\n failed: [{ filename: '<directory>', error: `Cannot read directory: ${msg}` }],\n unknown: [],\n };\n }\n\n const otterFiles = entries.filter((f) => f.endsWith('-otter.json'));\n\n for (const filename of otterFiles) {\n const filePath = join(otterDir, filename);\n\n // Skip symlinks at the directory-scan level too\n try {\n if (lstatSync(filePath).isSymbolicLink()) {\n failed.push({ filename, error: 'Skipped: symlink' });\n continue;\n }\n } catch {\n // verifyOtterFile will handle stat errors\n }\n\n const result = verifyOtterFile(filePath);\n\n if (result.verified) {\n verified.push(result.filename);\n } else if (result.error?.startsWith('Unknown otter file')) {\n unknown.push(result.filename);\n } else {\n failed.push({ filename: result.filename, error: result.error ?? 'Unknown error' });\n }\n }\n\n // Check for missing canonical files — ones we expect but didn't find on disk\n for (const canonicalName of CANONICAL_CHECKSUMS.keys()) {\n if (!otterFiles.includes(canonicalName)) {\n failed.push({ filename: canonicalName, error: 'Missing from directory' });\n }\n }\n\n return { verified, failed, unknown };\n}\n\n// ---------------------------------------------------------------------------\n// Otter directory resolution\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SEARCH_PATHS = ['node_modules/@stackwright-pro/otters/src/', 'packages/otters/src/'];\n\nfunction resolveOtterDir(): string | null {\n const cwd = process.cwd();\n for (const relative of DEFAULT_SEARCH_PATHS) {\n const candidate = join(cwd, relative);\n try {\n lstatSync(candidate);\n return candidate;\n } catch {\n // Not found, try next\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Audit stream — SOC2 / FedRAMP / DoD ATO compliance\n// ---------------------------------------------------------------------------\n\n/**\n * Structured audit event parameters for an integrity failure.\n * Kept as a plain interface so callers can pass partial results without\n * constructing the full VerifyAllOttersResult.\n */\nexport interface IntegrityAuditEvent {\n otterDir: string;\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Emit a structured INTEGRITY_FAIL audit event to stderr.\n *\n * Writes a single line to process.stderr in the format:\n * INTEGRITY_FAIL {\"level\":\"AUDIT\",\"event\":\"INTEGRITY_FAIL\",...}\n *\n * The line prefix \"INTEGRITY_FAIL\" (without JSON) allows log shippers\n * (FluentBit, syslog, CloudWatch Logs, Splunk) to match and route the\n * event to a dedicated audit stream using a simple string filter, even\n * before attempting JSON parsing.\n *\n * Exported for unit testing. Do not call directly in production code —\n * use registerIntegrityTools() which calls this automatically.\n */\nexport function emitIntegrityAuditEvent(params: IntegrityAuditEvent): void {\n const record = JSON.stringify({\n level: 'AUDIT',\n event: 'INTEGRITY_FAIL',\n timestamp: new Date().toISOString(),\n source: 'stackwright_pro_verify_otter_integrity',\n otterDir: params.otterDir,\n failedCount: params.failed.length,\n unknownCount: params.unknown.length,\n failures: params.failed,\n unknown: params.unknown,\n });\n process.stderr.write(`INTEGRITY_FAIL ${record}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// MCP tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerIntegrityTools(server: McpServer): void {\n server.tool(\n 'stackwright_pro_verify_otter_integrity',\n 'Verify SHA-256 integrity of all Pro otter agent definitions. Call this at startup before discovering otters. Auto-discovers the otter directory from known paths. Returns verified/failed/unknown lists.',\n {},\n async () => {\n const resolved = resolveOtterDir();\n\n if (!resolved) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: true,\n message:\n 'Could not locate otter directory. Searched: ' + DEFAULT_SEARCH_PATHS.join(', '),\n }),\n },\n ],\n isError: true,\n };\n }\n\n const result = verifyAllOtters(resolved);\n\n const allGood = result.failed.length === 0 && result.unknown.length === 0;\n\n // Emit to dedicated audit stream for SOC2/FedRAMP/DoD ATO compliance\n if (!allGood) {\n emitIntegrityAuditEvent({\n otterDir: resolved,\n failed: result.failed,\n unknown: result.unknown,\n });\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n otterDir: resolved,\n totalCanonical: CANONICAL_CHECKSUMS.size,\n verifiedCount: result.verified.length,\n failedCount: result.failed.length,\n unknownCount: result.unknown.length,\n verified: result.verified,\n failed: result.failed,\n unknown: result.unknown,\n ...(allGood\n ? {}\n : {\n error:\n 'INTEGRITY CHECK FAILED: SHA-256 mismatch detected in otter agent definitions. Do not proceed — otter files may have been tampered with.',\n }),\n }),\n },\n ],\n isError: !allGood,\n };\n }\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/integrity.ts"],"sourcesContent":["/**\n * Otter Integrity Verification\n * ============================\n * Protects the Pro Otter Raft from disk-based prompt injection / jailbreak attacks.\n *\n * TypeScript port of python/src/stackwright_pro/raft/integrity.py — this lets\n * the MCP package verify otter files without a Python dependency.\n *\n * Certificate-pinned canonical checksums — hardcoded in the MCP package.\n *\n * These are NOT read from disk. An attacker who modifies otter JSON files\n * in @stackwright-pro/otters cannot also modify these constants without\n * compromising the separately-published @stackwright-pro/mcp package.\n *\n * To update: node scripts/sync-mcp-checksums.cjs\n * (reads from packages/otters/src/checksums.json, writes this file)\n */\nimport { createHash, timingSafeEqual } from 'crypto';\nimport { readFileSync, readdirSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n// ---------------------------------------------------------------------------\n// Certificate-pinned canonical checksums — frozen Map, immutable by design.\n// DO NOT read these from disk — that would defeat the entire purpose.\n// Object.freeze prevents property mutation at runtime; ReadonlyMap prevents\n// .set() / .delete() at compile time (belt-and-suspenders).\n// ---------------------------------------------------------------------------\n\nconst _checksums = new Map<string, string>([\n [\n 'stackwright-pro-api-otter.json',\n '9fbaed0ce6116b82d0289f24432037d04637c89b8e73062ed946e5d49b294734',\n ],\n [\n 'stackwright-pro-auth-otter.json',\n 'bf0e66e35d15ba818ba6ff1a007df34975565bacbb35cc0c80151fb1da13e573',\n ],\n [\n 'stackwright-pro-dashboard-otter.json',\n '89e81ac161147ab532034b40e4f7863dcd7d03ef33fd94c97d19c3e56fb91a9e',\n ],\n [\n 'stackwright-pro-data-otter.json',\n '3bdf0da3b90282a72598c242507fa6806dee9ac589ccdae5555dc1254a697373',\n ],\n [\n 'stackwright-pro-designer-otter.json',\n 'af09ac8f06385bdbac63e2820daa2ff7d38b8ff1ff383c161f07e3fb9d9359c5',\n ],\n [\n 'stackwright-pro-foreman-otter.json',\n '582a26766a5bc80ef9f3aef53e82794c7f47f618bd28e4e70583bfc2b569398c',\n ],\n [\n 'stackwright-pro-page-otter.json',\n '9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626',\n ],\n [\n 'stackwright-pro-theme-otter.json',\n '08bb04009fdfb8743b10ac4d503cbaddaf8d7c804ba9b606aaed9cc516fd8e93',\n ],\n [\n 'stackwright-pro-workflow-otter.json',\n 'c90d6773b2287aa9a640c2715ca0e75f44c13e99fddcfb89ced36603f38930ce',\n ],\n]);\nObject.freeze(_checksums);\nconst CANONICAL_CHECKSUMS: ReadonlyMap<string, string> = _checksums;\n\n// ---------------------------------------------------------------------------\n// Import-time format validation — malformed constants are a packaging bug,\n// not a runtime surprise. Fail fast, fail loud.\n// ---------------------------------------------------------------------------\n\nconst SHA256_HEX_RE = /^[0-9a-f]{64}$/;\n\nfor (const [name, digest] of CANONICAL_CHECKSUMS) {\n if (!SHA256_HEX_RE.test(digest)) {\n throw new Error(\n `Malformed SHA-256 in CANONICAL_CHECKSUMS for \"${name}\": ` +\n `expected 64 hex chars, got ${digest.length}: \"${digest}\"`\n );\n }\n}\n\n// 1 MB — generous headroom for agent definitions; anything larger is suspicious.\nconst MAX_OTTER_BYTES = 1 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Core functions (exported for direct testing — no MCP server needed)\n// ---------------------------------------------------------------------------\n\n/** Compute the hex-encoded SHA-256 digest of raw bytes. Pure, no I/O. */\nexport function computeSha256(data: Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/** Constant-time comparison of two hex digest strings. */\nfunction safeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));\n}\n\n// ---------------------------------------------------------------------------\n// Single-file verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyOtterFileResult {\n verified: boolean;\n filename: string;\n error?: string;\n}\n\n/**\n * Read a single otter JSON file, check its size, compute its SHA-256,\n * and constant-time compare against the canonical checksum.\n *\n * Single read → hash → decode. No TOCTOU window.\n */\nexport function verifyOtterFile(filePath: string): VerifyOtterFileResult {\n const filename = basename(filePath);\n\n // Fast-fail on unknown filenames before any I/O\n const expected = CANONICAL_CHECKSUMS.get(filename);\n if (expected === undefined) {\n return { verified: false, filename, error: `Unknown otter file: not in canonical set` };\n }\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based swaps)\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot stat file: ${msg}` };\n }\n\n if (stat.isSymbolicLink()) {\n return { verified: false, filename, error: 'Refusing to verify symlink' };\n }\n\n // Stat-based size pre-check — don't materialise oversized payloads\n const size = stat.size;\n\n if (size > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${size.toLocaleString()})`,\n };\n }\n\n // Single read — used for hashing and UTF-8 validation (zero TOCTOU window)\n let raw: Buffer;\n try {\n raw = readFileSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot read file: ${msg}` };\n }\n\n // Belt-and-suspenders: re-check length after read in case of a race\n if (raw.length > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit after read (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${raw.length.toLocaleString()})`,\n };\n }\n\n // Hash the raw bytes\n const actual = computeSha256(raw);\n\n // Constant-time comparison prevents timing-oracle attacks\n if (!safeEqual(actual, expected)) {\n return {\n verified: false,\n filename,\n error: `SHA-256 mismatch: expected ${expected.substring(0, 8)}…, got ${actual.substring(0, 8)}…`,\n };\n }\n\n // UTF-8 validation — binary injection guard\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n decoder.decode(raw);\n } catch {\n return {\n verified: false,\n filename,\n error: 'File is not valid UTF-8 — may be corrupted or contain binary injection',\n };\n }\n\n return { verified: true, filename };\n}\n\n// ---------------------------------------------------------------------------\n// Directory-level verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyAllOttersResult {\n verified: string[];\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Scan a directory for `*-otter.json` files, verify each one against\n * canonical checksums. Returns lists of verified, failed, and unknown files.\n */\nexport function verifyAllOtters(otterDir: string): VerifyAllOttersResult {\n // ---------------------------------------------------------------------------\n // Path traversal guard — reject any input containing \"..\" sequences before\n // any I/O. An attacker controlling this parameter could otherwise scan\n // directories outside the expected otter install locations.\n // ---------------------------------------------------------------------------\n if (/(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/.test(otterDir) || otterDir.includes('..')) {\n return {\n verified: [],\n failed: [\n {\n filename: '<directory>',\n error: `Security: path traversal sequence detected in otter directory parameter`,\n },\n ],\n unknown: [],\n };\n }\n\n const verified: string[] = [];\n const failed: Array<{ filename: string; error: string }> = [];\n const unknown: string[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(otterDir);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n verified: [],\n failed: [{ filename: '<directory>', error: `Cannot read directory: ${msg}` }],\n unknown: [],\n };\n }\n\n const otterFiles = entries.filter((f) => f.endsWith('-otter.json'));\n\n for (const filename of otterFiles) {\n const filePath = join(otterDir, filename);\n\n // Skip symlinks at the directory-scan level too\n try {\n if (lstatSync(filePath).isSymbolicLink()) {\n failed.push({ filename, error: 'Skipped: symlink' });\n continue;\n }\n } catch {\n // verifyOtterFile will handle stat errors\n }\n\n const result = verifyOtterFile(filePath);\n\n if (result.verified) {\n verified.push(result.filename);\n } else if (result.error?.startsWith('Unknown otter file')) {\n unknown.push(result.filename);\n } else {\n failed.push({ filename: result.filename, error: result.error ?? 'Unknown error' });\n }\n }\n\n // Check for missing canonical files — ones we expect but didn't find on disk\n for (const canonicalName of CANONICAL_CHECKSUMS.keys()) {\n if (!otterFiles.includes(canonicalName)) {\n failed.push({ filename: canonicalName, error: 'Missing from directory' });\n }\n }\n\n return { verified, failed, unknown };\n}\n\n// ---------------------------------------------------------------------------\n// Otter directory resolution\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SEARCH_PATHS = ['node_modules/@stackwright-pro/otters/src/', 'packages/otters/src/'];\n\nfunction resolveOtterDir(): string | null {\n const cwd = process.cwd();\n for (const relative of DEFAULT_SEARCH_PATHS) {\n const candidate = join(cwd, relative);\n try {\n lstatSync(candidate);\n return candidate;\n } catch {\n // Not found, try next\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Audit stream — SOC2 / FedRAMP / DoD ATO compliance\n// ---------------------------------------------------------------------------\n\n/**\n * Structured audit event parameters for an integrity failure.\n * Kept as a plain interface so callers can pass partial results without\n * constructing the full VerifyAllOttersResult.\n */\nexport interface IntegrityAuditEvent {\n otterDir: string;\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Emit a structured INTEGRITY_FAIL audit event to stderr.\n *\n * Writes a single line to process.stderr in the format:\n * INTEGRITY_FAIL {\"level\":\"AUDIT\",\"event\":\"INTEGRITY_FAIL\",...}\n *\n * The line prefix \"INTEGRITY_FAIL\" (without JSON) allows log shippers\n * (FluentBit, syslog, CloudWatch Logs, Splunk) to match and route the\n * event to a dedicated audit stream using a simple string filter, even\n * before attempting JSON parsing.\n *\n * Exported for unit testing. Do not call directly in production code —\n * use registerIntegrityTools() which calls this automatically.\n */\nexport function emitIntegrityAuditEvent(params: IntegrityAuditEvent): void {\n const record = JSON.stringify({\n level: 'AUDIT',\n event: 'INTEGRITY_FAIL',\n timestamp: new Date().toISOString(),\n source: 'stackwright_pro_verify_otter_integrity',\n otterDir: params.otterDir,\n failedCount: params.failed.length,\n unknownCount: params.unknown.length,\n failures: params.failed,\n unknown: params.unknown,\n });\n process.stderr.write(`INTEGRITY_FAIL ${record}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// MCP tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerIntegrityTools(server: McpServer): void {\n server.tool(\n 'stackwright_pro_verify_otter_integrity',\n 'Verify SHA-256 integrity of all Pro otter agent definitions. Call this at startup before discovering otters. Auto-discovers the otter directory from known paths. Returns verified/failed/unknown lists.',\n {},\n async () => {\n const resolved = resolveOtterDir();\n\n if (!resolved) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: true,\n message:\n 'Could not locate otter directory. Searched: ' + DEFAULT_SEARCH_PATHS.join(', '),\n }),\n },\n ],\n isError: true,\n };\n }\n\n const result = verifyAllOtters(resolved);\n\n const allGood = result.failed.length === 0 && result.unknown.length === 0;\n\n // Emit to dedicated audit stream for SOC2/FedRAMP/DoD ATO compliance\n if (!allGood) {\n emitIntegrityAuditEvent({\n otterDir: resolved,\n failed: result.failed,\n unknown: result.unknown,\n });\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n otterDir: resolved,\n totalCanonical: CANONICAL_CHECKSUMS.size,\n verifiedCount: result.verified.length,\n failedCount: result.failed.length,\n unknownCount: result.unknown.length,\n verified: result.verified,\n failed: result.failed,\n unknown: result.unknown,\n ...(allGood\n ? {}\n : {\n error:\n 'INTEGRITY CHECK FAILED: SHA-256 mismatch detected in otter agent definitions. Do not proceed — otter files may have been tampered with.',\n }),\n }),\n },\n ],\n isError: !allGood,\n };\n }\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.js
CHANGED
|
@@ -3878,7 +3878,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
3878
3878
|
],
|
|
3879
3879
|
[
|
|
3880
3880
|
"stackwright-pro-data-otter.json",
|
|
3881
|
-
"
|
|
3881
|
+
"3bdf0da3b90282a72598c242507fa6806dee9ac589ccdae5555dc1254a697373"
|
|
3882
3882
|
],
|
|
3883
3883
|
[
|
|
3884
3884
|
"stackwright-pro-designer-otter.json",
|
|
@@ -3890,11 +3890,11 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
3890
3890
|
],
|
|
3891
3891
|
[
|
|
3892
3892
|
"stackwright-pro-page-otter.json",
|
|
3893
|
-
"
|
|
3893
|
+
"9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626"
|
|
3894
3894
|
],
|
|
3895
3895
|
[
|
|
3896
3896
|
"stackwright-pro-theme-otter.json",
|
|
3897
|
-
"
|
|
3897
|
+
"08bb04009fdfb8743b10ac4d503cbaddaf8d7c804ba9b606aaed9cc516fd8e93"
|
|
3898
3898
|
],
|
|
3899
3899
|
[
|
|
3900
3900
|
"stackwright-pro-workflow-otter.json",
|
|
@@ -4631,7 +4631,7 @@ var package_default = {
|
|
|
4631
4631
|
"test:coverage": "vitest run --coverage"
|
|
4632
4632
|
},
|
|
4633
4633
|
name: "@stackwright-pro/mcp",
|
|
4634
|
-
version: "0.2.0-alpha.
|
|
4634
|
+
version: "0.2.0-alpha.54",
|
|
4635
4635
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
4636
4636
|
license: "SEE LICENSE IN LICENSE",
|
|
4637
4637
|
main: "./dist/server.js",
|