@integsec/agentic-pentest-proxy 0.3.0 → 0.3.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 (37) hide show
  1. package/README.md +1 -1
  2. package/cloudflare/src/worker.mjs +7 -7
  3. package/cloudflare/wrangler.jsonc +2 -1
  4. package/dist/bin/integsec-agentic-pentest-bridge.js +2 -2
  5. package/dist/bin/integsec-agentic-pentest-gateway.js +4 -2
  6. package/dist/bin/integsec-agentic-pentest-gateway.js.map +1 -1
  7. package/dist/src/config.js +1 -1
  8. package/dist/src/gateway/admin.html +10 -10
  9. package/dist/src/gateway/admin.js +194 -56
  10. package/dist/src/gateway/auth.d.ts +5 -1
  11. package/dist/src/gateway/auth.d.ts.map +1 -1
  12. package/dist/src/gateway/auth.js +12 -3
  13. package/dist/src/gateway/auth.js.map +1 -1
  14. package/dist/src/gateway/runtime.d.ts +3 -3
  15. package/dist/src/gateway/runtime.d.ts.map +1 -1
  16. package/dist/src/gateway/runtime.js +28 -18
  17. package/dist/src/gateway/runtime.js.map +1 -1
  18. package/dist/src/gateway/server.d.ts +2 -1
  19. package/dist/src/gateway/server.d.ts.map +1 -1
  20. package/dist/src/gateway/server.js +170 -21
  21. package/dist/src/gateway/server.js.map +1 -1
  22. package/dist/src/gateway/state.d.ts +24 -4
  23. package/dist/src/gateway/state.d.ts.map +1 -1
  24. package/dist/src/gateway/state.js +136 -12
  25. package/dist/src/gateway/state.js.map +1 -1
  26. package/dist/src/index.d.ts +1 -1
  27. package/dist/src/index.js +1 -1
  28. package/dist/src/proxy.d.ts +1 -1
  29. package/dist/src/proxy.d.ts.map +1 -1
  30. package/dist/src/proxy.js +2 -2
  31. package/dist/src/proxy.js.map +1 -1
  32. package/dist/src/validator.d.ts +2 -1
  33. package/dist/src/validator.d.ts.map +1 -1
  34. package/dist/src/validator.js +9 -1
  35. package/dist/src/validator.js.map +1 -1
  36. package/docs/managed-gateway.md +14 -10
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@ An MCP proxy that checks tool calls against a pentest scope manifest before forw
4
4
 
5
5
  **Requires Node.js 22 or newer.** The proxy supports stdio tools, SSE upstreams such as Burp Suite, and HTTP upstreams.
6
6
 
7
- **Managing several tools?** The [managed gateway](docs/managed-gateway.md) adds a browser UI for tool toggles and scope drafts, one MCP URL for ChatGPT and Claude, a local Burp route, and per-scope execution profiles for local models.
7
+ **Managing several tools?** The [managed gateway](docs/managed-gateway.md) adds a browser UI with curated MCP server presets, scope examples and technique checkboxes, administrator approval and assignment, and a separate engagement choice for each user. It supports one hosted MCP URL for ChatGPT and Claude and a local Burp route.
8
8
 
9
9
  ## Quick start: add an MCP tool
10
10
 
@@ -20,7 +20,7 @@ export class PentestGatewayContainer extends Container {
20
20
 
21
21
  constructor(ctx, env) {
22
22
  super(ctx, env);
23
- for (const name of ['GATEWAY_ADMIN_TOKEN', 'INTERNAL_TOKEN', 'CLOUDFLARE_TEAM_DOMAIN', 'CLOUDFLARE_ACCESS_AUD', 'AUDIT_WEBHOOK_URL']) {
23
+ for (const name of ['GATEWAY_BOOTSTRAP_ADMIN_EMAILS', 'INTERNAL_TOKEN', 'CLOUDFLARE_TEAM_DOMAIN', 'CLOUDFLARE_ACCESS_AUD', 'AUDIT_WEBHOOK_URL']) {
24
24
  if (!env[name]) throw new Error(`Missing required Worker secret: ${name}`);
25
25
  }
26
26
  this.envVars = {
@@ -31,7 +31,7 @@ export class PentestGatewayContainer extends Container {
31
31
  GATEWAY_AUTH_MODE: 'cloudflare',
32
32
  CLOUDFLARE_TEAM_DOMAIN: env.CLOUDFLARE_TEAM_DOMAIN,
33
33
  CLOUDFLARE_ACCESS_AUD: env.CLOUDFLARE_ACCESS_AUD,
34
- GATEWAY_ADMIN_TOKEN: env.GATEWAY_ADMIN_TOKEN,
34
+ GATEWAY_BOOTSTRAP_ADMIN_EMAILS: env.GATEWAY_BOOTSTRAP_ADMIN_EMAILS,
35
35
  GATEWAY_INTERNAL_TOKEN: env.INTERNAL_TOKEN,
36
36
  AUDIT_WEBHOOK_URL: env.AUDIT_WEBHOOK_URL,
37
37
  AUDIT_WEBHOOK_TOKEN: env.INTERNAL_TOKEN,
@@ -68,7 +68,7 @@ export class PentestGatewayContainer extends Container {
68
68
  }
69
69
 
70
70
  async persistMutation(request) {
71
- const previous = await this.ctx.storage.get('gateway-state') ?? { version: 1, upstreams: [], scopes: [], activeScopeId: null };
71
+ const previous = await this.ctx.storage.get('gateway-state') ?? { version: 2, upstreams: [], scopes: [], selectedScopeByUser: {} };
72
72
  const response = await this.containerFetch(request);
73
73
  if (!response.ok) return response;
74
74
  const currentResponse = await this.containerFetch('http://localhost:9091/__internal/state', {
@@ -110,10 +110,10 @@ export class PentestGatewayContainer extends Container {
110
110
  }
111
111
 
112
112
  async listAudit(request) {
113
- if (!tokenMatches(request.headers.get('authorization'), this.env.GATEWAY_ADMIN_TOKEN)) return text('Admin token required', 401);
114
- const check = await this.containerFetch('http://localhost:9091/api/state', { headers: request.headers });
115
- await check.body?.cancel();
116
- if (check.status !== 200) return text('Access assertion required', 403);
113
+ const check = await this.containerFetch('http://localhost:9091/api/session', { headers: request.headers });
114
+ if (check.status !== 200) { await check.body?.cancel(); return text('Administrator access required', 403); }
115
+ const session = await check.json();
116
+ if (session.role !== 'admin') return text('Administrator access required', 403);
117
117
  const entries = await this.ctx.storage.list({ prefix: 'audit:', reverse: true, limit: 100 });
118
118
  return json({ entries: [...entries.values()] });
119
119
  }
@@ -4,12 +4,13 @@
4
4
  "main": "src/worker.mjs",
5
5
  "compatibility_date": "2026-11-07",
6
6
  "workers_dev": true,
7
+ "preview_urls": false,
7
8
  "routes": [{ "pattern": "agentic-proxy.pentestprepper.com", "custom_domain": true }],
8
9
  "observability": { "enabled": true },
9
10
  "containers": [
10
11
  {
11
12
  "class_name": "PentestGatewayContainer",
12
- "image": "docker.io/integsec/agentic-pentest-mcp-proxy:0.3.0",
13
+ "image": "docker.io/integsec/agentic-pentest-mcp-proxy:0.3.2",
13
14
  "instance_type": "basic",
14
15
  "max_instances": 1
15
16
  }
@@ -12,9 +12,9 @@ const url = new URL(process.argv[2] ?? process.env.GATEWAY_URL ?? "http://127.0.
12
12
  if (!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) || url.protocol !== "http:") {
13
13
  throw new Error("The local bridge only accepts a loopback HTTP gateway URL");
14
14
  }
15
- const upstream = new Client({ name: "agentic-pentest-bridge", version: "0.3.0" });
15
+ const upstream = new Client({ name: "agentic-pentest-bridge", version: "0.3.2" });
16
16
  await upstream.connect(new StreamableHTTPClientTransport(url));
17
- const bridge = new Server({ name: "agentic-pentest-local", version: "0.3.0" }, { capabilities: { tools: {} } });
17
+ const bridge = new Server({ name: "agentic-pentest-local", version: "0.3.2" }, { capabilities: { tools: {} } });
18
18
  bridge.setRequestHandler(ListToolsRequestSchema, async (request) => upstream.listTools(request.params));
19
19
  bridge.setRequestHandler(CallToolRequestSchema, async (request) => upstream.callTool(request.params));
20
20
  await bridge.connect(new StdioServerTransport());
@@ -6,10 +6,11 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
6
6
 
7
7
  Manage MCP upstreams and engagement scopes in a browser, then connect an MCP client at /mcp.
8
8
 
9
- Required: GATEWAY_ADMIN_TOKEN (at least 32 characters).
9
+ Local mode: GATEWAY_ADMIN_TOKEN (at least 32 characters).
10
10
  Optional: GATEWAY_HOST (127.0.0.1), GATEWAY_PORT (9091), GATEWAY_STATE_PATH (./gateway/state.json).
11
11
  Hosted with Cloudflare Access: GATEWAY_PUBLIC_URL, GATEWAY_AUTH_MODE=cloudflare,
12
- CLOUDFLARE_TEAM_DOMAIN, CLOUDFLARE_ACCESS_AUD.
12
+ CLOUDFLARE_TEAM_DOMAIN, CLOUDFLARE_ACCESS_AUD,
13
+ GATEWAY_BOOTSTRAP_ADMIN_EMAILS (comma-separated).
13
14
  Hosted with direct Entra: GATEWAY_PUBLIC_URL, ENTRA_TENANT, ENTRA_AUDIENCE, ENTRA_SCOPE.
14
15
 
15
16
  Run: integsec-agentic-pentest-gateway
@@ -22,6 +23,7 @@ const gateway = createGateway({
22
23
  port: Number(process.env.GATEWAY_PORT ?? "9091"),
23
24
  statePath: process.env.GATEWAY_STATE_PATH ?? "./gateway/state.json",
24
25
  adminToken: process.env.GATEWAY_ADMIN_TOKEN ?? "",
26
+ bootstrapAdminEmails: (process.env.GATEWAY_BOOTSTRAP_ADMIN_EMAILS ?? "").split(",").map((value) => value.trim()).filter(Boolean),
25
27
  publicUrl: process.env.GATEWAY_PUBLIC_URL,
26
28
  entraTenant: process.env.ENTRA_TENANT,
27
29
  entraAudience: process.env.ENTRA_AUDIENCE,
@@ -1 +1 @@
1
- {"version":3,"file":"integsec-agentic-pentest-gateway.js","sourceRoot":"","sources":["../../bin/integsec-agentic-pentest-gateway.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;CAYb,CAAC,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,OAAO,GAAG,aAAa,CAAC;IAC5B,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW;IAC7C,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC;IAChD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,sBAAsB;IACnE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE;IACjD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;IACzC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;IACrC,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;IACzC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW;IACnC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO;IACjF,oBAAoB,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;IACxD,mBAAmB,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB;IACtD,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;IACjD,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;IAC9C,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;CACnD,CAAC,CAAC;AAEH,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;AACvB,OAAO,CAAC,KAAK,CAAC,0BAA0B,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC,CAAC;AACzH,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACvD,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"integsec-agentic-pentest-gateway.js","sourceRoot":"","sources":["../../bin/integsec-agentic-pentest-gateway.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;CAab,CAAC,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,OAAO,GAAG,aAAa,CAAC;IAC5B,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW;IAC7C,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC;IAChD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,sBAAsB;IACnE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE;IACjD,oBAAoB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAChI,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;IACzC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;IACrC,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;IACzC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW;IACnC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO;IACjF,oBAAoB,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;IACxD,mBAAmB,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB;IACtD,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;IACjD,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;IAC9C,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;CACnD,CAAC,CAAC;AAEH,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;AACvB,OAAO,CAAC,KAAK,CAAC,0BAA0B,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC,CAAC;AACzH,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACvD,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC"}
@@ -50,7 +50,7 @@ export function loadConfig() {
50
50
  techniqueMapPath: process.env.TECHNIQUE_MAP_PATH,
51
51
  dnsCacheTtl: parseInt(process.env.DNS_CACHE_TTL || "60", 10),
52
52
  logLevel: process.env.LOG_LEVEL || "INFO",
53
- proxyVersion: process.env.PROXY_VERSION || "0.3.0",
53
+ proxyVersion: process.env.PROXY_VERSION || "0.3.2",
54
54
  };
55
55
  }
56
56
  //# sourceMappingURL=config.js.map
@@ -4,18 +4,18 @@
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1">
5
5
  <title>IntegSec MCP Gateway</title>
6
6
  <style>
7
- :root{font:15px system-ui,sans-serif;color:#e8f0f2;background:#0b1218}
8
- *{box-sizing:border-box}body{margin:0}header{padding:22px 5vw;background:#10202b;border-bottom:1px solid #234052;display:flex;justify-content:space-between;align-items:center;gap:20px}
9
- h1{font-size:21px;margin:0}h2{font-size:18px;margin:0 0 14px}h3{font-size:15px;margin:16px 0 6px}p{color:#a8bdc8;line-height:1.45}main{max-width:1200px;margin:30px auto;padding:0 20px;display:grid;grid-template-columns:1fr 1fr;gap:20px}.card{background:#14222c;border:1px solid #294454;border-radius:13px;padding:24px}.wide{grid-column:1/-1}
10
- input,select,textarea,button{font:inherit}input,select,textarea{width:100%;background:#0b1720;color:#fff;border:1px solid #426073;border-radius:7px;padding:9px;margin:5px 0 12px}textarea{min-height:70px}label{display:block;color:#c1d0d8;font-weight:600;font-size:13px}button{border:0;border-radius:7px;background:#26bfa4;color:#081914;padding:9px 14px;font-weight:700;cursor:pointer;margin:4px 7px 4px 0}button.secondary{background:#304b5a;color:#e8f0f2}button.danger{background:#6d3541;color:#fff}button:disabled{opacity:.5;cursor:not-allowed}.grid{display:grid;grid-template-columns:1fr 1fr;gap:0 14px}.row{border-top:1px solid #294454;padding:12px 0;display:flex;align-items:center;justify-content:space-between;gap:12px}.muted{font-size:12px;color:#93acb9}#status{padding:8px 5vw;color:#8ee7ce;min-height:35px}#status.error{color:#ff9e9e}code{color:#a6e7db}@media(max-width:800px){main{display:block}.card{margin-bottom:20px}.grid{display:block}}
7
+ :root{font:15px system-ui,sans-serif;color:#e8f0f2;background:#0b1218}*{box-sizing:border-box}body{margin:0}header{padding:22px 5vw;background:#10202b;border-bottom:1px solid #234052;display:flex;justify-content:space-between;align-items:center;gap:20px}h1{font-size:21px;margin:0}h2{font-size:18px;margin:0 0 14px}h3{font-size:15px;margin:16px 0 6px}p{color:#a8bdc8;line-height:1.45}main{max-width:1200px;margin:30px auto;padding:0 20px;display:grid;grid-template-columns:1fr 1fr;gap:20px}.card{background:#14222c;border:1px solid #294454;border-radius:13px;padding:24px}.wide{grid-column:1/-1}input,select,textarea,button{font:inherit}input:not([type=checkbox]),select,textarea{width:100%;background:#0b1720;color:#fff;border:1px solid #426073;border-radius:7px;padding:9px;margin:5px 0 12px}textarea{min-height:70px}label{display:block;color:#c1d0d8;font-weight:600;font-size:13px}button{border:0;border-radius:7px;background:#26bfa4;color:#081914;padding:9px 14px;font-weight:700;cursor:pointer;margin:4px 7px 4px 0}button.secondary{background:#304b5a;color:#e8f0f2}button.danger{background:#6d3541;color:#fff}button:disabled{opacity:.5;cursor:not-allowed}.grid{display:grid;grid-template-columns:1fr 1fr;gap:0 14px}.row{border-top:1px solid #294454;padding:12px 0;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.muted{font-size:12px;color:#93acb9}.choices{display:flex;flex-wrap:wrap;gap:8px 18px;margin:12px 0}.choices label{display:inline-flex;gap:7px;align-items:center;font-weight:400;font-size:14px}.choices input{width:auto;margin:0}.pill{display:inline-block;background:#234051;border-radius:20px;padding:3px 9px;font-size:12px;margin-left:8px}.actions{display:flex;flex-wrap:wrap;gap:3px}.help{background:#0d1e28;border-left:3px solid #26bfa4;padding:10px 14px;margin:12px 0}.review{white-space:pre-wrap;background:#0b1720;border:1px solid #426073;border-radius:7px;padding:14px;line-height:1.5}#status{padding:8px 5vw;color:#8ee7ce;min-height:35px}#status.error{color:#ff9e9e}code{color:#a6e7db}[hidden]{display:none!important}a{color:#8ee7ce}@media(max-width:800px){main{display:block}.card{margin-bottom:20px}.grid{display:block}}
11
8
  </style>
12
- <header><h1>IntegSec MCP Gateway</h1><span class="muted">Manage tools and engagement scope</span></header>
13
- <div id="status" role="status"></div>
9
+ <header><h1>IntegSec MCP Gateway</h1><span id="identity" class="muted">Loading session…</span></header>
10
+ <div id="status" role="status" aria-live="polite"></div>
14
11
  <main>
15
- <section class="card wide"><h2>Administrator access</h2><p>Enter the gateway admin token from your service environment. It stays in this browser tab only.</p><div class="grid"><label>Admin token<input id="token" type="password" autocomplete="off"></label><div><br><button id="connect">Connect</button></div></div></section>
16
- <section class="card"><h2>MCP servers</h2><div id="upstreams"></div><h3>Add a server</h3><div class="grid"><label>Name<input id="up-name" placeholder="burp"></label><label>Transport<select id="up-transport"><option value="stdio">Local process</option><option value="http">Streamable HTTP</option><option value="sse">SSE</option></select></label></div><label>Command or URL<input id="up-target" placeholder="burp-mcp-server or https://example.com/mcp"></label><label>Arguments, one per line<textarea id="up-args" placeholder="--port\n3000"></textarea></label><button id="add-upstream">Add server</button></section>
17
- <section class="card"><h2>Engagement scopes</h2><p>Save a draft, review it, then activate it. The active scope cannot be edited in place.</p><div id="scopes"></div><h3>Scope editor</h3><div class="grid"><label>Engagement ID<input id="engagement" placeholder="ENG-2026-001"></label><label>Client<input id="client" placeholder="Example Client"></label><label>Operator<input id="operator" placeholder="Test Team"></label><label>Starts (UTC)<input id="starts" type="datetime-local"></label><label>Ends (UTC)<input id="ends" type="datetime-local"></label><label>Execution profile<select id="profile"><option value="isolated">Isolated: local MCP only</option><option value="restricted">Restricted: approved hosts</option><option value="connected">Connected: network MCP allowed</option></select></label></div><p class="muted">Use one item per line in each field.</p><div class="grid"><label>IP ranges<textarea id="ips"></textarea></label><label>Domains<textarea id="domains"></textarea></label><label>URLs<textarea id="urls"></textarea></label><label>Cloud accounts<textarea id="cloud"></textarea></label><label>Excluded targets<textarea id="excluded-targets"></textarea></label><label>Authorized techniques<textarea id="techniques"></textarea></label><label>Excluded techniques<textarea id="excluded-techniques"></textarea></label><label>Allowed MCP hosts (restricted profile)<textarea id="hosts"></textarea></label></div><button id="save-scope">Save draft</button><button id="new-scope" class="secondary">Clear editor</button></section>
18
- <section class="card wide"><h2>Client connection</h2><p id="connection">Connect as an administrator to see the endpoint.</p><p class="muted">Local Burp tools must run on each tester’s own gateway or local MCP connection. A public gateway cannot launch Burp on a tester’s laptop.</p></section>
12
+ <section id="token-card" class="card wide" hidden><h2>Local administrator access</h2><p>Enter the local gateway admin token. It stays in this browser tab only.</p><div class="grid"><label>Admin token<input id="token" type="password" autocomplete="off"></label><div><br><button id="connect">Connect</button></div></div></section>
13
+ <section id="my-card" class="card wide" hidden><h2>My engagement</h2><p>Choose an approved engagement assigned to you. Your choice affects only your MCP calls. No engagement means no tools.</p><div id="my-engagements"></div><button id="clear-selection" class="secondary">Clear my selection</button><p id="my-selection" class="help"></p></section>
14
+ <section id="users-card" class="card wide admin-only" hidden><h2>Team users</h2><p>Cloudflare Access handles sign-in. People must also be on this exact-email list and assigned to an approved engagement. Administrators manage users, servers and scopes.</p><div id="users"></div><h3>Add a user</h3><div class="grid"><label>Email<input id="user-email" type="email" autocomplete="off" placeholder="tester@example.com"></label><label>Role<select id="user-role"><option value="tester">Tester</option><option value="admin">Administrator</option></select></label></div><button id="add-user">Add user</button></section>
15
+ <section id="upstreams-card" class="card admin-only" hidden><h2>MCP servers</h2><p>Start with a vetted preset or add an existing server. Adding a preset does not install software or start a tool; new servers begin disabled.</p><div id="upstreams"></div><h3>Curated presets</h3><div id="presets"></div><h3>Add a custom server</h3><div class="grid"><label>Name<input id="up-name" placeholder="burp"></label><label>Transport<select id="up-transport"><option value="stdio">Local process</option><option value="http">Streamable HTTP</option><option value="sse">SSE</option></select></label></div><label>Command or URL<input id="up-target" placeholder="installed-command or https://tools.example.com/mcp"></label><label>Arguments, one per line<textarea id="up-args" placeholder="mcp"></textarea></label><button id="add-upstream">Add disabled server</button></section>
16
+ <section id="scopes-card" class="card admin-only" hidden><h2>Engagement scopes</h2><p>Create a draft, review and approve it, then assign users. Approved scopes are immutable; duplicate one to revise it. Every user chooses their own current engagement above.</p><div id="scopes"></div></section>
17
+ <section id="editor-card" class="card wide admin-only" hidden><h2>Scope editor</h2><div id="editor-help" class="help">Start with a blank form or load an example. Replace all example targets before approval.</div><div class="actions"><button id="example-web" class="secondary">Web example</button><button id="example-internal" class="secondary">Internal network example</button><button id="example-cloud" class="secondary">Cloud example</button></div><div class="grid"><label>Engagement ID<input id="engagement" maxlength="64" placeholder="ENG-2026-001"></label><label>Client<input id="client" maxlength="120" placeholder="Example Client"></label><label>Operator<input id="operator" maxlength="120" placeholder="Test Team"></label><label>Starts (your local time)<input id="starts" type="datetime-local"></label><label>Ends (your local time)<input id="ends" type="datetime-local"></label><label>Execution profile<select id="profile"><option value="isolated">Isolated: local MCP only</option><option value="restricted">Restricted: approved MCP hosts</option><option value="connected">Connected: network MCP allowed</option></select></label></div><p class="muted">Use one authorized item per line. IPs require CIDR notation. Domains are hostnames. URLs must start with https:// or http://. Cloud accounts use aws:123456789012, azure:subscription-uuid, or gcp:project-id.</p><div class="grid"><label>IP ranges<textarea id="ips" placeholder="192.0.2.0/28"></textarea></label><label>Domains<textarea id="domains" placeholder="app.example.com"></textarea></label><label>URLs<textarea id="urls" placeholder="https://app.example.com/test/"></textarea></label><label>Cloud accounts<textarea id="cloud" placeholder="aws:123456789012"></textarea></label><label>Excluded targets<textarea id="excluded-targets" placeholder="admin.example.com"></textarea></label><label>Allowed MCP hosts (restricted profile)<textarea id="hosts" placeholder="tools.example.com"></textarea></label></div><h3>Authorized techniques</h3><p class="muted">Choose only activities the signed rules of engagement permit. Unselected categories are blocked. Unrecognized tool categories are blocked until mapped.</p><div id="techniques" class="choices"></div><h3>Explicit exclusions</h3><div id="excluded-techniques" class="choices"></div><div class="actions"><button id="save-scope">Save draft</button><button id="new-scope" class="secondary">New blank draft</button></div><h3>Review before approval</h3><div id="scope-review" class="review"></div></section>
18
+ <section class="card wide"><h2>Client connection</h2><p id="connection">Sign in to see your MCP endpoint.</p><p class="muted">Local Burp tools run on each tester’s laptop through their local gateway. The hosted gateway cannot reach a tester’s laptop automatically.</p><p class="muted">Cloudflare Access supports passkeys for faster repeat sign-in when enabled for this application. Email codes remain available for enrollment and recovery.</p></section>
19
19
  </main>
20
20
  <script src="/admin.js" defer></script>
21
21
  </html>
@@ -1,78 +1,216 @@
1
1
  const $ = (id) => document.getElementById(id);
2
- let state = null;
3
- let editing = null;
4
2
  const token = $('token');
5
3
  token.value = sessionStorage.getItem('gateway-token') || '';
4
+ let mode = 'token';
5
+ let role = 'admin';
6
+ let email = null;
7
+ let state = null;
8
+ let editing = null;
9
+ let viewingStatus = 'draft';
10
+ let bootstrapAdmins = new Set();
11
+ const techniques = [
12
+ ['recon', 'Reconnaissance and discovery'], ['web_app', 'Web and API testing'],
13
+ ['exploitation', 'Exploitation'], ['cloud_audit', 'Cloud assessment'],
14
+ ['dos', 'Denial of service'], ['destructive', 'Destructive actions'],
15
+ ['social_engineering', 'Social engineering'],
16
+ ];
17
+ const presets = [
18
+ { name: 'burp', label: 'Burp Suite', transport: 'sse', url: 'http://127.0.0.1:9876', local: true, source: 'https://github.com/PortSwigger/mcp-server', note: 'Enable the official MCP extension inside the tester’s local Burp.' },
19
+ { name: 'semgrep', label: 'Semgrep', transport: 'stdio', command: 'semgrep', args: ['mcp'], local: true, source: 'https://github.com/semgrep/semgrep', note: 'Install and configure the official Semgrep CLI first.' },
20
+ { name: 'trivy', label: 'Trivy', transport: 'stdio', command: 'trivy', args: ['mcp'], local: true, source: 'https://github.com/aquasecurity/trivy-mcp', note: 'Install Trivy and its official MCP plugin first; pin and review your version.' },
21
+ { name: 'snyk', label: 'Snyk', transport: 'stdio', command: 'snyk', args: ['mcp', '-t', 'stdio'], local: true, source: 'https://github.com/snyk/studio-mcp', note: 'Install the Snyk CLI and authenticate locally first.' },
22
+ { label: 'OWASP ZAP', source: 'https://www.zaproxy.org/docs/desktop/addons/mcp-integration/mcp-options/', note: 'Reference only: its MCP security key needs authenticated upstream support.' },
23
+ { label: 'PortSwigger DAST', source: 'https://portswigger.net/burp/documentation/dast/user-guide/using-ai/mcp-server', note: 'Reference only: its hosted MCP endpoint requires an API key.' },
24
+ { label: 'Prowler Cloud', source: 'https://prowler.com/prowler-cloud', note: 'Reference only: its hosted MCP endpoint requires an API key.' },
25
+ ];
6
26
 
7
- function status(message, error = false) { const node = $('status'); node.textContent = message; node.classList.toggle('error', error); }
27
+ function status(message, error = false) { $('status').textContent = message; $('status').classList.toggle('error', error); }
8
28
  async function api(path, method = 'GET', data) {
9
- const response = await fetch(path, { method, headers: { Authorization: `Bearer ${token.value}`, ...(data ? { 'Content-Type': 'application/json' } : {}) }, body: data ? JSON.stringify(data) : undefined });
10
- const result = await response.json();
29
+ const response = await fetch(path, { method, credentials: 'same-origin', cache: 'no-store', headers: {
30
+ ...(mode === 'token' ? { Authorization: `Bearer ${token.value}` } : {}),
31
+ ...(data !== undefined ? { 'Content-Type': 'application/json' } : {}),
32
+ }, body: data !== undefined ? JSON.stringify(data) : undefined });
33
+ const result = await response.json().catch(() => ({}));
11
34
  if (!response.ok) throw new Error(result.error || `HTTP ${response.status}`);
12
35
  return result;
13
36
  }
14
- const lines = (id) => $(id).value.split(/\r?\n/).map(x => x.trim()).filter(Boolean);
37
+ const lines = (id) => $(id).value.split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
15
38
  const setLines = (id, values) => { $(id).value = values.join('\n'); };
16
39
  const make = (tag, value) => { const node = document.createElement(tag); node.textContent = value; return node; };
17
- const button = (label, action, style = 'secondary') => { const node = make('button', label); node.className = style; node.addEventListener('click', () => action().catch(e => status(e.message, true))); return node; };
40
+ function button(label, action, style = 'secondary') {
41
+ const node = make('button', label); node.className = style;
42
+ node.addEventListener('click', () => Promise.resolve().then(action).catch((error) => status(error.message, true)));
43
+ return node;
44
+ }
45
+ function choiceList(container, name) {
46
+ const result = [];
47
+ for (const [value, label] of techniques) {
48
+ const option = make('label', ''); const input = document.createElement('input');
49
+ input.type = 'checkbox'; input.name = name; input.value = value;
50
+ option.append(input, make('span', label)); container.append(option); result.push(input);
51
+ }
52
+ return result;
53
+ }
54
+ const authorizedChoices = choiceList($('techniques'), 'authorized-technique');
55
+ const excludedChoices = choiceList($('excluded-techniques'), 'excluded-technique');
56
+ const checked = (choices) => choices.filter((item) => item.checked).map((item) => item.value);
57
+ function setChecked(choices, values) { for (const item of choices) item.checked = values.includes(item.value); }
58
+ function localDate(value) { const date = new Date(value); return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, 16); }
59
+ function draftFromForm() {
60
+ if (!$('starts').value || !$('ends').value) throw new Error('Enter both engagement dates.');
61
+ const start = new Date($('starts').value); const end = new Date($('ends').value);
62
+ if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime())) throw new Error('Enter valid engagement dates.');
63
+ return { profile: $('profile').value, allowedHosts: lines('hosts'), manifest: {
64
+ engagement_id: $('engagement').value.trim(), client: $('client').value.trim(), operator: $('operator').value.trim(),
65
+ authorized_targets: { ip_ranges: lines('ips'), domains: lines('domains'), urls: lines('urls'), cloud_accounts: lines('cloud') },
66
+ excluded_targets: lines('excluded-targets'), authorized_techniques: checked(authorizedChoices),
67
+ excluded_techniques: checked(excludedChoices), engagement_window: { start: start.toISOString(), end: end.toISOString() },
68
+ } };
69
+ }
70
+ function summarize(scope) {
71
+ const m = scope.manifest;
72
+ return `Engagement: ${m.engagement_id || '(missing)'}\nClient: ${m.client || '(missing)'} · Operator: ${m.operator || '(missing)'}\nWindow: ${m.engagement_window.start} → ${m.engagement_window.end}\nProfile: ${scope.profile}\nTargets: ${Object.values(m.authorized_targets).flat().join(', ') || '(none)'}\nExcluded targets: ${m.excluded_targets.join(', ') || '(none)'}\nAuthorized techniques: ${m.authorized_techniques.join(', ') || '(none)'}\nExcluded techniques: ${m.excluded_techniques.join(', ') || '(none)'}\nAllowed MCP hosts: ${scope.allowedHosts.join(', ') || '(none)'}`;
73
+ }
74
+ function reviewForm() { try { $('scope-review').textContent = summarize(draftFromForm()); } catch (error) { $('scope-review').textContent = error.message; } }
75
+ for (const field of $('editor-card').querySelectorAll('input,select,textarea')) field.addEventListener('input', reviewForm);
18
76
 
19
77
  async function refresh() {
20
- state = await api('/api/state');
21
- const up = $('upstreams'); up.replaceChildren();
78
+ const mine = await api('/api/engagements');
79
+ $('my-card').hidden = false;
80
+ const list = $('my-engagements'); list.replaceChildren();
81
+ for (const scope of mine.engagements) {
82
+ const active = mine.selectedScopeId === scope.id;
83
+ const windowOpen = Date.now() >= Date.parse(scope.manifest.engagement_window.start) && Date.now() <= Date.parse(scope.manifest.engagement_window.end);
84
+ const row = make('div', ''); row.className = 'row';
85
+ row.append(make('span', `${scope.manifest.engagement_id} · ${scope.manifest.client} · ${scope.profile} · ${windowOpen ? 'window open' : 'window closed'}${active ? ' · SELECTED' : ''}`));
86
+ const select = button(active ? 'Selected' : 'Select', async () => { await api('/api/my-scope', 'POST', { scopeId: scope.id }); await refresh(); });
87
+ select.disabled = active || !windowOpen;
88
+ row.append(select); list.append(row);
89
+ }
90
+ if (!mine.engagements.length) list.append(make('p', 'No approved engagements have been assigned to you yet. Ask an administrator.'));
91
+ const selected = mine.engagements.find((item) => item.id === mine.selectedScopeId);
92
+ const selectedWindowOpen = selected && Date.now() >= Date.parse(selected.manifest.engagement_window.start) && Date.now() <= Date.parse(selected.manifest.engagement_window.end);
93
+ $('my-selection').textContent = selected ? selectedWindowOpen ? `Current engagement: ${selected.manifest.engagement_id}. Its scope applies only to your MCP calls.` : `Engagement ${selected.manifest.engagement_id} is outside its approved window. Your MCP client will see no tools.` : 'No engagement selected. Your MCP client will see no tools.';
94
+ $('connection').textContent = `MCP endpoint: ${location.origin}/mcp. ${selected ? `Current engagement: ${selected.manifest.engagement_id}.` : 'Select an engagement before using tools.'}`;
95
+ if (role === 'admin') {
96
+ state = await api('/api/state');
97
+ renderUsers(); renderUpstreams(); renderPresets(); renderScopes();
98
+ }
99
+ status('Gateway state loaded.');
100
+ }
101
+ function renderUsers() {
102
+ if (mode !== 'access') return;
103
+ const container = $('users'); container.replaceChildren();
104
+ for (const user of state.users) {
105
+ const row = make('div', ''); row.className = 'row'; const bootstrap = bootstrapAdmins.has(user.email);
106
+ row.append(make('span', `${user.email} · ${user.role}${bootstrap ? ' · bootstrap administrator' : ''}`));
107
+ if (!bootstrap) {
108
+ row.append(button(user.role === 'admin' ? 'Make tester' : 'Make admin', async () => { await api(`/api/users/${user.id}`, 'PATCH', { role: user.role === 'admin' ? 'tester' : 'admin' }); await refresh(); }));
109
+ row.append(button('Remove', async () => { if (confirm(`Remove ${user.email} and all engagement assignments?`)) { await api(`/api/users/${user.id}`, 'DELETE'); await refresh(); } }, 'danger'));
110
+ }
111
+ container.append(row);
112
+ }
113
+ }
114
+ function renderUpstreams() {
115
+ const container = $('upstreams'); container.replaceChildren();
22
116
  for (const item of state.upstreams) {
23
117
  const row = make('div', ''); row.className = 'row';
24
- const label = make('div', `${item.name} · ${item.transport} · ${item.enabled ? 'Enabled' : 'Disabled'}`);
25
- label.appendChild(make('div', item.transport === 'stdio' ? item.command : item.url));
26
- row.append(label, button(item.enabled ? 'Disable' : 'Enable', async () => { await api(`/api/upstreams/${item.id}`, 'PATCH', { enabled: !item.enabled }); await refresh(); }));
27
- row.append(button('Remove', async () => { if (confirm(`Remove ${item.name}?`)) { await api(`/api/upstreams/${item.id}`, 'DELETE'); await refresh(); } }, 'danger'));
28
- up.append(row);
118
+ row.append(make('span', `${item.name} · ${item.transport} · ${item.enabled ? 'Enabled' : 'Disabled'} · ${item.transport === 'stdio' ? item.command : item.url}`));
119
+ row.append(button(item.enabled ? 'Disable' : 'Enable', async () => { await api(`/api/upstreams/${item.id}`, 'PATCH', { enabled: !item.enabled }); await refresh(); }));
120
+ row.append(button('Remove', async () => { if (confirm(`Remove server ${item.name}?`)) { await api(`/api/upstreams/${item.id}`, 'DELETE'); await refresh(); } }, 'danger'));
121
+ container.append(row);
29
122
  }
30
- const scopes = $('scopes'); scopes.replaceChildren();
31
- for (const item of state.scopes) {
32
- const active = item.id === state.activeScopeId;
123
+ }
124
+ function renderPresets() {
125
+ const container = $('presets'); container.replaceChildren();
126
+ for (const preset of presets) {
127
+ const row = make('div', ''); row.className = 'row'; const detail = make('div', `${preset.label} · ${preset.note} `);
128
+ const link = make('a', 'Official information'); link.href = preset.source; link.target = '_blank'; link.rel = 'noopener noreferrer'; detail.append(link);
129
+ row.append(detail);
130
+ if (preset.name) {
131
+ const add = button('Add disabled', async () => { await api('/api/upstreams', 'POST', { name: preset.name, enabled: false, transport: preset.transport, command: preset.command, args: preset.args || [], url: preset.url }); await refresh(); });
132
+ add.disabled = (mode === 'access' && preset.local) || state.upstreams.some((item) => item.name === preset.name);
133
+ if (mode === 'access' && preset.local) add.title = 'This preset runs on a local gateway, not the hosted Cloudflare gateway.';
134
+ row.append(add);
135
+ }
136
+ container.append(row);
137
+ }
138
+ }
139
+ function renderScopes() {
140
+ const container = $('scopes'); container.replaceChildren();
141
+ for (const scope of state.scopes) {
33
142
  const row = make('div', ''); row.className = 'row';
34
- row.append(make('div', `${item.manifest.engagement_id} · ${item.profile}${active ? ' · ACTIVE' : ''}`));
35
- row.append(button('View', async () => loadScope(item)));
36
- if (!active) {
37
- row.append(button('Activate', async () => { await api(`/api/scopes/${item.id}/activate`, 'POST'); await refresh(); }));
38
- row.append(button('Delete', async () => { if (confirm(`Delete draft ${item.manifest.engagement_id}?`)) { await api(`/api/scopes/${item.id}`, 'DELETE'); await refresh(); } }, 'danger'));
143
+ const label = make('div', `${scope.manifest.engagement_id} · ${scope.manifest.client}`);
144
+ label.append(make('span', scope.status)); label.lastChild.className = 'pill';
145
+ row.append(label);
146
+ const actions = make('div', ''); actions.className = 'actions';
147
+ actions.append(button(scope.status === 'draft' ? 'Edit' : 'View', () => loadScope(scope)));
148
+ actions.append(button('Duplicate', async () => { const copy = await api(`/api/scopes/${scope.id}/duplicate`, 'POST'); await refresh(); loadScope(copy); status('Draft copy created. Edit and approve it when ready.'); }));
149
+ if (scope.status === 'draft') actions.append(button('Review and approve', async () => {
150
+ if (!confirm(`Approve this scope for ${scope.manifest.engagement_id}?\n\n${summarize(scope)}\n\nApproval makes it eligible for assignment. No user is automatically assigned.`)) return;
151
+ await api(`/api/scopes/${scope.id}/approve`, 'POST'); await refresh();
152
+ }));
153
+ actions.append(button('Delete', async () => { if (confirm(`Delete ${scope.manifest.engagement_id} (${scope.status})? Assigned users will lose this engagement.`)) { await api(`/api/scopes/${scope.id}`, 'DELETE'); await refresh(); } }, 'danger'));
154
+ row.append(actions); container.append(row);
155
+ if (scope.status === 'approved' && mode === 'access') {
156
+ const assignment = make('div', ''); assignment.className = 'choices';
157
+ const inputs = state.users.map((user) => { const label = make('label', ''); const input = document.createElement('input'); input.type = 'checkbox'; input.value = user.email; input.checked = scope.assignedUserEmails.includes(user.email); label.append(input, make('span', user.email)); assignment.append(label); return input; });
158
+ assignment.append(button('Save assignments', async () => { await api(`/api/scopes/${scope.id}/assignments`, 'PUT', { emails: inputs.filter((item) => item.checked).map((item) => item.value) }); await refresh(); }));
159
+ container.append(assignment);
39
160
  }
40
- scopes.append(row);
41
161
  }
42
- $('connection').textContent = `MCP endpoint: ${location.origin}/mcp. ${state.activeScopeId ? 'An engagement scope is active.' : 'Activate a scope before connecting clients.'}`;
43
- status('Gateway state loaded.');
162
+ if (!state.scopes.length) container.append(make('p', 'No scopes yet. Use the editor below to create a draft.'));
44
163
  }
45
- function loadScope(item) {
46
- editing = item.id === state.activeScopeId ? null : item.id;
47
- const m = item.manifest;
164
+ function loadScope(scope) {
165
+ editing = scope.status === 'draft' ? scope.id : null; viewingStatus = scope.status;
166
+ const m = scope.manifest;
48
167
  $('engagement').value = m.engagement_id; $('client').value = m.client; $('operator').value = m.operator;
49
- $('starts').value = m.engagement_window.start.slice(0,16); $('ends').value = m.engagement_window.end.slice(0,16);
50
- $('profile').value = item.profile; setLines('ips', m.authorized_targets.ip_ranges); setLines('domains', m.authorized_targets.domains);
168
+ $('starts').value = localDate(m.engagement_window.start); $('ends').value = localDate(m.engagement_window.end);
169
+ $('profile').value = scope.profile; setLines('ips', m.authorized_targets.ip_ranges); setLines('domains', m.authorized_targets.domains);
51
170
  setLines('urls', m.authorized_targets.urls); setLines('cloud', m.authorized_targets.cloud_accounts);
52
- setLines('excluded-targets', m.excluded_targets); setLines('techniques', m.authorized_techniques);
53
- setLines('excluded-techniques', m.excluded_techniques); setLines('hosts', item.allowedHosts);
54
- status(item.id === state.activeScopeId ? 'Viewing active scope. Saving creates a new draft.' : 'Draft loaded for editing.');
171
+ setLines('excluded-targets', m.excluded_targets); setLines('hosts', scope.allowedHosts);
172
+ setChecked(authorizedChoices, m.authorized_techniques); setChecked(excludedChoices, m.excluded_techniques);
173
+ $('save-scope').disabled = scope.status !== 'draft';
174
+ $('editor-help').textContent = scope.status === 'draft' ? 'Editing a draft. Review the summary before approval.' : 'Viewing an immutable scope. Choose Duplicate to create an editable draft.';
175
+ reviewForm(); $('editor-card').scrollIntoView({ behavior: 'smooth' });
176
+ }
177
+ function clearEditor() {
178
+ editing = null; viewingStatus = 'draft';
179
+ for (const id of ['engagement', 'client', 'operator', 'starts', 'ends', 'ips', 'domains', 'urls', 'cloud', 'excluded-targets', 'hosts']) $(id).value = '';
180
+ $('profile').value = 'isolated'; setChecked(authorizedChoices, []); setChecked(excludedChoices, []);
181
+ $('save-scope').disabled = false; $('editor-help').textContent = 'New draft. Enter the exact authorized targets, window and techniques.'; reviewForm();
182
+ }
183
+ function loadExample(kind) {
184
+ clearEditor();
185
+ const now = Date.now(); $('starts').value = localDate(now + 3600000); $('ends').value = localDate(now + 25 * 3600000);
186
+ $('engagement').value = `EXAMPLE-${kind.toUpperCase()}`; $('client').value = 'Example Client'; $('operator').value = 'Test Team';
187
+ if (kind === 'web') { setLines('domains', ['app.example.com']); setLines('urls', ['https://app.example.com/test/']); setChecked(authorizedChoices, ['recon', 'web_app']); }
188
+ if (kind === 'internal') { setLines('ips', ['192.0.2.0/28']); setChecked(authorizedChoices, ['recon']); }
189
+ if (kind === 'cloud') { setLines('cloud', ['aws:123456789012']); setChecked(authorizedChoices, ['cloud_audit']); }
190
+ $('editor-help').textContent = 'Example only: replace the client, engagement ID and all example targets with the signed rules of engagement before approval.';
191
+ reviewForm();
192
+ }
193
+ $('connect').onclick = () => { sessionStorage.setItem('gateway-token', token.value); refresh().catch((error) => status(error.message, true)); };
194
+ $('clear-selection').onclick = () => api('/api/my-scope', 'POST', { scopeId: null }).then(refresh).catch((error) => status(error.message, true));
195
+ $('add-user').onclick = async () => { try { await api('/api/users', 'POST', { email: $('user-email').value.trim(), role: $('user-role').value }); $('user-email').value = ''; await refresh(); } catch (error) { status(error.message, true); } };
196
+ $('add-upstream').onclick = async () => { try { const transport = $('up-transport').value; await api('/api/upstreams', 'POST', { name: $('up-name').value.trim(), enabled: false, transport,
197
+ ...(transport === 'stdio' ? { command: $('up-target').value.trim(), args: lines('up-args') } : { url: $('up-target').value.trim(), args: [] }) });
198
+ $('up-name').value = ''; $('up-target').value = ''; $('up-args').value = ''; await refresh(); } catch (error) { status(error.message, true); } };
199
+ $('new-scope').onclick = clearEditor;
200
+ for (const kind of ['web', 'internal', 'cloud']) $(`example-${kind}`).onclick = () => loadExample(kind);
201
+ $('save-scope').onclick = async () => { try { if (viewingStatus !== 'draft') throw new Error('Duplicate an approved or archived scope to edit it.');
202
+ const scope = draftFromForm(); await api(editing ? `/api/scopes/${editing}` : '/api/scopes', editing ? 'PUT' : 'POST', scope);
203
+ clearEditor(); await refresh(); status('Draft saved. Review it, approve it, then assign users.');
204
+ } catch (error) { status(error.message, true); } };
205
+ async function initialize() {
206
+ const response = await fetch('/api/session', { credentials: 'same-origin', cache: 'no-store' });
207
+ const session = await response.json(); if (!response.ok) throw new Error(session.error || `HTTP ${response.status}`);
208
+ mode = session.mode; role = session.role || 'admin'; email = session.email || null;
209
+ bootstrapAdmins = new Set(session.bootstrapAdminEmails || []);
210
+ $('identity').textContent = email ? `${email} · ${role}` : 'Local gateway';
211
+ $('token-card').hidden = mode !== 'token';
212
+ for (const card of document.querySelectorAll('.admin-only')) card.hidden = role !== 'admin' || (card.id === 'users-card' && mode !== 'access');
213
+ if (mode === 'access' || token.value) await refresh();
214
+ reviewForm();
55
215
  }
56
- $('connect').onclick = () => { sessionStorage.setItem('gateway-token', token.value); refresh().catch(e => status(e.message, true)); };
57
- $('add-upstream').onclick = async () => {
58
- try {
59
- const transport = $('up-transport').value;
60
- await api('/api/upstreams', 'POST', { name: $('up-name').value.trim(), enabled: true, transport,
61
- ...(transport === 'stdio' ? { command: $('up-target').value.trim(), args: lines('up-args') } : { url: $('up-target').value.trim(), args: [] }) });
62
- $('up-name').value = ''; $('up-target').value = ''; $('up-args').value = ''; await refresh();
63
- } catch (e) { status(e.message, true); }
64
- };
65
- $('new-scope').onclick = () => { editing = null; for (const id of ['engagement','client','operator','starts','ends','ips','domains','urls','cloud','excluded-targets','techniques','excluded-techniques','hosts']) $(id).value = ''; $('profile').value = 'isolated'; status('New draft.'); };
66
- $('save-scope').onclick = async () => {
67
- try {
68
- const scope = { profile: $('profile').value, allowedHosts: lines('hosts'), manifest: {
69
- engagement_id: $('engagement').value.trim(), client: $('client').value.trim(), operator: $('operator').value.trim(),
70
- authorized_targets: { ip_ranges: lines('ips'), domains: lines('domains'), urls: lines('urls'), cloud_accounts: lines('cloud') },
71
- excluded_targets: lines('excluded-targets'), authorized_techniques: lines('techniques'), excluded_techniques: lines('excluded-techniques'),
72
- engagement_window: { start: new Date($('starts').value).toISOString(), end: new Date($('ends').value).toISOString() },
73
- } };
74
- await api(editing ? `/api/scopes/${editing}` : '/api/scopes', editing ? 'PUT' : 'POST', scope);
75
- editing = null; await refresh(); status('Draft saved. Review it before activation.');
76
- } catch (e) { status(e.message, true); }
77
- };
78
- if (token.value) refresh().catch(e => status(e.message, true));
216
+ initialize().catch((error) => status(error.message, true));
@@ -1,4 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
+ export interface GatewayIdentity {
3
+ actor: string;
4
+ email?: string;
5
+ }
2
6
  export declare class GatewayAuth {
3
7
  readonly publicUrl: string | undefined;
4
8
  private readonly tenant;
@@ -10,7 +14,7 @@ export declare class GatewayAuth {
10
14
  private accessJwks?;
11
15
  constructor(publicUrl: string | undefined, tenant: string | undefined, audience: string | undefined, requiredScope: string | undefined, mode?: "entra" | "cloudflare", teamDomain?: string | undefined);
12
16
  protectedResource(): Promise<Record<string, unknown>>;
13
- authorize(req: IncomingMessage, res: ServerResponse): Promise<string | false>;
17
+ authorize(req: IncomingMessage, res: ServerResponse): Promise<GatewayIdentity | false>;
14
18
  private denyAccess;
15
19
  private challenge;
16
20
  private getVerifier;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/gateway/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,qBAAa,WAAW;IAKpB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAT9B,OAAO,CAAC,QAAQ,CAAC,CAA2E;IAC5F,OAAO,CAAC,UAAU,CAAC,CAAwC;gBAGhD,SAAS,EAAE,MAAM,GAAG,SAAS,EACrB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,IAAI,GAAE,OAAO,GAAG,YAAsB,EACtC,UAAU,CAAC,EAAE,MAAM,YAAA;IAUhC,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAYrD,SAAS,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;IA0BnF,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,WAAW;CAepB"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/gateway/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,MAAM,WAAW,eAAe;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAElE,qBAAa,WAAW;IAKpB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAT9B,OAAO,CAAC,QAAQ,CAAC,CAA2E;IAC5F,OAAO,CAAC,UAAU,CAAC,CAAwC;gBAGhD,SAAS,EAAE,MAAM,GAAG,SAAS,EACrB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,IAAI,GAAE,OAAO,GAAG,YAAsB,EACtC,UAAU,CAAC,EAAE,MAAM,YAAA;IAUhC,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAYrD,SAAS,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,KAAK,CAAC;IA6B5F,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,WAAW;CAepB"}
@@ -41,7 +41,7 @@ export class GatewayAuth {
41
41
  }
42
42
  async authorize(req, res) {
43
43
  if (!this.publicUrl)
44
- return "local";
44
+ return { actor: "local" };
45
45
  if (this.mode === "cloudflare") {
46
46
  const token = req.headers["cf-access-jwt-assertion"];
47
47
  if (typeof token !== "string") {
@@ -52,7 +52,16 @@ export class GatewayAuth {
52
52
  const domain = new URL(this.teamDomain);
53
53
  this.accessJwks ??= createRemoteJWKSet(new URL("/cdn-cgi/access/certs", domain));
54
54
  const { payload } = await jwtVerify(token, this.accessJwks, { issuer: domain.origin, audience: this.audience, algorithms: ["RS256"] });
55
- return typeof payload.sub === "string" ? payload.sub : "cloudflare-access";
55
+ if (payload.type !== "app" || typeof payload.email !== "string") {
56
+ this.denyAccess(res);
57
+ return false;
58
+ }
59
+ const email = payload.email.trim().toLowerCase();
60
+ if (!email || email.length > 254) {
61
+ this.denyAccess(res);
62
+ return false;
63
+ }
64
+ return { actor: typeof payload.sub === "string" ? payload.sub : email, email };
56
65
  }
57
66
  catch {
58
67
  this.denyAccess(res);
@@ -72,7 +81,7 @@ export class GatewayAuth {
72
81
  this.challenge(res);
73
82
  return false;
74
83
  }
75
- return typeof payload.oid === "string" ? payload.oid : typeof payload.sub === "string" ? payload.sub : "entra-user";
84
+ return { actor: typeof payload.oid === "string" ? payload.oid : typeof payload.sub === "string" ? payload.sub : "entra-user" };
76
85
  }
77
86
  catch {
78
87
  this.challenge(res);
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/gateway/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/gateway/auth.ts"],"names":[],"mappings":"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"}
@@ -5,16 +5,16 @@ export declare class GatewayRuntime {
5
5
  readonly store: GatewayStore;
6
6
  private readonly hosted;
7
7
  private readonly requiredAuditSink?;
8
+ private readonly isActorAllowed?;
8
9
  private clients;
9
10
  private pending;
10
11
  private proxies;
11
- private proxyScope?;
12
- constructor(store: GatewayStore, hosted: boolean, requiredAuditSink?: ((entry: AuditEntry) => Promise<void>) | undefined);
12
+ constructor(store: GatewayStore, hosted: boolean, requiredAuditSink?: ((entry: AuditEntry) => Promise<void>) | undefined, isActorAllowed?: ((email?: string) => boolean) | undefined);
13
13
  sync(): Promise<void>;
14
14
  private scopeProxy;
15
15
  private clientFor;
16
16
  private available;
17
- createServer(actor?: string): Server;
17
+ createServer(actor?: string, email?: string): Server;
18
18
  close(): Promise<void>;
19
19
  }
20
20
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/gateway/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAiE,MAAM,YAAY,CAAC;AAIzG,qBAAa,cAAc;IAMb,QAAQ,CAAC,KAAK,EAAE,YAAY;IAAE,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAW,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAL/G,OAAO,CAAC,OAAO,CAA4D;IAC3E,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,OAAO,CAA4C;IAC3D,OAAO,CAAC,UAAU,CAAC,CAAc;gBAEZ,KAAK,EAAE,YAAY,EAAmB,MAAM,EAAE,OAAO,EAAmB,iBAAiB,CAAC,GAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,aAAA;IAE/I,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,OAAO,CAAC,UAAU;YAcJ,SAAS;IA2BvB,OAAO,CAAC,SAAS;IAOjB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAgE9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/gateway/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAiE,MAAM,YAAY,CAAC;AAIzG,qBAAa,cAAc;IAKb,QAAQ,CAAC,KAAK,EAAE,YAAY;IAAE,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAW,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAAwC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAJvL,OAAO,CAAC,OAAO,CAA4D;IAC3E,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,OAAO,CAA4C;gBAEtC,KAAK,EAAE,YAAY,EAAmB,MAAM,EAAE,OAAO,EAAmB,iBAAiB,CAAC,GAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,aAAA,EAAmB,cAAc,CAAC,GAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,aAAA;IAE9M,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,OAAO,CAAC,UAAU;YAWJ,SAAS;IA2BvB,OAAO,CAAC,SAAS;IAOjB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAyE9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}