@integsec/agentic-pentest-proxy 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 (89) hide show
  1. package/README.md +731 -0
  2. package/dist/bin/integsec-agentic-pentest-proxy.d.ts +3 -0
  3. package/dist/bin/integsec-agentic-pentest-proxy.d.ts.map +1 -0
  4. package/dist/bin/integsec-agentic-pentest-proxy.js +51 -0
  5. package/dist/bin/integsec-agentic-pentest-proxy.js.map +1 -0
  6. package/dist/src/audit/azure-monitor.d.ts +22 -0
  7. package/dist/src/audit/azure-monitor.d.ts.map +1 -0
  8. package/dist/src/audit/azure-monitor.js +62 -0
  9. package/dist/src/audit/azure-monitor.js.map +1 -0
  10. package/dist/src/audit/cloudwatch.d.ts +20 -0
  11. package/dist/src/audit/cloudwatch.d.ts.map +1 -0
  12. package/dist/src/audit/cloudwatch.js +89 -0
  13. package/dist/src/audit/cloudwatch.js.map +1 -0
  14. package/dist/src/audit/gcp-logging.d.ts +23 -0
  15. package/dist/src/audit/gcp-logging.d.ts.map +1 -0
  16. package/dist/src/audit/gcp-logging.js +70 -0
  17. package/dist/src/audit/gcp-logging.js.map +1 -0
  18. package/dist/src/audit/index.d.ts +49 -0
  19. package/dist/src/audit/index.d.ts.map +1 -0
  20. package/dist/src/audit/index.js +79 -0
  21. package/dist/src/audit/index.js.map +1 -0
  22. package/dist/src/audit/local.d.ts +25 -0
  23. package/dist/src/audit/local.d.ts.map +1 -0
  24. package/dist/src/audit/local.js +51 -0
  25. package/dist/src/audit/local.js.map +1 -0
  26. package/dist/src/config.d.ts +25 -0
  27. package/dist/src/config.d.ts.map +1 -0
  28. package/dist/src/config.js +26 -0
  29. package/dist/src/config.js.map +1 -0
  30. package/dist/src/dns-resolver.d.ts +21 -0
  31. package/dist/src/dns-resolver.d.ts.map +1 -0
  32. package/dist/src/dns-resolver.js +68 -0
  33. package/dist/src/dns-resolver.js.map +1 -0
  34. package/dist/src/domain-matcher.d.ts +35 -0
  35. package/dist/src/domain-matcher.d.ts.map +1 -0
  36. package/dist/src/domain-matcher.js +97 -0
  37. package/dist/src/domain-matcher.js.map +1 -0
  38. package/dist/src/extractor.d.ts +30 -0
  39. package/dist/src/extractor.d.ts.map +1 -0
  40. package/dist/src/extractor.js +176 -0
  41. package/dist/src/extractor.js.map +1 -0
  42. package/dist/src/index.d.ts +8 -0
  43. package/dist/src/index.d.ts.map +1 -0
  44. package/dist/src/index.js +7 -0
  45. package/dist/src/index.js.map +1 -0
  46. package/dist/src/ip-matcher.d.ts +38 -0
  47. package/dist/src/ip-matcher.d.ts.map +1 -0
  48. package/dist/src/ip-matcher.js +128 -0
  49. package/dist/src/ip-matcher.js.map +1 -0
  50. package/dist/src/manifest-schema.d.ts +77 -0
  51. package/dist/src/manifest-schema.d.ts.map +1 -0
  52. package/dist/src/manifest-schema.js +34 -0
  53. package/dist/src/manifest-schema.js.map +1 -0
  54. package/dist/src/manifest.d.ts +3 -0
  55. package/dist/src/manifest.d.ts.map +1 -0
  56. package/dist/src/manifest.js +115 -0
  57. package/dist/src/manifest.js.map +1 -0
  58. package/dist/src/proxy.d.ts +16 -0
  59. package/dist/src/proxy.d.ts.map +1 -0
  60. package/dist/src/proxy.js +72 -0
  61. package/dist/src/proxy.js.map +1 -0
  62. package/dist/src/sanitizer.d.ts +19 -0
  63. package/dist/src/sanitizer.d.ts.map +1 -0
  64. package/dist/src/sanitizer.js +68 -0
  65. package/dist/src/sanitizer.js.map +1 -0
  66. package/dist/src/technique-checker.d.ts +50 -0
  67. package/dist/src/technique-checker.d.ts.map +1 -0
  68. package/dist/src/technique-checker.js +110 -0
  69. package/dist/src/technique-checker.js.map +1 -0
  70. package/dist/src/transports/http.d.ts +3 -0
  71. package/dist/src/transports/http.d.ts.map +1 -0
  72. package/dist/src/transports/http.js +67 -0
  73. package/dist/src/transports/http.js.map +1 -0
  74. package/dist/src/transports/stdio.d.ts +3 -0
  75. package/dist/src/transports/stdio.d.ts.map +1 -0
  76. package/dist/src/transports/stdio.js +50 -0
  77. package/dist/src/transports/stdio.js.map +1 -0
  78. package/dist/src/types.d.ts +43 -0
  79. package/dist/src/types.d.ts.map +1 -0
  80. package/dist/src/types.js +2 -0
  81. package/dist/src/types.js.map +1 -0
  82. package/dist/src/validator.d.ts +54 -0
  83. package/dist/src/validator.d.ts.map +1 -0
  84. package/dist/src/validator.js +200 -0
  85. package/dist/src/validator.js.map +1 -0
  86. package/examples/claude-desktop-config.json +15 -0
  87. package/examples/scope-manifest.json +18 -0
  88. package/examples/technique-map.json +10 -0
  89. package/package.json +58 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * TechniqueChecker
3
+ *
4
+ * Maps tool names to technique categories using exact-match lists and
5
+ * substring patterns. All comparisons are case-insensitive.
6
+ *
7
+ * Default categories:
8
+ * dos — denial-of-service flooding tools
9
+ * destructive — data-wiping / destructive tools
10
+ * social_engineering — phishing / credential-harvesting frameworks
11
+ *
12
+ * Custom mappings passed to the constructor are merged with the defaults
13
+ * (they do not replace them). Both exact lists and patterns are extended
14
+ * per-category.
15
+ */
16
+ // ---------------------------------------------------------------------------
17
+ // Default technique map
18
+ // ---------------------------------------------------------------------------
19
+ const DEFAULT_MAPPINGS = {
20
+ dos: {
21
+ exact: ["hping3", "slowloris", "udpflood", "synflood"],
22
+ patterns: ["flood", "dos"],
23
+ },
24
+ destructive: {
25
+ exact: [],
26
+ patterns: ["destroy", "wipe", "wiper"],
27
+ },
28
+ social_engineering: {
29
+ exact: ["gophish", "setoolkit", "evilginx", "modlishka"],
30
+ patterns: [],
31
+ },
32
+ };
33
+ // ---------------------------------------------------------------------------
34
+ // TechniqueChecker class
35
+ // ---------------------------------------------------------------------------
36
+ export class TechniqueChecker {
37
+ mappings;
38
+ /**
39
+ * @param customMappings Optional extra mappings to merge on top of the
40
+ * defaults. Keys that already exist in the defaults
41
+ * have their `exact` and `patterns` arrays extended;
42
+ * new keys add entirely new categories.
43
+ */
44
+ constructor(customMappings) {
45
+ this.mappings = TechniqueChecker.mergeMappings(DEFAULT_MAPPINGS, customMappings ?? {});
46
+ }
47
+ // -------------------------------------------------------------------------
48
+ // Public API
49
+ // -------------------------------------------------------------------------
50
+ /**
51
+ * Returns the technique category that `toolName` belongs to, or `null` if
52
+ * no category matches.
53
+ */
54
+ getCategory(toolName) {
55
+ if (toolName === "")
56
+ return null;
57
+ const lower = toolName.toLowerCase();
58
+ for (const [category, mapping] of Object.entries(this.mappings)) {
59
+ // 1. Exact match (O(n) over a small list — acceptable)
60
+ if (mapping.exact.some((name) => name.toLowerCase() === lower)) {
61
+ return category;
62
+ }
63
+ // 2. Substring pattern match
64
+ if (mapping.patterns.some((pattern) => lower.includes(pattern.toLowerCase()))) {
65
+ return category;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Returns `true` when `toolName` belongs to a category that appears in
72
+ * `excludedTechniques`, `false` otherwise (including when the tool is not
73
+ * categorised).
74
+ */
75
+ isBlocked(toolName, excludedTechniques) {
76
+ if (excludedTechniques.length === 0)
77
+ return false;
78
+ const category = this.getCategory(toolName);
79
+ if (category === null)
80
+ return false;
81
+ return excludedTechniques.includes(category);
82
+ }
83
+ // -------------------------------------------------------------------------
84
+ // Private helpers
85
+ // -------------------------------------------------------------------------
86
+ /**
87
+ * Merges `custom` mappings into `base`, extending per-category arrays rather
88
+ * than replacing them. Returns a deep-copied result so the originals are
89
+ * never mutated.
90
+ */
91
+ static mergeMappings(base, custom) {
92
+ // Deep-copy base
93
+ const merged = {};
94
+ for (const [cat, mapping] of Object.entries(base)) {
95
+ merged[cat] = { exact: [...mapping.exact], patterns: [...mapping.patterns] };
96
+ }
97
+ // Merge / extend with custom entries
98
+ for (const [cat, mapping] of Object.entries(custom)) {
99
+ if (merged[cat] === undefined) {
100
+ merged[cat] = { exact: [...mapping.exact], patterns: [...mapping.patterns] };
101
+ }
102
+ else {
103
+ merged[cat].exact.push(...mapping.exact);
104
+ merged[cat].patterns.push(...mapping.patterns);
105
+ }
106
+ }
107
+ return merged;
108
+ }
109
+ }
110
+ //# sourceMappingURL=technique-checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technique-checker.js","sourceRoot":"","sources":["../../src/technique-checker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ScopeEnforcementProxy } from "../proxy.js";
2
+ export declare function runHttpProxy(proxy: ScopeEnforcementProxy, upstreamUrl: string, port: number): Promise<void>;
3
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/transports/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,wBAAsB,YAAY,CAChC,KAAK,EAAE,qBAAqB,EAC5B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAwCf"}
@@ -0,0 +1,67 @@
1
+ import { createServer } from "http";
2
+ export async function runHttpProxy(proxy, upstreamUrl, port) {
3
+ const auditLogger = proxy.getAuditLogger();
4
+ auditLogger.start();
5
+ const server = createServer(async (req, res) => {
6
+ const body = await readBody(req);
7
+ let message;
8
+ try {
9
+ message = JSON.parse(body);
10
+ }
11
+ catch {
12
+ return forwardRaw(upstreamUrl, req, body, res);
13
+ }
14
+ const { forward, response } = await proxy.handleMessage(message);
15
+ if (!forward && response) {
16
+ res.writeHead(200, { "Content-Type": "application/json" });
17
+ res.end(JSON.stringify(response));
18
+ return;
19
+ }
20
+ return forwardRaw(upstreamUrl, req, body, res);
21
+ });
22
+ server.listen(port, () => {
23
+ console.error(`[integsec-agentic-pentest-proxy] HTTP proxy listening on port ${port}, forwarding to ${upstreamUrl}`);
24
+ });
25
+ process.on("SIGINT", async () => {
26
+ await auditLogger.stop();
27
+ server.close();
28
+ process.exit(0);
29
+ });
30
+ process.on("SIGTERM", async () => {
31
+ await auditLogger.stop();
32
+ server.close();
33
+ process.exit(0);
34
+ });
35
+ }
36
+ function readBody(req) {
37
+ return new Promise((resolve, reject) => {
38
+ const chunks = [];
39
+ req.on("data", (chunk) => chunks.push(chunk));
40
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
41
+ req.on("error", reject);
42
+ });
43
+ }
44
+ async function forwardRaw(upstreamUrl, originalReq, body, clientRes) {
45
+ try {
46
+ const url = new URL(originalReq.url ?? "/", upstreamUrl);
47
+ const headers = {};
48
+ for (const [key, value] of Object.entries(originalReq.headers)) {
49
+ if (typeof value === "string")
50
+ headers[key] = value;
51
+ }
52
+ delete headers.host;
53
+ const resp = await fetch(url.toString(), {
54
+ method: originalReq.method ?? "POST",
55
+ headers,
56
+ body: originalReq.method !== "GET" ? body : undefined,
57
+ });
58
+ clientRes.writeHead(resp.status, Object.fromEntries(resp.headers.entries()));
59
+ const respBody = await resp.text();
60
+ clientRes.end(respBody);
61
+ }
62
+ catch (err) {
63
+ clientRes.writeHead(502, { "Content-Type": "application/json" });
64
+ clientRes.end(JSON.stringify({ error: `Upstream error: ${err.message}` }));
65
+ }
66
+ }
67
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/transports/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,MAAM,CAAC;AAG/E,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAA4B,EAC5B,WAAmB,EACnB,IAAY;IAEZ,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IAC3C,WAAW,CAAC,KAAK,EAAE,CAAC;IAEpB,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAE,EAAE;QAC9E,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;QAEjC,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAEjE,IAAI,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClC,OAAO;QACT,CAAC;QAED,OAAO,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,iEAAiE,IAAI,mBAAmB,WAAW,EAAE,CAAC,CAAC;IACvH,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,GAAoB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACtD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,WAAmB,EACnB,WAA4B,EAC5B,IAAY,EACZ,SAAyB;IAEzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,IAAI,GAAG,EAAE,WAAW,CAAC,CAAC;QACzD,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtD,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC;QAEpB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,MAAM;YACpC,OAAO;YACP,IAAI,EAAE,WAAW,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;SACtD,CAAC,CAAC;QAEH,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC7E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACnC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACjE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,mBAAmB,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ScopeEnforcementProxy } from "../proxy.js";
2
+ export declare function runStdioProxy(proxy: ScopeEnforcementProxy, upstreamCommand: string, upstreamArgs?: string[]): Promise<void>;
3
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../src/transports/stdio.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,wBAAsB,aAAa,CACjC,KAAK,EAAE,qBAAqB,EAC5B,eAAe,EAAE,MAAM,EACvB,YAAY,GAAE,MAAM,EAAO,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoDf"}
@@ -0,0 +1,50 @@
1
+ import { spawn } from "child_process";
2
+ import { createInterface } from "readline";
3
+ export async function runStdioProxy(proxy, upstreamCommand, upstreamArgs = []) {
4
+ const upstream = spawn(upstreamCommand, upstreamArgs, {
5
+ stdio: ["pipe", "pipe", "inherit"],
6
+ });
7
+ if (!upstream.stdin || !upstream.stdout) {
8
+ throw new Error("Failed to open stdio pipes to upstream MCP server");
9
+ }
10
+ const auditLogger = proxy.getAuditLogger();
11
+ auditLogger.start();
12
+ const agentInput = createInterface({ input: process.stdin, crlfDelay: Infinity });
13
+ const upstreamOutput = createInterface({ input: upstream.stdout, crlfDelay: Infinity });
14
+ // Agent → Proxy → Upstream
15
+ agentInput.on("line", async (line) => {
16
+ try {
17
+ const message = JSON.parse(line);
18
+ const { forward, response } = await proxy.handleMessage(message);
19
+ if (forward) {
20
+ upstream.stdin.write(line + "\n");
21
+ }
22
+ else if (response) {
23
+ process.stdout.write(JSON.stringify(response) + "\n");
24
+ }
25
+ }
26
+ catch {
27
+ // Malformed JSON — pass through conservatively
28
+ upstream.stdin.write(line + "\n");
29
+ }
30
+ });
31
+ // Upstream → Agent (pass through all responses)
32
+ upstreamOutput.on("line", (line) => {
33
+ process.stdout.write(line + "\n");
34
+ });
35
+ upstream.on("exit", async (code) => {
36
+ await auditLogger.stop();
37
+ process.exit(code ?? 0);
38
+ });
39
+ process.on("SIGINT", async () => {
40
+ await auditLogger.stop();
41
+ upstream.kill();
42
+ process.exit(0);
43
+ });
44
+ process.on("SIGTERM", async () => {
45
+ await auditLogger.stop();
46
+ upstream.kill();
47
+ process.exit(0);
48
+ });
49
+ }
50
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG3C,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAA4B,EAC5B,eAAuB,EACvB,eAAyB,EAAE;IAE3B,MAAM,QAAQ,GAAiB,KAAK,CAAC,eAAe,EAAE,YAAY,EAAE;QAClE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC;KACnC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IAC3C,WAAW,CAAC,KAAK,EAAE,CAAC;IAEpB,MAAM,UAAU,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClF,MAAM,cAAc,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAExF,2BAA2B;IAC3B,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACnC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YACjE,IAAI,OAAO,EAAE,CAAC;gBACZ,QAAQ,CAAC,KAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;YACrC,CAAC;iBAAM,IAAI,QAAQ,EAAE,CAAC;gBACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,+CAA+C;YAC/C,QAAQ,CAAC,KAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QACrC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACjC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,43 @@
1
+ export type Decision = "ALLOWED" | "BLOCKED_EXCLUDED_TARGET" | "BLOCKED_NOT_IN_SCOPE" | "BLOCKED_OUTSIDE_WINDOW" | "BLOCKED_TECHNIQUE" | "BLOCKED_DNS_FAILED" | "ALLOWED_NO_TARGET";
2
+ export interface ValidationResult {
3
+ decision: Decision;
4
+ reason: string;
5
+ matchedScopeItem?: string;
6
+ extractedTarget?: string;
7
+ resolvedIps?: string[];
8
+ durationMs: number;
9
+ }
10
+ export interface AuditEntry {
11
+ timestamp: string;
12
+ engagement_id: string;
13
+ client: string;
14
+ operator: string;
15
+ tool_name: string;
16
+ tool_parameters: Record<string, unknown>;
17
+ extracted_target: string | null;
18
+ resolved_ips: string[];
19
+ decision: Decision;
20
+ decision_reason: string;
21
+ matched_scope_item: string | null;
22
+ duration_ms: number;
23
+ proxy_version: string;
24
+ }
25
+ export interface ScopeManifest {
26
+ engagement_id: string;
27
+ client: string;
28
+ operator: string;
29
+ authorized_targets: {
30
+ ip_ranges: string[];
31
+ domains: string[];
32
+ urls: string[];
33
+ cloud_accounts: string[];
34
+ };
35
+ excluded_targets: string[];
36
+ authorized_techniques: string[];
37
+ excluded_techniques: string[];
38
+ engagement_window: {
39
+ start: string;
40
+ end: string;
41
+ };
42
+ }
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAChB,SAAS,GACT,yBAAyB,GACzB,sBAAsB,GACtB,wBAAwB,GACxB,mBAAmB,GACnB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE;QAClB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,iBAAiB,EAAE;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * validator.ts
3
+ *
4
+ * ScopeValidator — the core orchestrator that ties all validation together.
5
+ *
6
+ * Validation steps (in exact order):
7
+ * 1. Technique block check
8
+ * 2. Time window check
9
+ * 3. Target extraction
10
+ * 4. If no target found → ALLOWED_NO_TARGET
11
+ * 5. DNS resolution (fail closed on NXDOMAIN)
12
+ * 6. Exclusion check (exclusions win over authorizations)
13
+ * 7. Authorization check (domain pattern match OR IP CIDR match)
14
+ * 8. No match → BLOCKED_NOT_IN_SCOPE
15
+ *
16
+ * SECURITY NOTE: This module is the gatekeeper for all tool calls. Every
17
+ * check order matters — do not reorder without updating tests.
18
+ */
19
+ import type { ScopeManifest, ValidationResult } from "./types.js";
20
+ import { DnsResolver } from "./dns-resolver.js";
21
+ import { TechniqueChecker } from "./technique-checker.js";
22
+ export declare class ScopeValidator {
23
+ private readonly manifest;
24
+ private readonly dnsResolver;
25
+ private readonly techniqueChecker;
26
+ constructor(manifest: ScopeManifest, dnsResolver?: DnsResolver, techniqueChecker?: TechniqueChecker);
27
+ validate(toolName: string, params: Record<string, unknown>): Promise<ValidationResult>;
28
+ /**
29
+ * Check whether `target` matches any entry in `excluded` (case-insensitive
30
+ * exact string match).
31
+ */
32
+ private isExcluded;
33
+ /**
34
+ * Find the first authorized domain pattern that matches `hostname`.
35
+ * Returns the matching pattern string, or null if no match.
36
+ */
37
+ private findMatchingDomain;
38
+ /**
39
+ * Find the first authorized CIDR range that contains `ip`.
40
+ * Returns the matching CIDR string, or null if no match.
41
+ *
42
+ * Note: RFC-1918 and cloud metadata IPs are only allowed if they explicitly
43
+ * appear within an authorized ip_range entry. This function performs an
44
+ * exact `isIpInCidr` check — there is no implicit deny for private ranges
45
+ * because the exclusion of a private IP from the authorized list naturally
46
+ * means it will fall through to BLOCKED_NOT_IN_SCOPE in Step 8.
47
+ */
48
+ private findMatchingIpRange;
49
+ /**
50
+ * Build a ValidationResult, automatically populating durationMs.
51
+ */
52
+ private result;
53
+ }
54
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;gBAGlD,QAAQ,EAAE,aAAa,EACvB,WAAW,CAAC,EAAE,WAAW,EACzB,gBAAgB,CAAC,EAAE,gBAAgB;IAO/B,QAAQ,CACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,gBAAgB,CAAC;IA0I5B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;IACH,OAAO,CAAC,MAAM;CASf"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * validator.ts
3
+ *
4
+ * ScopeValidator — the core orchestrator that ties all validation together.
5
+ *
6
+ * Validation steps (in exact order):
7
+ * 1. Technique block check
8
+ * 2. Time window check
9
+ * 3. Target extraction
10
+ * 4. If no target found → ALLOWED_NO_TARGET
11
+ * 5. DNS resolution (fail closed on NXDOMAIN)
12
+ * 6. Exclusion check (exclusions win over authorizations)
13
+ * 7. Authorization check (domain pattern match OR IP CIDR match)
14
+ * 8. No match → BLOCKED_NOT_IN_SCOPE
15
+ *
16
+ * SECURITY NOTE: This module is the gatekeeper for all tool calls. Every
17
+ * check order matters — do not reorder without updating tests.
18
+ */
19
+ import { extractTarget } from "./extractor.js";
20
+ import { DnsResolver } from "./dns-resolver.js";
21
+ import { isIpInCidr } from "./ip-matcher.js";
22
+ import { matchesDomain } from "./domain-matcher.js";
23
+ import { TechniqueChecker } from "./technique-checker.js";
24
+ export class ScopeValidator {
25
+ manifest;
26
+ dnsResolver;
27
+ techniqueChecker;
28
+ constructor(manifest, dnsResolver, techniqueChecker) {
29
+ this.manifest = manifest;
30
+ this.dnsResolver = dnsResolver ?? new DnsResolver();
31
+ this.techniqueChecker = techniqueChecker ?? new TechniqueChecker();
32
+ }
33
+ async validate(toolName, params) {
34
+ const startTime = Date.now();
35
+ // ─────────────────────────────────────────────────────────────────────────
36
+ // Step 1: Technique block check
37
+ // ─────────────────────────────────────────────────────────────────────────
38
+ if (this.techniqueChecker.isBlocked(toolName, this.manifest.excluded_techniques)) {
39
+ const category = this.techniqueChecker.getCategory(toolName) ?? "unknown";
40
+ return this.result(startTime, {
41
+ decision: "BLOCKED_TECHNIQUE",
42
+ reason: `Tool "${toolName}" belongs to the blocked technique category "${category}".`,
43
+ });
44
+ }
45
+ // ─────────────────────────────────────────────────────────────────────────
46
+ // Step 2: Time window check
47
+ // ─────────────────────────────────────────────────────────────────────────
48
+ const now = Date.now();
49
+ const windowStart = Date.parse(this.manifest.engagement_window.start);
50
+ const windowEnd = Date.parse(this.manifest.engagement_window.end);
51
+ if (now < windowStart || now > windowEnd) {
52
+ return this.result(startTime, {
53
+ decision: "BLOCKED_OUTSIDE_WINDOW",
54
+ reason: `Current time is outside the engagement window (${this.manifest.engagement_window.start} – ${this.manifest.engagement_window.end}).`,
55
+ });
56
+ }
57
+ // ─────────────────────────────────────────────────────────────────────────
58
+ // Step 3: Target extraction
59
+ // ─────────────────────────────────────────────────────────────────────────
60
+ const extraction = extractTarget(toolName, params);
61
+ // ─────────────────────────────────────────────────────────────────────────
62
+ // Step 4: If no target found → ALLOWED_NO_TARGET
63
+ // ─────────────────────────────────────────────────────────────────────────
64
+ if (extraction === null) {
65
+ return this.result(startTime, {
66
+ decision: "ALLOWED_NO_TARGET",
67
+ reason: "No target-like parameter found in tool call; allowing by default.",
68
+ });
69
+ }
70
+ const target = extraction.target;
71
+ // ─────────────────────────────────────────────────────────────────────────
72
+ // Step 5: DNS resolution
73
+ // ─────────────────────────────────────────────────────────────────────────
74
+ const resolvedIps = await this.dnsResolver.resolve(target);
75
+ // For hostnames (not raw IPs), an empty resolution means NXDOMAIN — fail closed.
76
+ // For raw IPs the resolver returns [ip] so resolvedIps will never be empty.
77
+ if (resolvedIps.length === 0) {
78
+ return this.result(startTime, {
79
+ decision: "BLOCKED_DNS_FAILED",
80
+ reason: `DNS resolution for "${target}" returned no addresses (NXDOMAIN or resolution failure). Failing closed.`,
81
+ extractedTarget: target,
82
+ resolvedIps: [],
83
+ });
84
+ }
85
+ // ─────────────────────────────────────────────────────────────────────────
86
+ // Step 6: Exclusion check (exclusions win — checked before authorization)
87
+ // ─────────────────────────────────────────────────────────────────────────
88
+ const excluded = this.manifest.excluded_targets;
89
+ // Check hostname/IP target against excluded list (case-insensitive exact match)
90
+ if (this.isExcluded(target, excluded)) {
91
+ return this.result(startTime, {
92
+ decision: "BLOCKED_EXCLUDED_TARGET",
93
+ reason: `Target "${target}" matches an excluded target.`,
94
+ extractedTarget: target,
95
+ resolvedIps,
96
+ });
97
+ }
98
+ // Check each resolved IP against excluded list
99
+ for (const ip of resolvedIps) {
100
+ if (this.isExcluded(ip, excluded)) {
101
+ return this.result(startTime, {
102
+ decision: "BLOCKED_EXCLUDED_TARGET",
103
+ reason: `Resolved IP "${ip}" for target "${target}" matches an excluded target.`,
104
+ extractedTarget: target,
105
+ resolvedIps,
106
+ });
107
+ }
108
+ }
109
+ // ─────────────────────────────────────────────────────────────────────────
110
+ // Step 7: Authorization check
111
+ // ─────────────────────────────────────────────────────────────────────────
112
+ const authorizedIpRanges = this.manifest.authorized_targets.ip_ranges;
113
+ const authorizedDomains = this.manifest.authorized_targets.domains;
114
+ // 7a: Check hostname against authorized domain patterns
115
+ const matchedDomain = this.findMatchingDomain(target, authorizedDomains);
116
+ if (matchedDomain !== null) {
117
+ return this.result(startTime, {
118
+ decision: "ALLOWED",
119
+ reason: `Target "${target}" matches authorized domain pattern "${matchedDomain}".`,
120
+ matchedScopeItem: matchedDomain,
121
+ extractedTarget: target,
122
+ resolvedIps,
123
+ });
124
+ }
125
+ // 7b: Check resolved IPs against authorized IP ranges.
126
+ // For RFC-1918 and cloud metadata IPs: only allow if explicitly in authorized ip_ranges.
127
+ for (const ip of resolvedIps) {
128
+ const matchedRange = this.findMatchingIpRange(ip, authorizedIpRanges);
129
+ if (matchedRange !== null) {
130
+ return this.result(startTime, {
131
+ decision: "ALLOWED",
132
+ reason: `Resolved IP "${ip}" for target "${target}" is in authorized range "${matchedRange}".`,
133
+ matchedScopeItem: matchedRange,
134
+ extractedTarget: target,
135
+ resolvedIps,
136
+ });
137
+ }
138
+ }
139
+ // ─────────────────────────────────────────────────────────────────────────
140
+ // Step 8: No match → BLOCKED_NOT_IN_SCOPE
141
+ // ─────────────────────────────────────────────────────────────────────────
142
+ return this.result(startTime, {
143
+ decision: "BLOCKED_NOT_IN_SCOPE",
144
+ reason: `Target "${target}" (resolved IPs: ${resolvedIps.join(", ")}) does not match any authorized target or domain.`,
145
+ extractedTarget: target,
146
+ resolvedIps,
147
+ });
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Private helpers
151
+ // ---------------------------------------------------------------------------
152
+ /**
153
+ * Check whether `target` matches any entry in `excluded` (case-insensitive
154
+ * exact string match).
155
+ */
156
+ isExcluded(target, excluded) {
157
+ const lower = target.toLowerCase();
158
+ return excluded.some((entry) => entry.toLowerCase() === lower);
159
+ }
160
+ /**
161
+ * Find the first authorized domain pattern that matches `hostname`.
162
+ * Returns the matching pattern string, or null if no match.
163
+ */
164
+ findMatchingDomain(hostname, patterns) {
165
+ for (const pattern of patterns) {
166
+ if (matchesDomain(hostname, pattern)) {
167
+ return pattern;
168
+ }
169
+ }
170
+ return null;
171
+ }
172
+ /**
173
+ * Find the first authorized CIDR range that contains `ip`.
174
+ * Returns the matching CIDR string, or null if no match.
175
+ *
176
+ * Note: RFC-1918 and cloud metadata IPs are only allowed if they explicitly
177
+ * appear within an authorized ip_range entry. This function performs an
178
+ * exact `isIpInCidr` check — there is no implicit deny for private ranges
179
+ * because the exclusion of a private IP from the authorized list naturally
180
+ * means it will fall through to BLOCKED_NOT_IN_SCOPE in Step 8.
181
+ */
182
+ findMatchingIpRange(ip, ranges) {
183
+ for (const range of ranges) {
184
+ if (isIpInCidr(ip, range)) {
185
+ return range;
186
+ }
187
+ }
188
+ return null;
189
+ }
190
+ /**
191
+ * Build a ValidationResult, automatically populating durationMs.
192
+ */
193
+ result(startTime, fields) {
194
+ return {
195
+ ...fields,
196
+ durationMs: Date.now() - startTime,
197
+ };
198
+ }
199
+ }
200
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ {
2
+ "mcpServers": {
3
+ "kali-mcp-scoped": {
4
+ "command": "npx",
5
+ "args": ["-y", "@integsec/agentic-pentest-proxy"],
6
+ "env": {
7
+ "SCOPE_MANIFEST_PATH": "/path/to/scope.json",
8
+ "MCP_TRANSPORT": "stdio",
9
+ "UPSTREAM_MCP_COMMAND": "npx",
10
+ "UPSTREAM_MCP_ARGS": "-y,@anthropic/kali-mcp",
11
+ "AUDIT_LOG_PATH": "./audit/"
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "engagement_id": "ENG-2025-0142",
3
+ "client": "Acme Corp",
4
+ "operator": "operator@integsec.com",
5
+ "authorized_targets": {
6
+ "ip_ranges": ["10.10.10.0/24", "203.0.113.0/28"],
7
+ "domains": ["*.acme.com", "acme-staging.example.com"],
8
+ "urls": ["https://app.acme.com", "https://api.acme.com"],
9
+ "cloud_accounts": ["aws:123456789012", "azure:sub-xxxx"]
10
+ },
11
+ "excluded_targets": ["203.0.113.5", "hr.acme.com"],
12
+ "authorized_techniques": ["recon", "web_app", "api_testing"],
13
+ "excluded_techniques": ["dos", "destructive", "social_engineering"],
14
+ "engagement_window": {
15
+ "start": "2026-03-26T08:00:00Z",
16
+ "end": "2026-04-09T17:00:00Z"
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "wireless": {
3
+ "exact": ["aircrack-ng", "wifite", "kismet"],
4
+ "patterns": ["wifi", "wireless"]
5
+ },
6
+ "physical": {
7
+ "exact": ["lockpick_sim"],
8
+ "patterns": ["physical"]
9
+ }
10
+ }