@intutic/mcp-governance-proxy 1.9.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +28 -0
  2. package/dist/anomaly/detectors.d.ts +129 -0
  3. package/dist/anomaly/detectors.d.ts.map +1 -0
  4. package/dist/anomaly/detectors.js +368 -0
  5. package/dist/anomaly/detectors.js.map +1 -0
  6. package/dist/anomaly/index.d.ts +52 -0
  7. package/dist/anomaly/index.d.ts.map +1 -0
  8. package/dist/anomaly/index.js +71 -0
  9. package/dist/anomaly/index.js.map +1 -0
  10. package/dist/config.d.ts +107 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +129 -6
  13. package/dist/config.js.map +1 -1
  14. package/dist/daemon/healthMonitor.d.ts.map +1 -1
  15. package/dist/daemon/healthMonitor.js +45 -46
  16. package/dist/daemon/healthMonitor.js.map +1 -1
  17. package/dist/daemon/index.d.ts +15 -1
  18. package/dist/daemon/index.d.ts.map +1 -1
  19. package/dist/daemon/index.js +78 -9
  20. package/dist/daemon/index.js.map +1 -1
  21. package/dist/daemon/instrumentation.d.ts +20 -0
  22. package/dist/daemon/instrumentation.d.ts.map +1 -0
  23. package/dist/daemon/instrumentation.js +61 -0
  24. package/dist/daemon/instrumentation.js.map +1 -0
  25. package/dist/daemon/policyCache.d.ts +83 -0
  26. package/dist/daemon/policyCache.d.ts.map +1 -1
  27. package/dist/daemon/policyCache.js +280 -11
  28. package/dist/daemon/policyCache.js.map +1 -1
  29. package/dist/daemon/statusReporter.d.ts +12 -0
  30. package/dist/daemon/statusReporter.d.ts.map +1 -0
  31. package/dist/daemon/statusReporter.js +130 -0
  32. package/dist/daemon/statusReporter.js.map +1 -0
  33. package/dist/daemon/telemetryBatcher.d.ts +3 -1
  34. package/dist/daemon/telemetryBatcher.d.ts.map +1 -1
  35. package/dist/daemon/telemetryBatcher.js +56 -13
  36. package/dist/daemon/telemetryBatcher.js.map +1 -1
  37. package/dist/daemonClient.d.ts +1 -1
  38. package/dist/daemonClient.d.ts.map +1 -1
  39. package/dist/daemonClient.js +0 -2
  40. package/dist/daemonClient.js.map +1 -1
  41. package/dist/dlp.d.ts +19 -0
  42. package/dist/dlp.d.ts.map +1 -1
  43. package/dist/dlp.js +95 -18
  44. package/dist/dlp.js.map +1 -1
  45. package/dist/emitter.d.ts +40 -2
  46. package/dist/emitter.d.ts.map +1 -1
  47. package/dist/emitter.js +27 -5
  48. package/dist/emitter.js.map +1 -1
  49. package/dist/index.js +13 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/injection.d.ts +46 -0
  52. package/dist/injection.d.ts.map +1 -0
  53. package/dist/injection.js +147 -0
  54. package/dist/injection.js.map +1 -0
  55. package/dist/interceptor.d.ts +131 -1
  56. package/dist/interceptor.d.ts.map +1 -1
  57. package/dist/interceptor.js +301 -5
  58. package/dist/interceptor.js.map +1 -1
  59. package/dist/policy.d.ts +110 -2
  60. package/dist/policy.d.ts.map +1 -1
  61. package/dist/policy.js +206 -5
  62. package/dist/policy.js.map +1 -1
  63. package/dist/proxy.d.ts +205 -0
  64. package/dist/proxy.d.ts.map +1 -1
  65. package/dist/proxy.js +572 -65
  66. package/dist/proxy.js.map +1 -1
  67. package/dist/remoteBridge.d.ts +51 -0
  68. package/dist/remoteBridge.d.ts.map +1 -0
  69. package/dist/remoteBridge.js +182 -0
  70. package/dist/remoteBridge.js.map +1 -0
  71. package/dist/session.d.ts +158 -0
  72. package/dist/session.d.ts.map +1 -0
  73. package/dist/session.js +206 -0
  74. package/dist/session.js.map +1 -0
  75. package/dist/sessionScope.d.ts +51 -0
  76. package/dist/sessionScope.d.ts.map +1 -0
  77. package/dist/sessionScope.js +94 -0
  78. package/dist/sessionScope.js.map +1 -0
  79. package/dist/sessionStore.d.ts +69 -0
  80. package/dist/sessionStore.d.ts.map +1 -0
  81. package/dist/sessionStore.js +143 -0
  82. package/dist/sessionStore.js.map +1 -0
  83. package/dist/tofu.d.ts +112 -0
  84. package/dist/tofu.d.ts.map +1 -0
  85. package/dist/tofu.js +180 -0
  86. package/dist/tofu.js.map +1 -0
  87. package/dist/toolPoison.d.ts +32 -0
  88. package/dist/toolPoison.d.ts.map +1 -0
  89. package/dist/toolPoison.js +81 -0
  90. package/dist/toolPoison.js.map +1 -0
  91. package/dist/valkeyErrors.d.ts +17 -0
  92. package/dist/valkeyErrors.d.ts.map +1 -0
  93. package/dist/valkeyErrors.js +24 -0
  94. package/dist/valkeyErrors.js.map +1 -0
  95. package/dist/wasm/context.d.ts +57 -0
  96. package/dist/wasm/context.d.ts.map +1 -0
  97. package/dist/wasm/context.js +59 -0
  98. package/dist/wasm/context.js.map +1 -0
  99. package/dist/wasm/hostImports.d.ts +43 -0
  100. package/dist/wasm/hostImports.d.ts.map +1 -0
  101. package/dist/wasm/hostImports.js +158 -0
  102. package/dist/wasm/hostImports.js.map +1 -0
  103. package/dist/wasm/loader.d.ts +95 -0
  104. package/dist/wasm/loader.d.ts.map +1 -0
  105. package/dist/wasm/loader.js +198 -0
  106. package/dist/wasm/loader.js.map +1 -0
  107. package/dist/wasm/referencedFiles.d.ts +115 -0
  108. package/dist/wasm/referencedFiles.d.ts.map +1 -0
  109. package/dist/wasm/referencedFiles.js +366 -0
  110. package/dist/wasm/referencedFiles.js.map +1 -0
  111. package/dist/wasm/runner.d.ts +91 -0
  112. package/dist/wasm/runner.d.ts.map +1 -0
  113. package/dist/wasm/runner.js +308 -0
  114. package/dist/wasm/runner.js.map +1 -0
  115. package/dist/wasm/watch.d.ts +5 -0
  116. package/dist/wasm/watch.d.ts.map +1 -0
  117. package/dist/wasm/watch.js +54 -0
  118. package/dist/wasm/watch.js.map +1 -0
  119. package/dist/wasm/worker.d.ts +39 -0
  120. package/dist/wasm/worker.d.ts.map +1 -0
  121. package/dist/wasm/worker.js +245 -0
  122. package/dist/wasm/worker.js.map +1 -0
  123. package/package.json +14 -4
package/dist/tofu.d.ts ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * tofu.ts — Server-level trust-on-first-use (TOFU) pinning of MCP tool
3
+ * definitions.
4
+ *
5
+ * Direct TypeScript port of `packages/proxy/src/tool_pin.rs`'s canonicalization
6
+ * and hashing rule, extended from "one fingerprint per request's tool array"
7
+ * to "one fingerprint per {workspace, server} pair" — the shape this proxy
8
+ * needs, since each `McpGovernanceProxy` process fronts exactly one real MCP
9
+ * server (identified by `--server-name`, see config.ts) rather than a
10
+ * multi-provider gateway request.
11
+ *
12
+ * ## What this catches, and what it does not
13
+ *
14
+ * A tool-providing server declares its tools, and those declarations enter
15
+ * the model's context as instructions it will follow. Nothing in the MCP
16
+ * specification requires re-approval when a declaration changes, so a server
17
+ * can ship benign tools, wait to be trusted, and later serve altered ones —
18
+ * the "rug pull". The first tool list a workspace sees from a given server is
19
+ * pinned locally; every later `tools/list` response is compared against it.
20
+ *
21
+ * Straight from `tool_pin.rs`'s own doc comment, because it is still exactly
22
+ * true here: **TOFU is change-detection, not content-detection.** A server
23
+ * that is malicious from the very first `tools/list` response has its payload
24
+ * adopted as the trusted baseline — this module cannot help with that. It
25
+ * only catches a LATER definition change.
26
+ *
27
+ * @module
28
+ */
29
+ /** The subset of a `tools/list` tool entry this module hashes. */
30
+ export interface ToolDefinition {
31
+ name?: unknown;
32
+ description?: unknown;
33
+ inputSchema?: unknown;
34
+ }
35
+ /**
36
+ * Serialise with object keys sorted, so a re-serialised schema with the same
37
+ * content hashes identically regardless of property insertion order. Array
38
+ * order IS preserved — `required`/`enum` are ordered lists, and a rule that
39
+ * treated them as sets would let a real change (reordered enum values,
40
+ * different required-field order) hide from the pin. Direct port of
41
+ * `tool_pin.rs`'s `canonical_json`.
42
+ */
43
+ export declare function canonicalJson(value: unknown): string;
44
+ /**
45
+ * Canonical signature of one server's tool list — SHA-256 over the sorted,
46
+ * canonicalised `{name, description, inputSchema}` triple of every tool.
47
+ *
48
+ * Empty string when the server declares no tools, which the caller uses to
49
+ * skip TOFU entirely rather than pinning an absence (mirrors `tool_pin.rs`'s
50
+ * `signature`: "no tools yields no signature").
51
+ *
52
+ * Sorted by the full canonical per-tool string (name first, so this sorts by
53
+ * name in practice) so that a server reordering its list does not read as a
54
+ * change — reordering is not an attack, and a false positive here interrupts
55
+ * real work for nothing.
56
+ */
57
+ export declare function computeToolsFingerprint(tools: readonly ToolDefinition[]): string;
58
+ interface PinRecord {
59
+ fingerprint: string;
60
+ pinnedAt: string;
61
+ }
62
+ /** Reads the stored pin for a {workspace, server} pair, or `null` if none exists. */
63
+ export declare function loadPin(workspaceId: string, serverName: string): Promise<PinRecord | null>;
64
+ /**
65
+ * Persists a pin. Called once, on first contact — a pin is never silently
66
+ * overwritten by a later mismatch (that would defeat the point: the whole
67
+ * mechanism exists to keep comparing against the FIRST trusted definition,
68
+ * the same "SET NX, first definition wins" rule `tool_pin.rs` documents).
69
+ */
70
+ export declare function savePin(workspaceId: string, serverName: string, fingerprint: string): Promise<void>;
71
+ export type TofuOutcome = {
72
+ status: 'skipped';
73
+ } | {
74
+ status: 'first_contact';
75
+ fingerprint: string;
76
+ } | {
77
+ status: 'match';
78
+ fingerprint: string;
79
+ } | {
80
+ status: 'mismatch';
81
+ fingerprint: string;
82
+ previousFingerprint: string;
83
+ };
84
+ /**
85
+ * Runs the full TOFU check for one server's `tools/list` response: compute
86
+ * the fingerprint, load any existing pin, and report first-contact / match /
87
+ * mismatch. Does not itself decide whether a mismatch blocks the request —
88
+ * that is `mcpProxyFailBehavior`'s call, made by the caller (proxy.ts), the
89
+ * same fail-open/fail-closed mechanism `config.ts`/`interceptor.ts` already
90
+ * use elsewhere in this package.
91
+ */
92
+ export declare function checkTofu(workspaceId: string, serverName: string, tools: readonly ToolDefinition[]): Promise<TofuOutcome>;
93
+ /**
94
+ * Decides whether a `TofuOutcome` should block the `tools/list` request that
95
+ * produced it, given the workspace's `mcpProxyFailBehavior` — the SAME
96
+ * `failOpen` boolean `config.ts`/`interceptor.ts` already thread through for
97
+ * every other governance check in this package, not a new mechanism.
98
+ *
99
+ * A pure function, deliberately separated from `checkTofu`'s file I/O and
100
+ * from `proxy.ts`'s stdout-writing caller, so the fail-open/fail-closed
101
+ * branching itself — the part with real behavioural consequences — can be
102
+ * tested without spawning a child process or touching disk.
103
+ *
104
+ * Only `'mismatch'` can ever block: `'skipped'` (no tools declared) and
105
+ * `'first_contact'`/`'match'` are never a reason to refuse a request.
106
+ */
107
+ export declare function decideTofuAction(outcome: TofuOutcome, serverName: string, failOpen: boolean): {
108
+ block: boolean;
109
+ reason?: string;
110
+ };
111
+ export {};
112
+ //# sourceMappingURL=tofu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tofu.d.ts","sourceRoot":"","sources":["../src/tofu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAgBpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAiBhF;AAID,UAAU,SAAS;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAqBD,qFAAqF;AACrF,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAehG;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzG;AAID,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5E;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,OAAO,CAAC,WAAW,CAAC,CAetB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,OAAO,GAChB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBrC"}
package/dist/tofu.js ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * tofu.ts — Server-level trust-on-first-use (TOFU) pinning of MCP tool
3
+ * definitions.
4
+ *
5
+ * Direct TypeScript port of `packages/proxy/src/tool_pin.rs`'s canonicalization
6
+ * and hashing rule, extended from "one fingerprint per request's tool array"
7
+ * to "one fingerprint per {workspace, server} pair" — the shape this proxy
8
+ * needs, since each `McpGovernanceProxy` process fronts exactly one real MCP
9
+ * server (identified by `--server-name`, see config.ts) rather than a
10
+ * multi-provider gateway request.
11
+ *
12
+ * ## What this catches, and what it does not
13
+ *
14
+ * A tool-providing server declares its tools, and those declarations enter
15
+ * the model's context as instructions it will follow. Nothing in the MCP
16
+ * specification requires re-approval when a declaration changes, so a server
17
+ * can ship benign tools, wait to be trusted, and later serve altered ones —
18
+ * the "rug pull". The first tool list a workspace sees from a given server is
19
+ * pinned locally; every later `tools/list` response is compared against it.
20
+ *
21
+ * Straight from `tool_pin.rs`'s own doc comment, because it is still exactly
22
+ * true here: **TOFU is change-detection, not content-detection.** A server
23
+ * that is malicious from the very first `tools/list` response has its payload
24
+ * adopted as the trusted baseline — this module cannot help with that. It
25
+ * only catches a LATER definition change.
26
+ *
27
+ * @module
28
+ */
29
+ import * as node_crypto from 'node:crypto';
30
+ import * as node_fs from 'node:fs/promises';
31
+ import * as node_os from 'node:os';
32
+ import * as node_path from 'node:path';
33
+ /**
34
+ * Serialise with object keys sorted, so a re-serialised schema with the same
35
+ * content hashes identically regardless of property insertion order. Array
36
+ * order IS preserved — `required`/`enum` are ordered lists, and a rule that
37
+ * treated them as sets would let a real change (reordered enum values,
38
+ * different required-field order) hide from the pin. Direct port of
39
+ * `tool_pin.rs`'s `canonical_json`.
40
+ */
41
+ export function canonicalJson(value) {
42
+ if (Array.isArray(value)) {
43
+ return `[${value.map(canonicalJson).join(',')}]`;
44
+ }
45
+ if (value !== null && typeof value === 'object') {
46
+ const keys = Object.keys(value).sort();
47
+ const inner = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`);
48
+ return `{${inner.join(',')}}`;
49
+ }
50
+ // Scalars (string/number/boolean/null) and undefined: JSON.stringify(undefined)
51
+ // is the string 'undefined' has no JSON form; normalise it to 'null' so a
52
+ // missing optional field never produces an unparseable canonical fragment.
53
+ const serialised = JSON.stringify(value);
54
+ return serialised === undefined ? 'null' : serialised;
55
+ }
56
+ /**
57
+ * Canonical signature of one server's tool list — SHA-256 over the sorted,
58
+ * canonicalised `{name, description, inputSchema}` triple of every tool.
59
+ *
60
+ * Empty string when the server declares no tools, which the caller uses to
61
+ * skip TOFU entirely rather than pinning an absence (mirrors `tool_pin.rs`'s
62
+ * `signature`: "no tools yields no signature").
63
+ *
64
+ * Sorted by the full canonical per-tool string (name first, so this sorts by
65
+ * name in practice) so that a server reordering its list does not read as a
66
+ * change — reordering is not an attack, and a false positive here interrupts
67
+ * real work for nothing.
68
+ */
69
+ export function computeToolsFingerprint(tools) {
70
+ if (tools.length === 0)
71
+ return '';
72
+ const UNIT_SEP = String.fromCharCode(0x1f);
73
+ const RECORD_SEP = String.fromCharCode(0x1e);
74
+ const canonical = tools.map((t) => {
75
+ const name = typeof t.name === 'string' ? t.name : '';
76
+ const description = typeof t.description === 'string' ? t.description : '';
77
+ const schema = t.inputSchema === undefined ? '' : canonicalJson(t.inputSchema);
78
+ return name + UNIT_SEP + description + UNIT_SEP + schema;
79
+ });
80
+ canonical.sort();
81
+ const hash = node_crypto.createHash('sha256');
82
+ hash.update(canonical.join(RECORD_SEP));
83
+ return hash.digest('hex');
84
+ }
85
+ function pinsDir() {
86
+ return node_path.join(node_os.homedir(), '.intutic', 'mcp-pins');
87
+ }
88
+ /** Filenames are flat, one per {workspace, server} pair — the same flat,
89
+ * id-keyed convention `~/.intutic/wasm/<ruleId>.wasm` and
90
+ * `<workspaceRoot>/.intutic/sops/<sopId>.md` already use, not a nested
91
+ * per-workspace directory tree. Unsafe filename characters (anything but
92
+ * alphanumerics, dot, dash, underscore) are replaced so a server name
93
+ * containing e.g. `/` cannot escape the pins directory. */
94
+ function sanitizeForFilename(raw) {
95
+ return raw.replace(/[^a-zA-Z0-9._-]/g, '_');
96
+ }
97
+ function pinFilePath(workspaceId, serverName) {
98
+ const fileName = `${sanitizeForFilename(workspaceId)}__${sanitizeForFilename(serverName)}.json`;
99
+ return node_path.join(pinsDir(), fileName);
100
+ }
101
+ /** Reads the stored pin for a {workspace, server} pair, or `null` if none exists. */
102
+ export async function loadPin(workspaceId, serverName) {
103
+ try {
104
+ const raw = await node_fs.readFile(pinFilePath(workspaceId, serverName), 'utf-8');
105
+ const parsed = JSON.parse(raw);
106
+ if (typeof parsed === 'object' &&
107
+ parsed !== null &&
108
+ typeof parsed.fingerprint === 'string') {
109
+ return parsed;
110
+ }
111
+ return null;
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ /**
118
+ * Persists a pin. Called once, on first contact — a pin is never silently
119
+ * overwritten by a later mismatch (that would defeat the point: the whole
120
+ * mechanism exists to keep comparing against the FIRST trusted definition,
121
+ * the same "SET NX, first definition wins" rule `tool_pin.rs` documents).
122
+ */
123
+ export async function savePin(workspaceId, serverName, fingerprint) {
124
+ const dir = pinsDir();
125
+ await node_fs.mkdir(dir, { recursive: true });
126
+ const record = { fingerprint, pinnedAt: new Date().toISOString() };
127
+ await node_fs.writeFile(pinFilePath(workspaceId, serverName), JSON.stringify(record, null, 2) + '\n', 'utf-8');
128
+ }
129
+ /**
130
+ * Runs the full TOFU check for one server's `tools/list` response: compute
131
+ * the fingerprint, load any existing pin, and report first-contact / match /
132
+ * mismatch. Does not itself decide whether a mismatch blocks the request —
133
+ * that is `mcpProxyFailBehavior`'s call, made by the caller (proxy.ts), the
134
+ * same fail-open/fail-closed mechanism `config.ts`/`interceptor.ts` already
135
+ * use elsewhere in this package.
136
+ */
137
+ export async function checkTofu(workspaceId, serverName, tools) {
138
+ const fingerprint = computeToolsFingerprint(tools);
139
+ if (fingerprint === '')
140
+ return { status: 'skipped' };
141
+ const existing = await loadPin(workspaceId, serverName);
142
+ if (!existing) {
143
+ await savePin(workspaceId, serverName, fingerprint);
144
+ return { status: 'first_contact', fingerprint };
145
+ }
146
+ if (existing.fingerprint === fingerprint) {
147
+ return { status: 'match', fingerprint };
148
+ }
149
+ return { status: 'mismatch', fingerprint, previousFingerprint: existing.fingerprint };
150
+ }
151
+ /**
152
+ * Decides whether a `TofuOutcome` should block the `tools/list` request that
153
+ * produced it, given the workspace's `mcpProxyFailBehavior` — the SAME
154
+ * `failOpen` boolean `config.ts`/`interceptor.ts` already thread through for
155
+ * every other governance check in this package, not a new mechanism.
156
+ *
157
+ * A pure function, deliberately separated from `checkTofu`'s file I/O and
158
+ * from `proxy.ts`'s stdout-writing caller, so the fail-open/fail-closed
159
+ * branching itself — the part with real behavioural consequences — can be
160
+ * tested without spawning a child process or touching disk.
161
+ *
162
+ * Only `'mismatch'` can ever block: `'skipped'` (no tools declared) and
163
+ * `'first_contact'`/`'match'` are never a reason to refuse a request.
164
+ */
165
+ export function decideTofuAction(outcome, serverName, failOpen) {
166
+ if (outcome.status !== 'mismatch')
167
+ return { block: false };
168
+ const reason = `MCP server "${serverName}"'s tool definitions changed since they were first pinned ` +
169
+ `(possible supply-chain "rug pull"). TOFU detects the change; it cannot tell whether ` +
170
+ `the new definitions are malicious. An operator can review the server and, if the ` +
171
+ `change is legitimate, remove its stale pin file under ~/.intutic/mcp-pins/ to re-pin.`;
172
+ if (failOpen)
173
+ return { block: false, reason };
174
+ return {
175
+ block: true,
176
+ reason: `${reason} Blocked by workspace policy (fail-closed mode). Contact your ` +
177
+ `administrator or update mcpProxyFailBehavior to open.`,
178
+ };
179
+ }
180
+ //# sourceMappingURL=tofu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tofu.js","sourceRoot":"","sources":["../src/tofu.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * toolPoison.ts — Tool-description poisoning patterns.
3
+ *
4
+ * Direct TypeScript port of `packages/proxy/src/tool_poison.rs`'s `scan`
5
+ * function and its seven named patterns (confirmed by reading that file
6
+ * directly). This is a SEPARATE pattern set from `injection.ts`, on purpose:
7
+ * `tool_poison.rs`'s own module doc records that it tried reusing
8
+ * `injection::scan` for this and measured that its five patterns match ZERO
9
+ * of the tool-poisoning payloads — they are tuned for conversational
10
+ * jailbreak phrasing ("ignore previous instructions"), while a poisoned tool
11
+ * description reads like documentation, not a jailbreak. `ToolPoisoningDetector`
12
+ * (Phase 2's `anomaly/detectors.ts`) is what consumes this module.
13
+ *
14
+ * The Rust source gates each pattern behind a cheap substring pre-check
15
+ * (`GATES`) before running the regex, measured to be ~1.8× faster than the
16
+ * regex alone on their corpus. That optimization is NOT ported here — it is
17
+ * a performance detail proven equivalent to the plain regex loop (the Rust
18
+ * module's own `gating_never_changes_a_verdict` test asserts exactly that
19
+ * equivalence), never a correctness one, and the volume of tool descriptions
20
+ * one MCP session evaluates is orders of magnitude below the Rust proxy's
21
+ * request-path corpus. The pattern SOURCE TEXT below is unchanged from the
22
+ * Rust `PATTERNS` array — only the verbatim regex text is what was actually
23
+ * measured, so preserving it exactly (not the gate) is what matters here.
24
+ *
25
+ * @module
26
+ */
27
+ /**
28
+ * Pattern names matching this tool description, deduplicated, empty when it
29
+ * looks like ordinary prose (the overwhelmingly common case).
30
+ */
31
+ export declare function scanToolDescription(description: string): string[];
32
+ //# sourceMappingURL=toolPoison.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolPoison.d.ts","sourceRoot":"","sources":["../src/toolPoison.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAkDH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjE"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * toolPoison.ts — Tool-description poisoning patterns.
3
+ *
4
+ * Direct TypeScript port of `packages/proxy/src/tool_poison.rs`'s `scan`
5
+ * function and its seven named patterns (confirmed by reading that file
6
+ * directly). This is a SEPARATE pattern set from `injection.ts`, on purpose:
7
+ * `tool_poison.rs`'s own module doc records that it tried reusing
8
+ * `injection::scan` for this and measured that its five patterns match ZERO
9
+ * of the tool-poisoning payloads — they are tuned for conversational
10
+ * jailbreak phrasing ("ignore previous instructions"), while a poisoned tool
11
+ * description reads like documentation, not a jailbreak. `ToolPoisoningDetector`
12
+ * (Phase 2's `anomaly/detectors.ts`) is what consumes this module.
13
+ *
14
+ * The Rust source gates each pattern behind a cheap substring pre-check
15
+ * (`GATES`) before running the regex, measured to be ~1.8× faster than the
16
+ * regex alone on their corpus. That optimization is NOT ported here — it is
17
+ * a performance detail proven equivalent to the plain regex loop (the Rust
18
+ * module's own `gating_never_changes_a_verdict` test asserts exactly that
19
+ * equivalence), never a correctness one, and the volume of tool descriptions
20
+ * one MCP session evaluates is orders of magnitude below the Rust proxy's
21
+ * request-path corpus. The pattern SOURCE TEXT below is unchanged from the
22
+ * Rust `PATTERNS` array — only the verbatim regex text is what was actually
23
+ * measured, so preserving it exactly (not the gate) is what matters here.
24
+ *
25
+ * @module
26
+ */
27
+ /**
28
+ * Ported verbatim from `tool_poison.rs`'s `PATTERNS` const. All bounded
29
+ * repetitions here are small fixed-width gaps (`{0,40}`, `{0,60}`, `{0,80}`)
30
+ * over a handful of alternations — the same "no wide bounded repetition, no
31
+ * RegexSet performance regression" shape `injection.ts` documents, not the
32
+ * `dlp.rs` shape that regressed.
33
+ */
34
+ const PATTERNS = [
35
+ {
36
+ name: 'hidden-instruction-block',
37
+ regex: /<\s*(important|secret|system|hidden|instructions?)\s*>/i,
38
+ },
39
+ {
40
+ name: 'conceal-from-user',
41
+ regex: /\bdo\s+not\s+(mention|tell|inform|reveal|disclose|show)\b[^.!?]{0,40}\buser\b|\bwithout\s+(informing|telling|notifying|alerting)\s+the\s+user\b/i,
42
+ },
43
+ {
44
+ name: 'sidechannel-exfil',
45
+ regex: /\b(pass|send|include|append|forward|provide)\b[^.!?]{0,60}\b(content|value|text)s?\s+of\b[^.!?]{0,40}(~\/|\.env|\.ssh|id_rsa|credential|secret|token|api[_ ]?key)/i,
46
+ },
47
+ {
48
+ name: 'read-sensitive-path',
49
+ // Rust's own comment: no `\b` before the alternation, because a word
50
+ // boundary cannot exist before a literal `.` — `\b\.env` matches
51
+ // nothing. Preserved exactly, including that absence.
52
+ regex: /\b(read|open|cat|load|access)\b[^.!?]{0,30}(~\/\.\w+|\.ssh\/|id_rsa|\.env\b|\.aws\/|mcp\.json|\/etc\/passwd)/i,
53
+ },
54
+ {
55
+ name: 'cross-tool-shadowing',
56
+ regex: /\buse\s+this\s+tool\s+instead\s+of\b|\bdo\s+not\s+use\s+the\s+\w+\s+tool\b|\bthis\s+tool\s+supersedes\b/i,
57
+ },
58
+ {
59
+ name: 'agent-directed-precondition',
60
+ regex: /\bbefore\s+(using|calling|invoking)\s+(this|the)\s+tool\b[^.!?]{0,80}\b(you\s+must|first\s+read|first\s+call)\b/i,
61
+ },
62
+ {
63
+ name: 'tool-scoped-instruction-override',
64
+ regex: /\b(ignore|disregard|override)\b[^.!?]{0,40}\b(other\s+tools?|previous\s+tools?|system\s+prompt)\b/i,
65
+ },
66
+ ];
67
+ /**
68
+ * Pattern names matching this tool description, deduplicated, empty when it
69
+ * looks like ordinary prose (the overwhelmingly common case).
70
+ */
71
+ export function scanToolDescription(description) {
72
+ if (!description)
73
+ return [];
74
+ const found = [];
75
+ for (const { name, regex } of PATTERNS) {
76
+ if (regex.test(description))
77
+ found.push(name);
78
+ }
79
+ return found;
80
+ }
81
+ //# sourceMappingURL=toolPoison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolPoison.js","sourceRoot":"","sources":["../src/toolPoison.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAOH;;;;;;GAMG;AACH,MAAM,QAAQ,GAAiC;IAC7C;QACE,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,yDAAyD;KACjE;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EACH,kJAAkJ;KACrJ;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EACH,oKAAoK;KACvK;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,qEAAqE;QACrE,iEAAiE;QACjE,sDAAsD;QACtD,KAAK,EAAE,+GAA+G;KACvH;IACD;QACE,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,0GAA0G;KAClH;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE,kHAAkH;KAC1H;IACD;QACE,IAAI,EAAE,kCAAkC;QACxC,KAAK,EAAE,oGAAoG;KAC5G;CACF,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,CAAC,WAAW;QAAE,OAAO,EAAE,CAAA;IAC3B,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/C,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * valkeyErrors.ts — one-line rendering of an ioredis connection error.
3
+ *
4
+ * Not just `err.message`: Node's dual-stack connect reports a refused Valkey
5
+ * as an AggregateError holding one child error per address family, and an
6
+ * AggregateError's own `.message` is the empty string — so the most common
7
+ * outage was logged `"err":""` and told an operator nothing. Message text
8
+ * only, so a credentialed VALKEY_URL cannot reach the log through an error
9
+ * property bag.
10
+ *
11
+ * Shared by the daemon's policy cache and telemetry batcher and by the stdio
12
+ * proxy's session store (Wave 5.3) — three Valkey clients, one description.
13
+ *
14
+ * @module
15
+ */
16
+ export declare function describeConnectionError(err: unknown): string;
17
+ //# sourceMappingURL=valkeyErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valkeyErrors.d.ts","sourceRoot":"","sources":["../src/valkeyErrors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAM5D"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * valkeyErrors.ts — one-line rendering of an ioredis connection error.
3
+ *
4
+ * Not just `err.message`: Node's dual-stack connect reports a refused Valkey
5
+ * as an AggregateError holding one child error per address family, and an
6
+ * AggregateError's own `.message` is the empty string — so the most common
7
+ * outage was logged `"err":""` and told an operator nothing. Message text
8
+ * only, so a credentialed VALKEY_URL cannot reach the log through an error
9
+ * property bag.
10
+ *
11
+ * Shared by the daemon's policy cache and telemetry batcher and by the stdio
12
+ * proxy's session store (Wave 5.3) — three Valkey clients, one description.
13
+ *
14
+ * @module
15
+ */
16
+ export function describeConnectionError(err) {
17
+ if (err instanceof AggregateError && err.errors.length > 0) {
18
+ return err.errors.map((e) => (e instanceof Error ? e.message : String(e))).join('; ');
19
+ }
20
+ if (err instanceof Error)
21
+ return err.message || err.name;
22
+ return String(err);
23
+ }
24
+ //# sourceMappingURL=valkeyErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valkeyErrors.js","sourceRoot":"","sources":["../src/valkeyErrors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAY;IAClD,IAAI,GAAG,YAAY,cAAc,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAChG,CAAC;IACD,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,CAAA;IACxD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;AACpB,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * wasm/context.ts — Builds the `RequestContext` JSON this proxy honestly has
3
+ * data for, matching the field NAMES `packages/proxy/src/wasm/context.rs`
4
+ * (and its AssemblyScript mirror, `packages/wasm-sdk/assembly/index.ts`)
5
+ * already define, so an existing WASM rule reads this exactly the way it
6
+ * reads the Rust LLM-traffic proxy's context.
7
+ *
8
+ * Deliberately incomplete: `harness`, `model`, every budget/cost field, every
9
+ * graph/node-identity field, and every SOP-declaration field
10
+ * (`denied_tools`/`plan_steps`/`scope_paths`/...) are OMITTED — not sent as
11
+ * `null`, simply absent — because this proxy has no honest value for them.
12
+ * AssemblyScript's `JSON.parse`-based `readContext` (assembly/index.ts)
13
+ * leaves a missing field at its class default (empty string / 0 / empty
14
+ * array), which is the "unknown" reading a well-written rule already has to
15
+ * handle for the Rust proxy's own `-1`-sentinelled fields — never a
16
+ * fabricated value.
17
+ *
18
+ * @module
19
+ */
20
+ import type { ToolsListEntry } from '../session.js';
21
+ export interface WasmContextInput {
22
+ sessionId: string;
23
+ workspaceId: string;
24
+ /** The post-curation `tools/list` this session last saw (SessionState). */
25
+ tools: readonly ToolsListEntry[];
26
+ /**
27
+ * A synthesized identifier for the CURRENT tool call — MCP has no
28
+ * server-assigned call id the way Anthropic's `tool_use_id` is; this
29
+ * proxy mints one for context-building purposes only, honestly labeled
30
+ * as this proxy's own synthesized value, not claimed to be anything the
31
+ * harness itself asserted.
32
+ */
33
+ toolCallId: string;
34
+ toolName: string;
35
+ toolArguments: unknown;
36
+ /** The prospective sequence (current call included) — same array Phase 2's detectors evaluate against. */
37
+ toolSequence: readonly string[];
38
+ callsLast60s: number;
39
+ /** This call's OWN DLP scan findings — see dlpEscalation's callers: by
40
+ * pipeline position, a non-empty result here would already have blocked
41
+ * the request at interceptor.ts's DLP step, so this is honestly almost
42
+ * always empty by construction, not a bug in this builder. */
43
+ dlpFindingDescriptions: readonly string[];
44
+ injectionFindings: readonly string[];
45
+ injectionSources: readonly string[];
46
+ corroboratingDetectors: number;
47
+ /** `undefined` when no `tools/list` response has been TOFU-checked yet this session. */
48
+ toolContractChanged: boolean | undefined;
49
+ }
50
+ /**
51
+ * Builds the plain object to `JSON.stringify` and hand to a WASM rule's
52
+ * `evaluate(offset, len)` export. Field names match `context.rs`/
53
+ * `assembly/index.ts` exactly; fields this proxy has no honest data for are
54
+ * not present as keys at all.
55
+ */
56
+ export declare function buildWasmContext(input: WasmContextInput): Record<string, unknown>;
57
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/wasm/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,2EAA2E;IAC3E,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IAChC;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,OAAO,CAAA;IACtB,0GAA0G;IAC1G,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB;;;mEAG+D;IAC/D,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,sBAAsB,EAAE,MAAM,CAAA;IAC9B,wFAAwF;IACxF,mBAAmB,EAAE,OAAO,GAAG,SAAS,CAAA;CACzC;AASD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyBjF"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * wasm/context.ts — Builds the `RequestContext` JSON this proxy honestly has
3
+ * data for, matching the field NAMES `packages/proxy/src/wasm/context.rs`
4
+ * (and its AssemblyScript mirror, `packages/wasm-sdk/assembly/index.ts`)
5
+ * already define, so an existing WASM rule reads this exactly the way it
6
+ * reads the Rust LLM-traffic proxy's context.
7
+ *
8
+ * Deliberately incomplete: `harness`, `model`, every budget/cost field, every
9
+ * graph/node-identity field, and every SOP-declaration field
10
+ * (`denied_tools`/`plan_steps`/`scope_paths`/...) are OMITTED — not sent as
11
+ * `null`, simply absent — because this proxy has no honest value for them.
12
+ * AssemblyScript's `JSON.parse`-based `readContext` (assembly/index.ts)
13
+ * leaves a missing field at its class default (empty string / 0 / empty
14
+ * array), which is the "unknown" reading a well-written rule already has to
15
+ * handle for the Rust proxy's own `-1`-sentinelled fields — never a
16
+ * fabricated value.
17
+ *
18
+ * @module
19
+ */
20
+ /** `(tool, count)` pairs, wire-shaped as 2-element arrays — see context.ts's module doc. */
21
+ function foldToolCallCounts(sequence) {
22
+ const counts = new Map();
23
+ for (const tool of sequence)
24
+ counts.set(tool, (counts.get(tool) ?? 0) + 1);
25
+ return Array.from(counts.entries());
26
+ }
27
+ /**
28
+ * Builds the plain object to `JSON.stringify` and hand to a WASM rule's
29
+ * `evaluate(offset, len)` export. Field names match `context.rs`/
30
+ * `assembly/index.ts` exactly; fields this proxy has no honest data for are
31
+ * not present as keys at all.
32
+ */
33
+ export function buildWasmContext(input) {
34
+ const ctx = {
35
+ session_id: input.sessionId,
36
+ workspace_id: input.workspaceId,
37
+ tools: input.tools.map((t) => ({ name: t.name, description: t.description ?? '' })),
38
+ tool_calls: [{ id: input.toolCallId, name: input.toolName, arguments: input.toolArguments ?? {} }],
39
+ tool_sequence: [...input.toolSequence],
40
+ tool_call_counts: foldToolCallCounts(input.toolSequence),
41
+ calls_last_60s: input.callsLast60s,
42
+ // Our own DLP scanner has no offset/length tracking (dlp.ts never
43
+ // recorded match spans), so `pattern_name` is the only field populated
44
+ // — mapped from the finding's human-readable description (the closest
45
+ // honest analogue this scanner has to a pattern identity).
46
+ // `category`/`action`/`offset`/`length` are left absent rather than
47
+ // fabricated; the AssemblyScript `DlpFinding` class defaults them to
48
+ // `""`/0, the same "unknown" reading every other absent field gets.
49
+ dlp_findings: input.dlpFindingDescriptions.map((description) => ({ pattern_name: description })),
50
+ injection_findings: [...input.injectionFindings],
51
+ injection_sources: [...input.injectionSources],
52
+ corroborating_detectors: input.corroboratingDetectors,
53
+ };
54
+ if (input.toolContractChanged !== undefined) {
55
+ ctx['tool_contract_changed'] = input.toolContractChanged;
56
+ }
57
+ return ctx;
58
+ }
59
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/wasm/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkCH,4FAA4F;AAC5F,SAAS,kBAAkB,CAAC,QAA2B;IACrD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,IAAI,IAAI,QAAQ;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC1E,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAuB;IACtD,MAAM,GAAG,GAA4B;QACnC,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,YAAY,EAAE,KAAK,CAAC,WAAW;QAC/B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC;QACnF,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;QAClG,aAAa,EAAE,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC;QACtC,gBAAgB,EAAE,kBAAkB,CAAC,KAAK,CAAC,YAAY,CAAC;QACxD,cAAc,EAAE,KAAK,CAAC,YAAY;QAClC,kEAAkE;QAClE,uEAAuE;QACvE,sEAAsE;QACtE,2DAA2D;QAC3D,oEAAoE;QACpE,qEAAqE;QACrE,oEAAoE;QACpE,YAAY,EAAE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAChG,kBAAkB,EAAE,CAAC,GAAG,KAAK,CAAC,iBAAiB,CAAC;QAChD,iBAAiB,EAAE,CAAC,GAAG,KAAK,CAAC,gBAAgB,CAAC;QAC9C,uBAAuB,EAAE,KAAK,CAAC,sBAAsB;KACtD,CAAA;IACD,IAAI,KAAK,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;QAC5C,GAAG,CAAC,uBAAuB,CAAC,GAAG,KAAK,CAAC,mBAAmB,CAAA;IAC1D,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * wasm/hostImports.ts — The 4 frozen host imports (`WASM_HOST_IMPORTS` from
3
+ * `@intutic/shared-types`), implemented for this proxy's `worker_threads`
4
+ * sandbox.
5
+ *
6
+ * Built from `packages/proxy/src/wasm/host.rs` (the Rust behavior each
7
+ * import must match) and from `packages/wasm-sdk/__tests__/generatedRules.test.ts`'s
8
+ * `HOST_IMPL` (the reference Node-side implementation the task named as the
9
+ * base to build from). `log_info`/`abort`/`trace` mirror `host.rs`'s
10
+ * tracing-log behavior, routed through this package's own `createStderrLogger`
11
+ * (never `console.log`/stdout — the same stdio-isolation rule every other
12
+ * module in this package follows). `read_referenced_file` resolves against
13
+ * the per-evaluation table the runner pre-reads through `referencedFiles.ts`
14
+ * (TD-441) — the port of `host.rs`'s `read_referenced_file_impl`, code for
15
+ * code: the read budget is charged before argument validation, a refusal is
16
+ * a value the rule can act on, and nothing here traps. With no table (no
17
+ * `INTUTIC_WASM_MANIFEST_ROOT`, or a rule that does not import the function)
18
+ * every call gets `ERR_REFUSED`, which is what the Rust proxy answers with no
19
+ * root configured.
20
+ *
21
+ * @module
22
+ */
23
+ import { ReferencedFiles } from './referencedFiles.js';
24
+ /** `referenced_files.rs`'s `ERR_REFUSED` (-2), kept exported here for the callers that imported it before the resolver existed. */
25
+ export declare const ERR_REFUSED = -2;
26
+ /** What one evaluation's host imports need beyond the guest memory. */
27
+ export interface HostImportState {
28
+ /** The pre-read table; `ReferencedFiles.empty()` refuses everything. */
29
+ files: ReferencedFiles;
30
+ /** Reads left this evaluation, decremented by every call. */
31
+ fileReadsRemaining: number;
32
+ }
33
+ export declare function newHostImportState(files?: ReferencedFiles): HostImportState;
34
+ /**
35
+ * Builds the `env.*` import object for one WASM instance. `getMemory` is a
36
+ * thunk rather than a captured value because the memory export does not
37
+ * exist until AFTER `WebAssembly.instantiate` returns, while the import
38
+ * object has to be supplied BEFORE it — the same ordering `host.rs` avoids
39
+ * entirely by reading `caller.get_export("memory")` inside each call; this
40
+ * is the Node-side equivalent of that lazy lookup.
41
+ */
42
+ export declare function createHostImports(getMemory: () => WebAssembly.Memory | undefined, state?: HostImportState): WebAssembly.ModuleImports;
43
+ //# sourceMappingURL=hostImports.d.ts.map