mcp-rce-guard 0.1.0

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/audit/log.d.ts +75 -0
  4. package/dist/audit/log.d.ts.map +1 -0
  5. package/dist/audit/log.js +191 -0
  6. package/dist/audit/log.js.map +1 -0
  7. package/dist/canary/tracker.d.ts +38 -0
  8. package/dist/canary/tracker.d.ts.map +1 -0
  9. package/dist/canary/tracker.js +40 -0
  10. package/dist/canary/tracker.js.map +1 -0
  11. package/dist/cli.d.ts +14 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +128 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/cve/replay.d.ts +44 -0
  16. package/dist/cve/replay.d.ts.map +1 -0
  17. package/dist/cve/replay.js +221 -0
  18. package/dist/cve/replay.js.map +1 -0
  19. package/dist/egress/policy.d.ts +27 -0
  20. package/dist/egress/policy.d.ts.map +1 -0
  21. package/dist/egress/policy.js +62 -0
  22. package/dist/egress/policy.js.map +1 -0
  23. package/dist/index.d.ts +27 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +38 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/isolation/cgroups.d.ts +20 -0
  28. package/dist/isolation/cgroups.d.ts.map +1 -0
  29. package/dist/isolation/cgroups.js +33 -0
  30. package/dist/isolation/cgroups.js.map +1 -0
  31. package/dist/isolation/landlock.d.ts +42 -0
  32. package/dist/isolation/landlock.d.ts.map +1 -0
  33. package/dist/isolation/landlock.js +67 -0
  34. package/dist/isolation/landlock.js.map +1 -0
  35. package/dist/isolation/platform.d.ts +27 -0
  36. package/dist/isolation/platform.d.ts.map +1 -0
  37. package/dist/isolation/platform.js +96 -0
  38. package/dist/isolation/platform.js.map +1 -0
  39. package/dist/isolation/sandbox-exec.d.ts +17 -0
  40. package/dist/isolation/sandbox-exec.d.ts.map +1 -0
  41. package/dist/isolation/sandbox-exec.js +58 -0
  42. package/dist/isolation/sandbox-exec.js.map +1 -0
  43. package/dist/normalize.d.ts +32 -0
  44. package/dist/normalize.d.ts.map +1 -0
  45. package/dist/normalize.js +66 -0
  46. package/dist/normalize.js.map +1 -0
  47. package/dist/server.d.ts +15 -0
  48. package/dist/server.d.ts.map +1 -0
  49. package/dist/server.js +152 -0
  50. package/dist/server.js.map +1 -0
  51. package/dist/state.d.ts +34 -0
  52. package/dist/state.d.ts.map +1 -0
  53. package/dist/state.js +104 -0
  54. package/dist/state.js.map +1 -0
  55. package/dist/tools/audit.d.ts +26 -0
  56. package/dist/tools/audit.d.ts.map +1 -0
  57. package/dist/tools/audit.js +97 -0
  58. package/dist/tools/audit.js.map +1 -0
  59. package/dist/tools/getAuditLog.d.ts +34 -0
  60. package/dist/tools/getAuditLog.d.ts.map +1 -0
  61. package/dist/tools/getAuditLog.js +65 -0
  62. package/dist/tools/getAuditLog.js.map +1 -0
  63. package/dist/tools/injectEgress.d.ts +21 -0
  64. package/dist/tools/injectEgress.d.ts.map +1 -0
  65. package/dist/tools/injectEgress.js +49 -0
  66. package/dist/tools/injectEgress.js.map +1 -0
  67. package/dist/tools/register.d.ts +16 -0
  68. package/dist/tools/register.d.ts.map +1 -0
  69. package/dist/tools/register.js +44 -0
  70. package/dist/tools/register.js.map +1 -0
  71. package/dist/tools/scanCve.d.ts +14 -0
  72. package/dist/tools/scanCve.d.ts.map +1 -0
  73. package/dist/tools/scanCve.js +29 -0
  74. package/dist/tools/scanCve.js.map +1 -0
  75. package/dist/tools/trackCanary.d.ts +23 -0
  76. package/dist/tools/trackCanary.d.ts.map +1 -0
  77. package/dist/tools/trackCanary.js +44 -0
  78. package/dist/tools/trackCanary.js.map +1 -0
  79. package/dist/trust-tiers.d.ts +18 -0
  80. package/dist/trust-tiers.d.ts.map +1 -0
  81. package/dist/trust-tiers.js +73 -0
  82. package/dist/trust-tiers.js.map +1 -0
  83. package/dist/types.d.ts +187 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +82 -0
  86. package/dist/types.js.map +1 -0
  87. package/dist/version.d.ts +7 -0
  88. package/dist/version.d.ts.map +1 -0
  89. package/dist/version.js +14 -0
  90. package/dist/version.js.map +1 -0
  91. package/package.json +74 -0
package/dist/cli.js ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mcp-rce-guard CLI.
4
+ *
5
+ * Subcommands:
6
+ * serve — run the stdio MCP server (same as mcp-rce-guard-server bin)
7
+ * platform — print detected isolation backend + capabilities
8
+ * scan-cve <command...> — run CVE replay fixtures against a candidate command
9
+ * audit-log [--since=..] — print recent audit-log entries
10
+ * policy <profile.json> — emit landlock + sandbox-exec + cgroups specs from a profile
11
+ */
12
+ import { Command } from "commander";
13
+ import { readFileSync } from "node:fs";
14
+ import { detectPlatform } from "./isolation/platform.js";
15
+ import { runReplay } from "./cve/replay.js";
16
+ import { readAudit, rotateAuditLog, auditLogPath } from "./audit/log.js";
17
+ import { buildLandlockPolicy } from "./isolation/landlock.js";
18
+ import { buildSandboxProfile } from "./isolation/sandbox-exec.js";
19
+ import { buildCgroupSpec } from "./isolation/cgroups.js";
20
+ import { IsolationProfileSchema } from "./types.js";
21
+ import { resolveProfile } from "./trust-tiers.js";
22
+ import { NAME, VERSION } from "./version.js";
23
+ import { main as serverMain } from "./server.js";
24
+ const program = new Command();
25
+ program
26
+ .name(NAME)
27
+ .description("Layer-3 RCE defense for MCP servers")
28
+ .version(VERSION);
29
+ program
30
+ .command("serve")
31
+ .description("Run the stdio MCP server")
32
+ .action(async () => {
33
+ await serverMain();
34
+ });
35
+ program
36
+ .command("platform")
37
+ .description("Print detected isolation backend + capabilities")
38
+ .action(() => {
39
+ const info = detectPlatform();
40
+ process.stdout.write(JSON.stringify(info, null, 2) + "\n");
41
+ });
42
+ program
43
+ .command("scan-cve")
44
+ .description("Run CVE replay fixtures against a candidate command")
45
+ .argument("<command...>", "command tokens (joined with spaces)")
46
+ .option("--cve <ids...>", "CVE IDs to run (default: all)")
47
+ .option("--timeout <ms>", "timeout in milliseconds", "30000")
48
+ .action((cmdTokens, opts) => {
49
+ const command = cmdTokens.join(" ");
50
+ const cveSet = opts.cve;
51
+ const timeoutMs = parseInt(opts.timeout, 10) || 30_000;
52
+ const result = runReplay(command, cveSet, timeoutMs);
53
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
54
+ process.exit(result.overall === "pass" ? 0 : 2);
55
+ });
56
+ program
57
+ .command("audit-log")
58
+ .description("Print recent audit-log entries")
59
+ .option("--since <iso>", "only entries since this ISO timestamp")
60
+ .option("--handle <h>", "filter by subprocess handle")
61
+ .option("--limit <n>", "max entries", "100")
62
+ .action(async (opts) => {
63
+ const filter = {
64
+ limit: parseInt(opts.limit, 10) || 100
65
+ };
66
+ if (opts.since)
67
+ filter.since = opts.since;
68
+ if (opts.handle)
69
+ filter.subprocessHandle = opts.handle;
70
+ const entries = await readAudit(filter);
71
+ for (const e of entries) {
72
+ process.stdout.write(JSON.stringify(e) + "\n");
73
+ }
74
+ });
75
+ program
76
+ .command("cleanup")
77
+ .description("Force-rotate the audit log (audit.log → audit.log.1, shifting backups)")
78
+ .action(async () => {
79
+ const result = await rotateAuditLog();
80
+ process.stdout.write(JSON.stringify({ ...result, path: auditLogPath() }, null, 2) + "\n");
81
+ });
82
+ program
83
+ .command("policy")
84
+ .description("Emit isolation specs from a profile JSON file")
85
+ .argument("<file>", "path to profile JSON")
86
+ .option("--tier <tier>", "trust tier base (LOW|MEDIUM|HIGH|CRITICAL)", "MEDIUM")
87
+ .option("--cgroup-name <name>", "cgroup name for cgroups spec", "mcp-rce-guard-default")
88
+ .action((file, opts) => {
89
+ const raw = readFileSync(file, "utf8");
90
+ const overrides = IsolationProfileSchema.partial().parse(JSON.parse(raw));
91
+ const tier = opts.tier;
92
+ const profile = resolveProfile(tier, overrides);
93
+ const out = {
94
+ tier,
95
+ profile,
96
+ landlock: buildLandlockPolicy(profile),
97
+ sandboxExec: buildSandboxProfile(profile),
98
+ cgroups: buildCgroupSpec(opts.cgroupName, profile)
99
+ };
100
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
101
+ });
102
+ const isDirect = (() => {
103
+ try {
104
+ return process.argv[1]?.endsWith("cli.js") ?? false;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ })();
110
+ if (isDirect) {
111
+ // No args → serve. Matches the behavior of `mcp-rce-guard-server` bin.
112
+ if (process.argv.length <= 2) {
113
+ serverMain().catch((e) => {
114
+ const msg = e instanceof Error ? e.message : String(e);
115
+ process.stderr.write(`[mcp-rce-guard] fatal: ${msg}\n`);
116
+ process.exit(1);
117
+ });
118
+ }
119
+ else {
120
+ program.parseAsync(process.argv).catch((e) => {
121
+ const msg = e instanceof Error ? e.message : String(e);
122
+ process.stderr.write(`[mcp-rce-guard cli] error: ${msg}\n`);
123
+ process.exit(1);
124
+ });
125
+ }
126
+ }
127
+ export { program };
128
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CVE replay engine.
3
+ *
4
+ * v0.1 ships built-in replay logic for three 2026-CVEs. Each fixture is a
5
+ * deterministic predicate that examines the candidate `targetServerCommand`
6
+ * + the registered subprocess profile and decides if the configuration
7
+ * would be vulnerable. A `pass` verdict means the configuration BLOCKS the
8
+ * vuln class; `fail` means the configuration is still vulnerable.
9
+ *
10
+ * The fixtures are *behavioral predicates*, not exploit payloads, so this
11
+ * package can ship without security-distribution-hygiene flags. The
12
+ * separate `mcp-rce-fixtures` package contains additional offline replay
13
+ * suites for CI use; v0.1 inlines just enough to make the tool useful
14
+ * without requiring the fixtures package.
15
+ *
16
+ * Sources:
17
+ * - MCP-SDK-RCE 22.04.2026 (Cloudflare patch in createMcpHandler)
18
+ * - CVE-2026-27124 (FastMCP Confused Deputy)
19
+ * - Nginx-MCP RCE CVSS 9.8
20
+ */
21
+ import type { CveId } from "../types.js";
22
+ export interface CveFixture {
23
+ id: CveId;
24
+ description: string;
25
+ predicate: (cmd: string) => CveFixtureResult;
26
+ }
27
+ export interface CveFixtureResult {
28
+ status: "pass" | "fail";
29
+ evidence: string;
30
+ }
31
+ export declare const BUILT_IN_FIXTURES: readonly CveFixture[];
32
+ export declare function getFixture(id: CveId): CveFixture | undefined;
33
+ export interface ReplayPerCve {
34
+ id: CveId;
35
+ status: "pass" | "fail" | "skipped";
36
+ durationMs: number;
37
+ evidence?: string;
38
+ }
39
+ export interface ReplayResult {
40
+ overall: "pass" | "fail";
41
+ perCve: ReplayPerCve[];
42
+ }
43
+ export declare function runReplay(targetCommand: string, cveSet?: CveId[], timeoutMs?: number): ReplayResult;
44
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/cve/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,KAAK,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoGD,eAAO,MAAM,iBAAiB,EAAE,SAAS,UAAU,EAqElD,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,CAE5D;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,wBAAgB,SAAS,CACvB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,KAAK,EAAE,EAChB,SAAS,SAAS,GACjB,YAAY,CAqCd"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * CVE replay engine.
3
+ *
4
+ * v0.1 ships built-in replay logic for three 2026-CVEs. Each fixture is a
5
+ * deterministic predicate that examines the candidate `targetServerCommand`
6
+ * + the registered subprocess profile and decides if the configuration
7
+ * would be vulnerable. A `pass` verdict means the configuration BLOCKS the
8
+ * vuln class; `fail` means the configuration is still vulnerable.
9
+ *
10
+ * The fixtures are *behavioral predicates*, not exploit payloads, so this
11
+ * package can ship without security-distribution-hygiene flags. The
12
+ * separate `mcp-rce-fixtures` package contains additional offline replay
13
+ * suites for CI use; v0.1 inlines just enough to make the tool useful
14
+ * without requiring the fixtures package.
15
+ *
16
+ * Sources:
17
+ * - MCP-SDK-RCE 22.04.2026 (Cloudflare patch in createMcpHandler)
18
+ * - CVE-2026-27124 (FastMCP Confused Deputy)
19
+ * - Nginx-MCP RCE CVSS 9.8
20
+ */
21
+ import { normalizeArg } from "../normalize.js";
22
+ /**
23
+ * Allowlist of variable names that are considered safe to embed verbatim in
24
+ * a target command. Anything outside this allowlist gets flagged as
25
+ * potential confused-deputy material by the cve-2026-27124 fixture.
26
+ *
27
+ * Reviewer S1024 F4-Sub-b: previous code used a narrow blocklist
28
+ * (`API_KEY|SECRET|TOKEN|PASSWORD|BEARER`) and missed obvious credentials
29
+ * like `AUTH_HEADER`, `COOKIE`, `SESSION_ID`, `PRIVATE_KEY`, `REFRESH_TOKEN`.
30
+ * Inverting to allowlist closes the entire credential-name surface.
31
+ */
32
+ const SAFE_ENV_VARS = new Set([
33
+ "HOME",
34
+ "USER",
35
+ "USERNAME",
36
+ "LOGNAME",
37
+ "SHELL",
38
+ "PATH",
39
+ "LANG",
40
+ "LC_ALL",
41
+ "LC_CTYPE",
42
+ "TZ",
43
+ "TERM",
44
+ "TMPDIR",
45
+ "TEMP",
46
+ "TMP",
47
+ "PWD",
48
+ "OLDPWD",
49
+ "HOSTNAME",
50
+ "DISPLAY",
51
+ "EDITOR",
52
+ "PAGER",
53
+ "NODE_ENV",
54
+ "DEBUG",
55
+ "CI"
56
+ ]);
57
+ /**
58
+ * Shell binaries that are commonly used to wrap a subprocess and grant it the
59
+ * full inheritance surface that Nginx-MCP RCE (CVSS 9.8) exploits. Broadened
60
+ * per Reviewer S1024 F4-Sub-c — busybox/dash/csh/ksh/fish/tcsh now covered,
61
+ * arbitrary absolute paths covered, indirect invocation (`exec`, `nice`, `env`)
62
+ * covered.
63
+ */
64
+ const SHELL_BINARIES = [
65
+ "sh",
66
+ "bash",
67
+ "zsh",
68
+ "dash",
69
+ "ksh",
70
+ "csh",
71
+ "tcsh",
72
+ "fish",
73
+ "ash",
74
+ "busybox"
75
+ ];
76
+ /**
77
+ * Detect any unquoted shell metacharacter or substitution form anywhere in
78
+ * the command. Covers (Reviewer S1024 F4-Sub-a evidence):
79
+ * - Standalone metachars: `;`, `|`, `&` (single + double), `>`, `<`
80
+ * - Command substitution: backticks `` `cmd` ``
81
+ * - `$(cmd)` substitution
82
+ * - `<<<` here-strings
83
+ * - `${VAR}` and `$VAR` variable expansions adjacent to metachars
84
+ */
85
+ const SHELL_METACHAR_PATTERNS = [
86
+ { pattern: /`[^`]*`/, name: "backtick command substitution" },
87
+ { pattern: /\$\([^)]*\)/, name: "$(...) command substitution" },
88
+ { pattern: /<<</, name: "here-string redirect (<<<)" },
89
+ { pattern: /(^|[^\\])[;]/, name: "unescaped semicolon" },
90
+ { pattern: /(^|[^\\|])\|(?!\|)/, name: "unescaped pipe" },
91
+ { pattern: /(^|[^\\&])&(?!&)/, name: "unescaped background ampersand" },
92
+ { pattern: /(^|[^\\])&&/, name: "logical-and operator" },
93
+ { pattern: /(^|[^\\])\|\|/, name: "logical-or operator" },
94
+ { pattern: /(^|[^\\])>(?!>)/, name: "stdout redirect" },
95
+ { pattern: /(^|[^\\])>>/, name: "append redirect" },
96
+ { pattern: /(^|[^\\])<(?![<])/, name: "stdin redirect" },
97
+ { pattern: /\$\{[^}]+\}/, name: "${VAR} expansion" },
98
+ { pattern: /\$[A-Za-z_][A-Za-z0-9_]*/, name: "$VAR expansion" }
99
+ ];
100
+ function findShellInvocation(cmd) {
101
+ // Tokenize on whitespace + simple separators so we can spot a shell
102
+ // binary anywhere in the command (not just anchored at the start).
103
+ const tokens = cmd
104
+ .split(/[\s;|&]+/)
105
+ .map((t) => t.trim())
106
+ .filter((t) => t.length > 0);
107
+ for (const token of tokens) {
108
+ // Strip absolute-path prefix to compare just the basename.
109
+ const basename = token.split("/").pop() ?? token;
110
+ if (SHELL_BINARIES.includes(basename)) {
111
+ return token;
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+ export const BUILT_IN_FIXTURES = [
117
+ {
118
+ id: "mcp-sdk-rce-2026-04-22",
119
+ description: "MCP-SDK ungesanitized Tool-Args fed into shell command. Predicate: command must NOT contain ANY shell metacharacters or substitution forms (broadened per Reviewer S1024 F4-Sub-a — covers ;, |, &, &&, ||, backticks, $(), <<<, redirects, ${VAR}, $VAR).",
120
+ predicate: (cmd) => {
121
+ for (const { pattern, name } of SHELL_METACHAR_PATTERNS) {
122
+ if (pattern.test(cmd)) {
123
+ return {
124
+ status: "fail",
125
+ evidence: `command contains ${name} (${cmd}) — shell-injection class`
126
+ };
127
+ }
128
+ }
129
+ return {
130
+ status: "pass",
131
+ evidence: "no shell metacharacters or substitution forms detected"
132
+ };
133
+ }
134
+ },
135
+ {
136
+ id: "cve-2026-27124",
137
+ description: "FastMCP Confused Deputy — server forwards caller-controlled token to downstream. Predicate: any embedded `${VAR}` or `$VAR` must be in the SAFE_ENV_VARS allowlist (Reviewer S1024 F4-Sub-b — invert to allowlist).",
138
+ predicate: (cmd) => {
139
+ // Match both ${VAR_NAME} and $VAR_NAME forms.
140
+ const bracedRe = /\$\{([A-Za-z_][A-Za-z0-9_]*)[^}]*\}/g;
141
+ const bareRe = /\$([A-Za-z_][A-Za-z0-9_]*)\b/g;
142
+ const flagged = [];
143
+ for (const re of [bracedRe, bareRe]) {
144
+ let m;
145
+ while ((m = re.exec(cmd)) !== null) {
146
+ const name = m[1];
147
+ if (name && !SAFE_ENV_VARS.has(name)) {
148
+ flagged.push(name);
149
+ }
150
+ }
151
+ }
152
+ if (flagged.length > 0) {
153
+ const list = Array.from(new Set(flagged)).join(", ");
154
+ return {
155
+ status: "fail",
156
+ evidence: `command embeds non-allowlisted env-var(s): ${list} — potential confused-deputy class`
157
+ };
158
+ }
159
+ return {
160
+ status: "pass",
161
+ evidence: "no non-allowlisted env-vars embedded in command"
162
+ };
163
+ }
164
+ },
165
+ {
166
+ id: "nginx-mcp-rce-9.8",
167
+ description: "Nginx-MCP RCE CVSS 9.8 — bridge-server spawns subprocess inheriting full parent permissions. Predicate: no shell binary may appear anywhere in the command, regardless of path/invocation form (broadened per Reviewer S1024 F4-Sub-c — busybox/dash/csh/ksh/fish/tcsh covered, arbitrary paths covered, indirect invocation covered).",
168
+ predicate: (cmd) => {
169
+ const found = findShellInvocation(cmd);
170
+ if (found !== null) {
171
+ return {
172
+ status: "fail",
173
+ evidence: `command invokes shell binary "${found}" — RCE-bridge class`
174
+ };
175
+ }
176
+ return {
177
+ status: "pass",
178
+ evidence: "no shell binary invocation detected"
179
+ };
180
+ }
181
+ }
182
+ ];
183
+ export function getFixture(id) {
184
+ return BUILT_IN_FIXTURES.find((f) => f.id === id);
185
+ }
186
+ export function runReplay(targetCommand, cveSet, timeoutMs = 30_000) {
187
+ // Reviewer F3 (Session post-S1056): pre-normalize the target command before
188
+ // feeding it to predicates. NFKC + ZWC-strip + Bidi-block ensures
189
+ // Fullwidth-Unicode-Bypass payloads like `$(rm -rf /)` collapse to
190
+ // `$(rm -rf /)` and trigger SHELL_METACHAR_PATTERNS. Without this step,
191
+ // `scan_cve_replay` returned false-negatives on the simulate_attacker_input
192
+ // fullwidth-unicode corpus that README §13 explicitly claims to cover.
193
+ // The audit_subprocess path already normalizes via Pillar-8 shared normalize.ts;
194
+ // this aligns scan_cve_replay with the same canonical form.
195
+ const normalizedCommand = normalizeArg(targetCommand);
196
+ const ids = cveSet ?? BUILT_IN_FIXTURES.map((f) => f.id);
197
+ const perCve = [];
198
+ const startAll = Date.now();
199
+ for (const id of ids) {
200
+ const fixture = getFixture(id);
201
+ if (!fixture) {
202
+ perCve.push({ id, status: "skipped", durationMs: 0, evidence: "unknown fixture id" });
203
+ continue;
204
+ }
205
+ if (Date.now() - startAll > timeoutMs) {
206
+ perCve.push({ id, status: "skipped", durationMs: 0, evidence: "timeout" });
207
+ continue;
208
+ }
209
+ const start = Date.now();
210
+ const result = fixture.predicate(normalizedCommand);
211
+ perCve.push({
212
+ id,
213
+ status: result.status,
214
+ durationMs: Date.now() - start,
215
+ evidence: result.evidence
216
+ });
217
+ }
218
+ const overall = perCve.every((r) => r.status !== "fail") ? "pass" : "fail";
219
+ return { overall, perCve };
220
+ }
221
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/cve/replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Egress policy evaluation.
3
+ *
4
+ * Default-deny: a host:port is allowed only if it matches an entry in the
5
+ * allowlist. Wildcard "*" allows all (audit-only mode). Subdomain matching
6
+ * uses literal suffix-with-dot (no glob).
7
+ *
8
+ * Pre-Publish-Audit F7: host strings on both sides of the match are first
9
+ * NFKC + zero-width-strip + bidi-block normalized via Pillar-8 normalize.ts
10
+ * and lowercased, so a fullwidth-unicode hostname (e.g. `example.com`)
11
+ * cannot smuggle past an allowlist that lists the ASCII form.
12
+ */
13
+ export interface EgressDecision {
14
+ allowed: boolean;
15
+ reason: string;
16
+ }
17
+ /**
18
+ * Evaluate whether a host:port pair is allowed by the allowlist.
19
+ *
20
+ * Allowlist entry forms:
21
+ * - "*" → allow all (used in audit-only mode)
22
+ * - "example.com:443" → exact host:port match
23
+ * - ".example.com:443"→ suffix match (any subdomain)
24
+ * - "example.com:*" → host match, any port
25
+ */
26
+ export declare function evaluateEgress(host: string, port: number, allowlist: readonly string[]): EgressDecision;
27
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/egress/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAWD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,cAAc,CAchB"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Egress policy evaluation.
3
+ *
4
+ * Default-deny: a host:port is allowed only if it matches an entry in the
5
+ * allowlist. Wildcard "*" allows all (audit-only mode). Subdomain matching
6
+ * uses literal suffix-with-dot (no glob).
7
+ *
8
+ * Pre-Publish-Audit F7: host strings on both sides of the match are first
9
+ * NFKC + zero-width-strip + bidi-block normalized via Pillar-8 normalize.ts
10
+ * and lowercased, so a fullwidth-unicode hostname (e.g. `example.com`)
11
+ * cannot smuggle past an allowlist that lists the ASCII form.
12
+ */
13
+ import { normalizeArg } from "../normalize.js";
14
+ /**
15
+ * Canonicalize a host string: NFKC + invisible-codepoint strip via the
16
+ * shared normalize pipeline, then lowercase. Used on both allowlist entries
17
+ * and the candidate host so matching is locale-stable and unicode-safe.
18
+ */
19
+ function canonicalHost(host) {
20
+ return normalizeArg(host).toLowerCase();
21
+ }
22
+ /**
23
+ * Evaluate whether a host:port pair is allowed by the allowlist.
24
+ *
25
+ * Allowlist entry forms:
26
+ * - "*" → allow all (used in audit-only mode)
27
+ * - "example.com:443" → exact host:port match
28
+ * - ".example.com:443"→ suffix match (any subdomain)
29
+ * - "example.com:*" → host match, any port
30
+ */
31
+ export function evaluateEgress(host, port, allowlist) {
32
+ if (!host || !Number.isFinite(port) || port < 0 || port > 65535) {
33
+ return { allowed: false, reason: "invalid host or port" };
34
+ }
35
+ if (allowlist.includes("*")) {
36
+ return { allowed: true, reason: "wildcard allow" };
37
+ }
38
+ const candidate = canonicalHost(host);
39
+ for (const entry of allowlist) {
40
+ if (matches(candidate, port, entry)) {
41
+ return { allowed: true, reason: `match: ${entry}` };
42
+ }
43
+ }
44
+ return { allowed: false, reason: "default-deny: no allowlist match" };
45
+ }
46
+ function matches(host, port, entry) {
47
+ const colonIdx = entry.lastIndexOf(":");
48
+ if (colonIdx < 0)
49
+ return false;
50
+ const entryHost = canonicalHost(entry.slice(0, colonIdx));
51
+ const entryPort = entry.slice(colonIdx + 1);
52
+ // Port match
53
+ if (entryPort !== "*" && entryPort !== String(port)) {
54
+ return false;
55
+ }
56
+ // Host match (both sides already canonicalized).
57
+ if (entryHost.startsWith(".")) {
58
+ return host.endsWith(entryHost) || host === entryHost.slice(1);
59
+ }
60
+ return host === entryHost;
61
+ }
62
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/egress/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,IAAY,EACZ,SAA4B;IAE5B,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QAChE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC;QACtD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,IAAY,EAAE,KAAa;IACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IAE5C,aAAa;IACb,IAAI,SAAS,KAAK,GAAG,IAAI,SAAS,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,iDAAiD;IACjD,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,KAAK,SAAS,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * mcp-rce-guard — public library entrypoint.
3
+ *
4
+ * This module re-exports the building blocks for callers that want to
5
+ * embed mcp-rce-guard in their own MCP server / CLI tool.
6
+ */
7
+ export { NAME, VERSION } from "./version.js";
8
+ export { IsolationProfileSchema, RegisterSubprocessArgsSchema, AuditSubprocessArgsSchema, ScanCveReplayArgsSchema, TrackCanaryArgsSchema, InjectEgressPolicyArgsSchema, GetAuditLogArgsSchema, TrustTierSchema, CveIdSchema, EgressModeSchema, AuditVerdict, type IsolationProfile, type RegisterSubprocessArgs, type AuditSubprocessArgs, type ScanCveReplayArgs, type TrackCanaryArgs, type InjectEgressPolicyArgs, type GetAuditLogArgs, type AuditLogEntry, type RegisteredSubprocess, type AuditVerdictType, type CveId, type EgressMode } from "./types.js";
9
+ export { normalizeArg, normalizeArgs, hasInvisibleCodepoints } from "./normalize.js";
10
+ export { TRUST_TIER_DEFAULTS, resolveProfile, type TrustTier } from "./trust-tiers.js";
11
+ export { detectPlatform, type PlatformInfo, type IsolationBackend } from "./isolation/platform.js";
12
+ export { buildLandlockPolicy, policyAllowsExec } from "./isolation/landlock.js";
13
+ export type { LandlockPolicyDescriptor } from "./isolation/landlock.js";
14
+ export { buildSandboxProfile } from "./isolation/sandbox-exec.js";
15
+ export { buildCgroupSpec, type CgroupSpec } from "./isolation/cgroups.js";
16
+ export { makeCanary, detectLeaks, type LeakChannel, type LeakLocation, type CorpusEntry } from "./canary/tracker.js";
17
+ export { runReplay, BUILT_IN_FIXTURES, getFixture, type CveFixture, type ReplayResult, type ReplayPerCve } from "./cve/replay.js";
18
+ export { evaluateEgress, type EgressDecision } from "./egress/policy.js";
19
+ export { appendAudit, readAudit, auditLogPath } from "./audit/log.js";
20
+ export { registerSubprocessTool, type RegisterSubprocessResult } from "./tools/register.js";
21
+ export { auditSubprocessTool, type AuditSubprocessResult } from "./tools/audit.js";
22
+ export { scanCveReplayTool } from "./tools/scanCve.js";
23
+ export { trackCanaryTool, type TrackCanaryResult } from "./tools/trackCanary.js";
24
+ export { injectEgressPolicyTool, type InjectEgressPolicyResult } from "./tools/injectEgress.js";
25
+ export { getAuditLogTool, type GetAuditLogResult } from "./tools/getAuditLog.js";
26
+ export { clearRegistry } from "./state.js";
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,KAAK,EACV,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAGrF,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChF,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAG1E,OAAO,EACL,UAAU,EACV,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKzE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGtE,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EACL,eAAe,EACf,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,eAAe,EACf,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * mcp-rce-guard — public library entrypoint.
3
+ *
4
+ * This module re-exports the building blocks for callers that want to
5
+ * embed mcp-rce-guard in their own MCP server / CLI tool.
6
+ */
7
+ export { NAME, VERSION } from "./version.js";
8
+ // Types + schemas
9
+ export { IsolationProfileSchema, RegisterSubprocessArgsSchema, AuditSubprocessArgsSchema, ScanCveReplayArgsSchema, TrackCanaryArgsSchema, InjectEgressPolicyArgsSchema, GetAuditLogArgsSchema, TrustTierSchema, CveIdSchema, EgressModeSchema, AuditVerdict } from "./types.js";
10
+ // Normalize (Pillar-8-shared)
11
+ export { normalizeArg, normalizeArgs, hasInvisibleCodepoints } from "./normalize.js";
12
+ // Trust tiers
13
+ export { TRUST_TIER_DEFAULTS, resolveProfile } from "./trust-tiers.js";
14
+ // Isolation backends
15
+ export { detectPlatform } from "./isolation/platform.js";
16
+ export { buildLandlockPolicy, policyAllowsExec } from "./isolation/landlock.js";
17
+ export { buildSandboxProfile } from "./isolation/sandbox-exec.js";
18
+ export { buildCgroupSpec } from "./isolation/cgroups.js";
19
+ // Canary
20
+ export { makeCanary, detectLeaks } from "./canary/tracker.js";
21
+ // CVE replay
22
+ export { runReplay, BUILT_IN_FIXTURES, getFixture } from "./cve/replay.js";
23
+ // Egress
24
+ export { evaluateEgress } from "./egress/policy.js";
25
+ // Audit log
26
+ // Note: `clearAuditLog` is intentionally NOT re-exported here (Pre-Publish-Audit F9).
27
+ // It is a @internal test helper; tests import it directly from "./audit/log.js".
28
+ export { appendAudit, readAudit, auditLogPath } from "./audit/log.js";
29
+ // Tools (programmatic access — same handlers the MCP server registers)
30
+ export { registerSubprocessTool } from "./tools/register.js";
31
+ export { auditSubprocessTool } from "./tools/audit.js";
32
+ export { scanCveReplayTool } from "./tools/scanCve.js";
33
+ export { trackCanaryTool } from "./tools/trackCanary.js";
34
+ export { injectEgressPolicyTool } from "./tools/injectEgress.js";
35
+ export { getAuditLogTool } from "./tools/getAuditLog.js";
36
+ // State (test-only escape hatch — guarded by name)
37
+ export { clearRegistry } from "./state.js";
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE7C,kBAAkB;AAClB,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EAab,MAAM,YAAY,CAAC;AAEpB,8BAA8B;AAC9B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAErF,cAAc;AACd,OAAO,EACL,mBAAmB,EACnB,cAAc,EAEf,MAAM,kBAAkB,CAAC;AAE1B,qBAAqB;AACrB,OAAO,EACL,cAAc,EAGf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAmB,MAAM,wBAAwB,CAAC;AAE1E,SAAS;AACT,OAAO,EACL,UAAU,EACV,WAAW,EAIZ,MAAM,qBAAqB,CAAC;AAE7B,aAAa;AACb,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,UAAU,EAIX,MAAM,iBAAiB,CAAC;AAEzB,SAAS;AACT,OAAO,EAAE,cAAc,EAAuB,MAAM,oBAAoB,CAAC;AAEzE,YAAY;AACZ,sFAAsF;AACtF,iFAAiF;AACjF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,uEAAuE;AACvE,OAAO,EACL,sBAAsB,EAEvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EAEpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EACL,eAAe,EAEhB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,eAAe,EAEhB,MAAM,wBAAwB,CAAC;AAEhC,mDAAmD;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * cgroups-v2 resource cap synthesis.
3
+ *
4
+ * v0.1 emits a *spec* listing the files-to-write and values. The actual
5
+ * write happens in the spawning helper (separation-of-concerns: we don't
6
+ * want to write to /sys/fs/cgroup from inside a tool handler — that needs
7
+ * elevated privileges that the MCP server typically lacks).
8
+ */
9
+ import type { IsolationProfile } from "../types.js";
10
+ export interface CgroupSpec {
11
+ /** cgroup v2 path under /sys/fs/cgroup. The spawner creates it. */
12
+ cgroupPath: string;
13
+ /** key/value writes the spawner must perform. */
14
+ writes: Array<{
15
+ file: string;
16
+ value: string;
17
+ }>;
18
+ }
19
+ export declare function buildCgroupSpec(cgroupName: string, profile: IsolationProfile): CgroupSpec;
20
+ //# sourceMappingURL=cgroups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cgroups.d.ts","sourceRoot":"","sources":["../../src/isolation/cgroups.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,GACxB,UAAU,CAyBZ"}