mcp-recon 0.2.2

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 (95) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +271 -0
  3. package/dist/bin/recon.d.ts +18 -0
  4. package/dist/bin/recon.d.ts.map +1 -0
  5. package/dist/bin/recon.js +361 -0
  6. package/dist/bin/recon.js.map +1 -0
  7. package/dist/caveats/index.d.ts +46 -0
  8. package/dist/caveats/index.d.ts.map +1 -0
  9. package/dist/caveats/index.js +186 -0
  10. package/dist/caveats/index.js.map +1 -0
  11. package/dist/caveats/render.d.ts +25 -0
  12. package/dist/caveats/render.d.ts.map +1 -0
  13. package/dist/caveats/render.js +100 -0
  14. package/dist/caveats/render.js.map +1 -0
  15. package/dist/caveats/types.d.ts +94 -0
  16. package/dist/caveats/types.d.ts.map +1 -0
  17. package/dist/caveats/types.js +17 -0
  18. package/dist/caveats/types.js.map +1 -0
  19. package/dist/classify/caveat.d.ts +29 -0
  20. package/dist/classify/caveat.d.ts.map +1 -0
  21. package/dist/classify/caveat.js +103 -0
  22. package/dist/classify/caveat.js.map +1 -0
  23. package/dist/classify/index.d.ts +21 -0
  24. package/dist/classify/index.d.ts.map +1 -0
  25. package/dist/classify/index.js +186 -0
  26. package/dist/classify/index.js.map +1 -0
  27. package/dist/classify/rules.d.ts +62 -0
  28. package/dist/classify/rules.d.ts.map +1 -0
  29. package/dist/classify/rules.js +219 -0
  30. package/dist/classify/rules.js.map +1 -0
  31. package/dist/classify/types.d.ts +45 -0
  32. package/dist/classify/types.d.ts.map +1 -0
  33. package/dist/classify/types.js +9 -0
  34. package/dist/classify/types.js.map +1 -0
  35. package/dist/enumerate.d.ts +79 -0
  36. package/dist/enumerate.d.ts.map +1 -0
  37. package/dist/enumerate.js +62 -0
  38. package/dist/enumerate.js.map +1 -0
  39. package/dist/fuzz/axes/boundary.d.ts +17 -0
  40. package/dist/fuzz/axes/boundary.d.ts.map +1 -0
  41. package/dist/fuzz/axes/boundary.js +143 -0
  42. package/dist/fuzz/axes/boundary.js.map +1 -0
  43. package/dist/fuzz/axes/encoding.d.ts +17 -0
  44. package/dist/fuzz/axes/encoding.d.ts.map +1 -0
  45. package/dist/fuzz/axes/encoding.js +59 -0
  46. package/dist/fuzz/axes/encoding.js.map +1 -0
  47. package/dist/fuzz/axes/path-traversal.d.ts +17 -0
  48. package/dist/fuzz/axes/path-traversal.d.ts.map +1 -0
  49. package/dist/fuzz/axes/path-traversal.js +56 -0
  50. package/dist/fuzz/axes/path-traversal.js.map +1 -0
  51. package/dist/fuzz/axes/schema-violation.d.ts +18 -0
  52. package/dist/fuzz/axes/schema-violation.d.ts.map +1 -0
  53. package/dist/fuzz/axes/schema-violation.js +74 -0
  54. package/dist/fuzz/axes/schema-violation.js.map +1 -0
  55. package/dist/fuzz/axes/type-confusion.d.ts +17 -0
  56. package/dist/fuzz/axes/type-confusion.d.ts.map +1 -0
  57. package/dist/fuzz/axes/type-confusion.js +67 -0
  58. package/dist/fuzz/axes/type-confusion.js.map +1 -0
  59. package/dist/fuzz/axes/url-hostility.d.ts +17 -0
  60. package/dist/fuzz/axes/url-hostility.d.ts.map +1 -0
  61. package/dist/fuzz/axes/url-hostility.js +61 -0
  62. package/dist/fuzz/axes/url-hostility.js.map +1 -0
  63. package/dist/fuzz/index.d.ts +41 -0
  64. package/dist/fuzz/index.d.ts.map +1 -0
  65. package/dist/fuzz/index.js +147 -0
  66. package/dist/fuzz/index.js.map +1 -0
  67. package/dist/fuzz/prng.d.ts +26 -0
  68. package/dist/fuzz/prng.d.ts.map +1 -0
  69. package/dist/fuzz/prng.js +52 -0
  70. package/dist/fuzz/prng.js.map +1 -0
  71. package/dist/fuzz/schema.d.ts +46 -0
  72. package/dist/fuzz/schema.d.ts.map +1 -0
  73. package/dist/fuzz/schema.js +84 -0
  74. package/dist/fuzz/schema.js.map +1 -0
  75. package/dist/fuzz/types.d.ts +53 -0
  76. package/dist/fuzz/types.d.ts.map +1 -0
  77. package/dist/fuzz/types.js +11 -0
  78. package/dist/fuzz/types.js.map +1 -0
  79. package/dist/index.d.ts +25 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +25 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/report/index.d.ts +25 -0
  84. package/dist/report/index.d.ts.map +1 -0
  85. package/dist/report/index.js +133 -0
  86. package/dist/report/index.js.map +1 -0
  87. package/dist/scan/index.d.ts +52 -0
  88. package/dist/scan/index.d.ts.map +1 -0
  89. package/dist/scan/index.js +81 -0
  90. package/dist/scan/index.js.map +1 -0
  91. package/dist/transport.d.ts +43 -0
  92. package/dist/transport.d.ts.map +1 -0
  93. package/dist/transport.js +74 -0
  94. package/dist/transport.js.map +1 -0
  95. package/package.json +72 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Caveat synthesizer — the bridge from mcp-recon to capnagent.
3
+ *
4
+ * For each classified tool, generate a copy-pasteable capnagent
5
+ * caveat string that bounds the tool's authority to the smallest
6
+ * surface that preserves utility. The caveat is always a *suggestion*;
7
+ * the operator should review and tighten further to fit their
8
+ * deployment.
9
+ *
10
+ * Convention: every suggestion is a single DSL predicate that can be
11
+ * passed to `Issuer.issue(...).caveat(...)` or `cap.attenuate(...)`.
12
+ * The caveat language is documented in capnagent's
13
+ * `caveat_dsl.rs`.
14
+ */
15
+ /**
16
+ * Suggest a capnagent caveat for the given classification + facts.
17
+ * The suggestion places `<placeholder>` markers where the operator
18
+ * must substitute deployment-specific values.
19
+ */
20
+ export function synthesizeCaveat(input) {
21
+ const { tool, data_class, authority_level, facts } = input;
22
+ // Privileged tools: deny outright. Leaving exec/shell exposed is
23
+ // almost never the right answer; if it is, the operator can
24
+ // hand-write a tighter argv-allowlist caveat.
25
+ if (authority_level === "privileged") {
26
+ return `tool != "${tool}" // PRIVILEGED — recommend deny outright; operator should hand-write argv allowlist if exposing`;
27
+ }
28
+ // Destructive tools: caller-bind + bounded args + expiry.
29
+ if (authority_level === "destructive") {
30
+ const argClauses = boundedArgClauses(tool, facts, "destructive");
31
+ return [
32
+ `tool == "${tool}"`,
33
+ `caller == "<your-caller-id>"`,
34
+ ...argClauses,
35
+ `now <= @<your-cap-expiry>`,
36
+ ].join(" AND ");
37
+ }
38
+ // Per-class write/read defaults.
39
+ const argClauses = boundedArgClauses(tool, facts, authority_level);
40
+ const baseClauses = [
41
+ `tool == "${tool}"`,
42
+ `caller == "<your-caller-id>"`,
43
+ ...argClauses,
44
+ `now <= @<your-cap-expiry>`,
45
+ ];
46
+ // Add a class-flavoured comment so the operator knows what
47
+ // surface they're bounding.
48
+ const flavor = flavorComment(data_class, authority_level);
49
+ return `${baseClauses.join(" AND ")} // ${flavor}`;
50
+ }
51
+ function boundedArgClauses(_tool, facts, authority) {
52
+ const clauses = [];
53
+ for (const arg of facts.args) {
54
+ const argName = arg.path[0];
55
+ if (argName === undefined)
56
+ continue;
57
+ if (arg.isPathShaped && arg.declaredType === "string") {
58
+ // For path-shaped args, the recommended caveat is the same
59
+ // shape capnagent's mcp-fs-agent uses post-v0.5.
60
+ clauses.push(`arg.${argName} starts_with "<your-sandbox-prefix>/"`);
61
+ continue;
62
+ }
63
+ if (arg.isUrlShaped && arg.declaredType === "string") {
64
+ clauses.push(`arg.${argName} == "https://<your-allowed-origin>"`);
65
+ continue;
66
+ }
67
+ if (arg.isCommandShaped && arg.declaredType === "string") {
68
+ clauses.push(`arg.${argName} starts_with "<your-allowed-command-prefix>"`);
69
+ continue;
70
+ }
71
+ if (arg.enumValues !== undefined) {
72
+ // Enums constrain themselves; no extra caveat needed in v0.1.
73
+ continue;
74
+ }
75
+ if (authority === "destructive" &&
76
+ (arg.declaredType === "string" || arg.declaredType === "unknown")) {
77
+ // Destructive + free-form string arg = double-bound.
78
+ clauses.push(`arg.${argName} starts_with "<your-bounded-prefix>/"`);
79
+ }
80
+ }
81
+ return clauses;
82
+ }
83
+ function flavorComment(dc, auth) {
84
+ switch (dc) {
85
+ case "filesystem":
86
+ return `${auth.toUpperCase()} filesystem; bound the sandbox prefix tightly (capnagent round 07/10)`;
87
+ case "network":
88
+ return `${auth.toUpperCase()} network; allowlist exact origins (capnagent round 05/09)`;
89
+ case "shell":
90
+ return `${auth.toUpperCase()} shell; argv allowlist mandatory (capnagent shell-agent example)`;
91
+ case "payments":
92
+ return `${auth.toUpperCase()} payments; bound amount + merchant + caller`;
93
+ case "messaging":
94
+ return `${auth.toUpperCase()} messaging; bound recipient list + caller`;
95
+ case "system":
96
+ return `${auth.toUpperCase()} system; usually safe but verify env-var exposure`;
97
+ case "metadata":
98
+ return `${auth.toUpperCase()} metadata`;
99
+ case "unknown":
100
+ return `${auth.toUpperCase()} unclassified — operator must review`;
101
+ }
102
+ }
103
+ //# sourceMappingURL=caveat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caveat.js","sourceRoot":"","sources":["../../src/classify/caveat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `classify` — turn an inventory (+ optional fuzz results) into a
3
+ * v0.1 classification document.
4
+ *
5
+ * Methodology in docs/METHODOLOGY.md. The rules live in `./rules.ts`.
6
+ * Confidence combines via noisy-OR; authority is the lattice-join of
7
+ * rule floors plus side-effect-verb escalations.
8
+ */
9
+ import type { ToolInventory } from "../enumerate.js";
10
+ import type { FuzzResults } from "../fuzz/types.js";
11
+ import { type ToolFacts } from "../fuzz/schema.js";
12
+ import { type ClassificationResults } from "./types.js";
13
+ export { CLASSIFICATION_SCHEMA } from "./types.js";
14
+ export type { AuthorityLevel, Classification, ClassificationResults, DataClass, } from "./types.js";
15
+ /** Classify every tool in `inventory`. Optionally fold in `fuzz` for confidence. */
16
+ export declare function classify(inventory: ToolInventory, fuzz?: FuzzResults): ClassificationResults;
17
+ /** Noisy-OR combiner: 1 - ∏(1 - c_i). */
18
+ export declare function noisyOr(confidences: readonly number[]): number;
19
+ export { synthesizeCaveat } from "./caveat.js";
20
+ export type { ToolFacts };
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/classify/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAerE,OAAO,EAIL,KAAK,qBAAqB,EAE3B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EACV,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,oFAAoF;AACpF,wBAAgB,QAAQ,CACtB,SAAS,EAAE,aAAa,EACxB,IAAI,CAAC,EAAE,WAAW,GACjB,qBAAqB,CAsBvB;AA2KD,yCAAyC;AACzC,wBAAgB,OAAO,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAO9D;AAcD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * `classify` — turn an inventory (+ optional fuzz results) into a
3
+ * v0.1 classification document.
4
+ *
5
+ * Methodology in docs/METHODOLOGY.md. The rules live in `./rules.ts`.
6
+ * Confidence combines via noisy-OR; authority is the lattice-join of
7
+ * rule floors plus side-effect-verb escalations.
8
+ */
9
+ import { extractToolFacts } from "../fuzz/schema.js";
10
+ import { synthesizeCaveat } from "./caveat.js";
11
+ import { DESCRIPTION_MATCH_WEIGHT, DESTRUCTIVE_VERBS, FUZZ_INFORMED_BONUS, NAME_MATCH_WEIGHT, PRIVILEGED_VERBS, RULES, SCHEMA_MATCH_WEIGHT, SIDE_EFFECT_VERBS, escalateAuthority, maxAuthority, } from "./rules.js";
12
+ import { CLASSIFICATION_SCHEMA, } from "./types.js";
13
+ export { CLASSIFICATION_SCHEMA } from "./types.js";
14
+ /** Classify every tool in `inventory`. Optionally fold in `fuzz` for confidence. */
15
+ export function classify(inventory, fuzz) {
16
+ const fuzzByTool = new Map();
17
+ if (fuzz) {
18
+ for (const s of fuzz.summary) {
19
+ fuzzByTool.set(s.tool, {
20
+ ok: s.ok,
21
+ total: s.ok + s.protocol_error + s.runtime_error,
22
+ });
23
+ }
24
+ }
25
+ const classifications = inventory.tools.map((tool) => classifyTool(tool, fuzzByTool.get(tool.name)));
26
+ return {
27
+ schema: CLASSIFICATION_SCHEMA,
28
+ scanned_at: new Date().toISOString(),
29
+ server: inventory.server,
30
+ fuzz_informed: fuzz !== undefined,
31
+ classifications,
32
+ };
33
+ }
34
+ function classifyTool(tool, fuzzStats) {
35
+ const facts = extractToolFacts(tool.name, tool.inputSchema);
36
+ const description = tool.description ?? "";
37
+ const name = tool.name;
38
+ const fired = [];
39
+ for (const rule of RULES) {
40
+ if (rule.scope !== "description" && rule.pattern.test(name)) {
41
+ fired.push({ rule, weight: NAME_MATCH_WEIGHT, where: "name" });
42
+ }
43
+ if (rule.scope !== "name" && rule.pattern.test(description)) {
44
+ fired.push({ rule, weight: DESCRIPTION_MATCH_WEIGHT, where: "description" });
45
+ }
46
+ }
47
+ // Schema-shape signal — adds to filesystem / network if arg names
48
+ // are path-shaped or URL-shaped.
49
+ const schemaFired = [];
50
+ for (const arg of facts.args) {
51
+ if (arg.isPathShaped) {
52
+ schemaFired.push({
53
+ data_class: "filesystem",
54
+ weight: SCHEMA_MATCH_WEIGHT,
55
+ reason: `arg "${arg.path[0]}" is path-shaped`,
56
+ });
57
+ }
58
+ if (arg.isUrlShaped) {
59
+ schemaFired.push({
60
+ data_class: "network",
61
+ weight: SCHEMA_MATCH_WEIGHT,
62
+ reason: `arg "${arg.path[0]}" is URL-shaped`,
63
+ });
64
+ }
65
+ if (arg.isCommandShaped) {
66
+ schemaFired.push({
67
+ data_class: "shell",
68
+ weight: SCHEMA_MATCH_WEIGHT,
69
+ reason: `arg "${arg.path[0]}" is command-shaped`,
70
+ });
71
+ }
72
+ }
73
+ // Bucket evidence by data_class. Per-bucket noisy-OR over rule
74
+ // confidences gives us the final per-class confidence; the winner
75
+ // is the highest.
76
+ const evidenceByClass = new Map();
77
+ const floorsByClass = new Map();
78
+ const rationaleParts = [];
79
+ for (const f of fired) {
80
+ pushEvidence(evidenceByClass, f.rule.data_class, f.weight);
81
+ floorsByClass.set(f.rule.data_class, maxAuthority(floorsByClass.get(f.rule.data_class) ?? f.rule.authority_floor, f.rule.authority_floor));
82
+ rationaleParts.push(`${f.where} match "${f.rule.pattern.source}" → ${f.rule.data_class}/${f.rule.authority_floor} (${f.weight.toFixed(2)})`);
83
+ }
84
+ for (const s of schemaFired) {
85
+ pushEvidence(evidenceByClass, s.data_class, s.weight);
86
+ rationaleParts.push(`schema: ${s.reason} → ${s.data_class} (${s.weight.toFixed(2)})`);
87
+ }
88
+ // Pick the highest-confidence data-class.
89
+ let winnerClass = "unknown";
90
+ let winnerConfidence = 0;
91
+ for (const [dc, confidences] of evidenceByClass.entries()) {
92
+ const combined = noisyOr(confidences);
93
+ if (combined > winnerConfidence) {
94
+ winnerClass = dc;
95
+ winnerConfidence = combined;
96
+ }
97
+ }
98
+ // Apply fuzz-informed bonus: if the server actually accepted any of
99
+ // our adversarial inputs, raise confidence we know what kind of
100
+ // tool this is (the fuzzer crossed enough surface to confirm a
101
+ // real interaction, even if accepted inputs are not "successes").
102
+ if (fuzzStats && fuzzStats.total > 0 && fuzzStats.ok > 0) {
103
+ winnerConfidence = noisyOr([winnerConfidence, FUZZ_INFORMED_BONUS]);
104
+ rationaleParts.push(`fuzz: ${fuzzStats.ok}/${fuzzStats.total} accepted → +${FUZZ_INFORMED_BONUS}`);
105
+ }
106
+ // Authority: the floor for the winning class, escalated by any
107
+ // side-effect verbs in the description.
108
+ let authority = floorsByClass.get(winnerClass) ?? "read";
109
+ for (const verb of PRIVILEGED_VERBS) {
110
+ if (verb.test(description)) {
111
+ authority = "privileged";
112
+ rationaleParts.push(`description has privileged verb → escalate to privileged`);
113
+ }
114
+ }
115
+ for (const verb of DESTRUCTIVE_VERBS) {
116
+ if (verb.test(description)) {
117
+ authority = maxAuthority(authority, "destructive");
118
+ rationaleParts.push(`description has destructive verb → escalate to destructive`);
119
+ }
120
+ }
121
+ // Generic side-effect verbs only escalate by ONE step, and only if
122
+ // we haven't already saturated.
123
+ for (const verb of SIDE_EFFECT_VERBS) {
124
+ if (verb.test(description) && authority === "read") {
125
+ authority = escalateAuthority(authority);
126
+ rationaleParts.push(`description has side-effect verb → escalate one step`);
127
+ break;
128
+ }
129
+ }
130
+ // Confused-deputy flag: tool takes user-controllable string args
131
+ // AND has write/destructive/privileged authority. (METHODOLOGY.md
132
+ // §"The confused-deputy flag")
133
+ const hasUserStringArg = facts.args.some((a) => (a.declaredType === "string" || a.declaredType === "unknown") &&
134
+ a.enumValues === undefined);
135
+ const writeOrAbove = authority !== "read";
136
+ const confusedDeputy = hasUserStringArg && writeOrAbove;
137
+ if (confusedDeputy) {
138
+ rationaleParts.push("user-controllable string arg + non-read authority → confused-deputy candidate");
139
+ }
140
+ const recommended_caveat = synthesizeCaveat({
141
+ tool: name,
142
+ data_class: winnerClass,
143
+ authority_level: authority,
144
+ facts,
145
+ });
146
+ return {
147
+ tool: name,
148
+ data_class: winnerClass,
149
+ authority_level: authority,
150
+ confused_deputy_candidate: confusedDeputy,
151
+ confidence: round(winnerConfidence, 3),
152
+ rationale: rationaleParts.length === 0 ? "no rules fired" : rationaleParts.join("; "),
153
+ recommended_caveat,
154
+ };
155
+ }
156
+ function pushEvidence(map, dc, w) {
157
+ const arr = map.get(dc);
158
+ if (arr)
159
+ arr.push(w);
160
+ else
161
+ map.set(dc, [w]);
162
+ }
163
+ /** Noisy-OR combiner: 1 - ∏(1 - c_i). */
164
+ export function noisyOr(confidences) {
165
+ if (confidences.length === 0)
166
+ return 0;
167
+ let product = 1;
168
+ for (const c of confidences) {
169
+ product *= 1 - clamp(c, 0, 1);
170
+ }
171
+ return clamp(1 - product, 0, 1);
172
+ }
173
+ function clamp(x, lo, hi) {
174
+ if (x < lo)
175
+ return lo;
176
+ if (x > hi)
177
+ return hi;
178
+ return x;
179
+ }
180
+ function round(x, decimals) {
181
+ const f = 10 ** decimals;
182
+ return Math.round(x * f) / f;
183
+ }
184
+ // Re-export the synthesizer / facts for downstream tools that want it.
185
+ export { synthesizeCaveat } from "./caveat.js";
186
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/classify/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Rule table — the single source of truth the classifier reads.
3
+ *
4
+ * Every rule is a triple: (pattern, data-class, authority-level)
5
+ * plus a confidence weight. Rules over tool *names* and *descriptions*
6
+ * use regex match; rules over *schemas* use heuristic shape detection
7
+ * already implemented in `../fuzz/schema.ts`.
8
+ *
9
+ * Per docs/METHODOLOGY.md §"Confidence scoring":
10
+ * - Tool name match — 0.7 (strong cue, gameable)
11
+ * - Description match — 0.5 (helpful, often missing)
12
+ * - Schema match — 0.4 (structural, gameable too)
13
+ * - Side-effect verb in desc — 0.6 (raises authority by one step)
14
+ *
15
+ * Adding a rule? Update this file AND docs/METHODOLOGY.md's change-log
16
+ * with date + reason. No silent drift.
17
+ */
18
+ import type { AuthorityLevel, DataClass } from "./types.js";
19
+ /** Single rule predicting (data_class, authority_level) given a name/description match. */
20
+ export interface NameOrDescRule {
21
+ /** Regex pattern (case-insensitive). */
22
+ pattern: RegExp;
23
+ data_class: DataClass;
24
+ /**
25
+ * Authority floor — the rule asserts at least this authority level.
26
+ * Final authority can be escalated by side-effect verbs.
27
+ */
28
+ authority_floor: AuthorityLevel;
29
+ /** Where this rule applies. */
30
+ scope: "name" | "description" | "either";
31
+ }
32
+ /** Confidence weights per rule scope. */
33
+ export declare const NAME_MATCH_WEIGHT = 0.7;
34
+ export declare const DESCRIPTION_MATCH_WEIGHT = 0.5;
35
+ export declare const SCHEMA_MATCH_WEIGHT = 0.4;
36
+ /**
37
+ * Bonus added when fuzz results show the tool actually accepts adversarial
38
+ * inputs (i.e. > 1 ok response in the fuzz stream). Caps confidence higher.
39
+ */
40
+ export declare const FUZZ_INFORMED_BONUS = 0.1;
41
+ /** Side-effect verbs that raise authority by one step when seen in description. */
42
+ export declare const SIDE_EFFECT_VERBS: RegExp[];
43
+ /**
44
+ * Side-effect verbs strong enough to assert *destructive* directly
45
+ * (matches push authority to "destructive", not just "write").
46
+ */
47
+ export declare const DESTRUCTIVE_VERBS: RegExp[];
48
+ /**
49
+ * Side-effect verbs strong enough to assert *privileged* directly
50
+ * (subprocess spawn / shell execution).
51
+ */
52
+ export declare const PRIVILEGED_VERBS: RegExp[];
53
+ /**
54
+ * Rule list. Ordered from most specific to most general — the
55
+ * classifier evaluates all rules; this ordering is for human review.
56
+ */
57
+ export declare const RULES: readonly NameOrDescRule[];
58
+ /** Return the higher of two authority levels (lattice join). */
59
+ export declare function maxAuthority(a: AuthorityLevel, b: AuthorityLevel): AuthorityLevel;
60
+ /** Escalate authority by one step (read → write → destructive → privileged). */
61
+ export declare function escalateAuthority(a: AuthorityLevel): AuthorityLevel;
62
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/classify/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5D,2FAA2F;AAC3F,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,eAAe,EAAE,cAAc,CAAC;IAChC,+BAA+B;IAC/B,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,QAAQ,CAAC;CAC1C;AAED,yCAAyC;AACzC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,UAM7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,UAE7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,UAA2D,CAAC;AAEzF;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EA+I1C,CAAC;AAUF,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,cAAc,CAEjF;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,cAAc,GAAG,cAAc,CAWnE"}
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Rule table — the single source of truth the classifier reads.
3
+ *
4
+ * Every rule is a triple: (pattern, data-class, authority-level)
5
+ * plus a confidence weight. Rules over tool *names* and *descriptions*
6
+ * use regex match; rules over *schemas* use heuristic shape detection
7
+ * already implemented in `../fuzz/schema.ts`.
8
+ *
9
+ * Per docs/METHODOLOGY.md §"Confidence scoring":
10
+ * - Tool name match — 0.7 (strong cue, gameable)
11
+ * - Description match — 0.5 (helpful, often missing)
12
+ * - Schema match — 0.4 (structural, gameable too)
13
+ * - Side-effect verb in desc — 0.6 (raises authority by one step)
14
+ *
15
+ * Adding a rule? Update this file AND docs/METHODOLOGY.md's change-log
16
+ * with date + reason. No silent drift.
17
+ */
18
+ /** Confidence weights per rule scope. */
19
+ export const NAME_MATCH_WEIGHT = 0.7;
20
+ export const DESCRIPTION_MATCH_WEIGHT = 0.5;
21
+ export const SCHEMA_MATCH_WEIGHT = 0.4;
22
+ /**
23
+ * Bonus added when fuzz results show the tool actually accepts adversarial
24
+ * inputs (i.e. > 1 ok response in the fuzz stream). Caps confidence higher.
25
+ */
26
+ export const FUZZ_INFORMED_BONUS = 0.1;
27
+ /** Side-effect verbs that raise authority by one step when seen in description. */
28
+ export const SIDE_EFFECT_VERBS = [
29
+ /\b(write|create|insert|append|update|modify|edit|set|put|post|patch)\b/i,
30
+ /\b(delete|remove|drop|unlink|destroy|cancel|revoke)\b/i,
31
+ /\b(send|email|notify|page|alert|publish|broadcast)\b/i,
32
+ /\b(spawn|exec|launch|run|invoke|fork|shell)\b/i,
33
+ /\b(charge|pay|transfer|wire|refund|debit|credit)\b/i,
34
+ ];
35
+ /**
36
+ * Side-effect verbs strong enough to assert *destructive* directly
37
+ * (matches push authority to "destructive", not just "write").
38
+ */
39
+ export const DESTRUCTIVE_VERBS = [
40
+ /\b(delete|remove|drop|unlink|destroy|cancel|revoke|wipe|purge|truncate)\b/i,
41
+ ];
42
+ /**
43
+ * Side-effect verbs strong enough to assert *privileged* directly
44
+ * (subprocess spawn / shell execution).
45
+ */
46
+ export const PRIVILEGED_VERBS = [/\b(spawn|exec|shell|bash|cmd|invoke_program|fork)\b/i];
47
+ /**
48
+ * Rule list. Ordered from most specific to most general — the
49
+ * classifier evaluates all rules; this ordering is for human review.
50
+ */
51
+ export const RULES = [
52
+ // ─── filesystem ──────────────────────────────────────────────
53
+ {
54
+ pattern: /\b(read[_-]?(file|text[_-]?file|media[_-]?file|multiple[_-]?files))\b/i,
55
+ data_class: "filesystem",
56
+ authority_floor: "read",
57
+ scope: "name",
58
+ },
59
+ {
60
+ pattern: /\b(list[_-]?(directory|directory[_-]?with[_-]?sizes)|directory[_-]?tree)\b/i,
61
+ data_class: "filesystem",
62
+ authority_floor: "read",
63
+ scope: "name",
64
+ },
65
+ {
66
+ pattern: /\b(get[_-]?file[_-]?info|search[_-]?files|list[_-]?allowed[_-]?directories)\b/i,
67
+ data_class: "filesystem",
68
+ authority_floor: "read",
69
+ scope: "name",
70
+ },
71
+ {
72
+ pattern: /\b(write[_-]?file|edit[_-]?file|create[_-]?directory|move[_-]?file)\b/i,
73
+ data_class: "filesystem",
74
+ authority_floor: "write",
75
+ scope: "name",
76
+ },
77
+ {
78
+ pattern: /\b(delete[_-]?(path|file|directory|dir))\b/i,
79
+ data_class: "filesystem",
80
+ authority_floor: "destructive",
81
+ scope: "name",
82
+ },
83
+ {
84
+ pattern: /\b(file|directory|folder|filesystem|filepath|dirent)\b/i,
85
+ data_class: "filesystem",
86
+ authority_floor: "read",
87
+ scope: "description",
88
+ },
89
+ // ─── network ─────────────────────────────────────────────────
90
+ {
91
+ pattern: /\b(http[_-]?(get|post|put|delete)|fetch|request)\b/i,
92
+ data_class: "network",
93
+ authority_floor: "read",
94
+ scope: "name",
95
+ },
96
+ {
97
+ pattern: /\b(http|https|fetch|request|webhook|api[_-]?call|url|origin)\b/i,
98
+ data_class: "network",
99
+ authority_floor: "read",
100
+ scope: "description",
101
+ },
102
+ // ─── shell ──────────────────────────────────────────────────
103
+ {
104
+ pattern: /\b(exec|shell|run[_-]?command|spawn|process)\b/i,
105
+ data_class: "shell",
106
+ authority_floor: "privileged",
107
+ scope: "name",
108
+ },
109
+ {
110
+ pattern: /\b(execute|shell|subprocess|invoke|launch[_-]?process)\b/i,
111
+ data_class: "shell",
112
+ authority_floor: "privileged",
113
+ scope: "description",
114
+ },
115
+ // ─── payments ───────────────────────────────────────────────
116
+ {
117
+ pattern: /\b(charge|refund|purchase|wire|payment|invoice|debit|credit)\b/i,
118
+ data_class: "payments",
119
+ authority_floor: "write",
120
+ scope: "either",
121
+ },
122
+ // ─── messaging ──────────────────────────────────────────────
123
+ {
124
+ pattern: /\b(send[_-]?(email|message|sms|notification)|notify|publish|broadcast)\b/i,
125
+ data_class: "messaging",
126
+ authority_floor: "write",
127
+ scope: "name",
128
+ },
129
+ {
130
+ pattern: /\b(email|notification|message|chat|page|alert)\b/i,
131
+ data_class: "messaging",
132
+ authority_floor: "read",
133
+ scope: "description",
134
+ },
135
+ // ─── system ─────────────────────────────────────────────────
136
+ {
137
+ pattern: /\b(get[_-]?env|environ(ment)?|process[_-]?(info|status)|sysinfo|system[_-]?info|hostname)\b/i,
138
+ data_class: "system",
139
+ authority_floor: "read",
140
+ scope: "either",
141
+ },
142
+ // ─── metadata / read-only ───────────────────────────────────
143
+ {
144
+ pattern: /\b(read[_-]?graph|search[_-]?nodes|open[_-]?nodes)\b/i,
145
+ data_class: "metadata",
146
+ authority_floor: "read",
147
+ scope: "name",
148
+ },
149
+ {
150
+ pattern: /\b(echo|get[_-]?(structured[_-]?content|annotated[_-]?message|resource[_-]?(links|reference)|sum|tiny[_-]?image))\b/i,
151
+ data_class: "metadata",
152
+ authority_floor: "read",
153
+ scope: "name",
154
+ },
155
+ {
156
+ pattern: /\b(toggle[_-]?(simulated[_-]?logging|subscriber[_-]?updates))\b/i,
157
+ data_class: "system",
158
+ authority_floor: "write",
159
+ scope: "name",
160
+ },
161
+ // ─── memory-server CRUD on a knowledge graph ─────────────────
162
+ {
163
+ pattern: /\b(create[_-]?(entities|relations))\b/i,
164
+ data_class: "metadata",
165
+ authority_floor: "write",
166
+ scope: "name",
167
+ },
168
+ {
169
+ pattern: /\b(add[_-]?observations)\b/i,
170
+ data_class: "metadata",
171
+ authority_floor: "write",
172
+ scope: "name",
173
+ },
174
+ {
175
+ pattern: /\b(delete[_-]?(entities|relations|observations))\b/i,
176
+ data_class: "metadata",
177
+ authority_floor: "destructive",
178
+ scope: "name",
179
+ },
180
+ // ─── thinking / opaque reasoning tools ──────────────────────
181
+ {
182
+ pattern: /\b(sequentialthinking|reason|plan|think)\b/i,
183
+ data_class: "metadata",
184
+ authority_floor: "read",
185
+ scope: "name",
186
+ },
187
+ // ─── trigger-shaped + simulate-shaped (test surfaces) ────────
188
+ {
189
+ pattern: /\b(trigger[_-]?(long[_-]?running[_-]?operation)?|simulate[_-]?(research[_-]?query))\b/i,
190
+ data_class: "metadata",
191
+ authority_floor: "write",
192
+ scope: "name",
193
+ },
194
+ ];
195
+ /** Rank the four authority levels so we can take the strongest signal. */
196
+ const AUTHORITY_RANK = {
197
+ read: 0,
198
+ write: 1,
199
+ destructive: 2,
200
+ privileged: 3,
201
+ };
202
+ /** Return the higher of two authority levels (lattice join). */
203
+ export function maxAuthority(a, b) {
204
+ return AUTHORITY_RANK[a] >= AUTHORITY_RANK[b] ? a : b;
205
+ }
206
+ /** Escalate authority by one step (read → write → destructive → privileged). */
207
+ export function escalateAuthority(a) {
208
+ switch (a) {
209
+ case "read":
210
+ return "write";
211
+ case "write":
212
+ return "destructive";
213
+ case "destructive":
214
+ return "privileged";
215
+ case "privileged":
216
+ return "privileged"; // saturates
217
+ }
218
+ }
219
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/classify/rules.ts"],"names":[],"mappings":"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"}