@integsec/agentic-pentest-proxy 0.3.1 → 0.3.3
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.
- package/README.md +1 -1
- package/cloudflare/src/worker.mjs +4 -3
- package/cloudflare/wrangler.jsonc +2 -1
- package/dist/bin/integsec-agentic-pentest-bridge.js +2 -2
- package/dist/src/config.js +1 -1
- package/dist/src/gateway/admin.html +10 -11
- package/dist/src/gateway/admin.js +251 -76
- package/dist/src/gateway/runtime.d.ts +0 -1
- package/dist/src/gateway/runtime.d.ts.map +1 -1
- package/dist/src/gateway/runtime.js +18 -16
- package/dist/src/gateway/runtime.js.map +1 -1
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +97 -32
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/state.d.ts +10 -3
- package/dist/src/gateway/state.d.ts.map +1 -1
- package/dist/src/gateway/state.js +106 -10
- package/dist/src/gateway/state.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/proxy.d.ts +1 -1
- package/dist/src/proxy.d.ts.map +1 -1
- package/dist/src/proxy.js +2 -2
- package/dist/src/proxy.js.map +1 -1
- package/dist/src/validator.d.ts +2 -1
- package/dist/src/validator.d.ts.map +1 -1
- package/dist/src/validator.js +9 -1
- package/dist/src/validator.js.map +1 -1
- package/docs/managed-gateway.md +13 -7
- 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
|
|
7
|
+
**Managing several tools?** The [managed gateway](docs/managed-gateway.md) adds a browser UI with curated MCP server presets, scope templates and technique checkboxes, JSON policy import and download, 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
|
|
|
@@ -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:
|
|
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', {
|
|
@@ -111,8 +111,9 @@ export class PentestGatewayContainer extends Container {
|
|
|
111
111
|
|
|
112
112
|
async listAudit(request) {
|
|
113
113
|
const check = await this.containerFetch('http://localhost:9091/api/session', { headers: request.headers });
|
|
114
|
-
await check.body?.cancel();
|
|
115
|
-
|
|
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);
|
|
116
117
|
const entries = await this.ctx.storage.list({ prefix: 'audit:', reverse: true, limit: 100 });
|
|
117
118
|
return json({ entries: [...entries.values()] });
|
|
118
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.
|
|
13
|
+
"image": "docker.io/integsec/agentic-pentest-mcp-proxy:0.3.3",
|
|
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.
|
|
15
|
+
const upstream = new Client({ name: "agentic-pentest-bridge", version: "0.3.3" });
|
|
16
16
|
await upstream.connect(new StreamableHTTPClientTransport(url));
|
|
17
|
-
const bridge = new Server({ name: "agentic-pentest-local", version: "0.3.
|
|
17
|
+
const bridge = new Server({ name: "agentic-pentest-local", version: "0.3.3" }, { 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());
|
package/dist/src/config.js
CHANGED
|
@@ -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.
|
|
53
|
+
proxyVersion: process.env.PROXY_VERSION || "0.3.3",
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
//# sourceMappingURL=config.js.map
|
|
@@ -4,19 +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}[hidden]{display:none!important}@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">
|
|
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 id="token-card" class="card wide" hidden><h2>
|
|
16
|
-
<section id="
|
|
17
|
-
<section class="card"><h2>
|
|
18
|
-
<section class="card"><h2>
|
|
19
|
-
<section class="card
|
|
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 class="actions"><button id="create-engagement" hidden>Create engagement</button><button id="import-policy" class="secondary" hidden>Import scope JSON</button><input id="policy-file" type="file" accept=".json,application/json" hidden><button id="clear-selection" class="secondary" hidden>Clear selection</button></div><div id="my-engagements"></div><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>Add an MCP server, then turn it on when its connection is ready. Adding a preset does not install the upstream software. Tools that run on a tester’s laptop must be added to that tester’s local gateway.</p><div id="upstreams"></div><h3>Curated servers</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 server</button></section>
|
|
16
|
+
<section id="scopes-card" class="card admin-only" hidden><h2>Engagement scopes</h2><p>Create or import a scope, save it, 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 a template. Enter the real client and authorized targets before approval.</div><div class="actions"><button id="example-web" class="secondary">Web template</button><button id="example-internal" class="secondary">Network template</button><button id="example-cloud" class="secondary">Cloud template</button></div><div class="grid"><label>Engagement ID<input id="engagement" maxlength="64" placeholder="Engagement identifier"></label><label>Client<input id="client" maxlength="120" placeholder="Authorized client name"></label><label>Operator<input id="operator" maxlength="120" placeholder="Testing team or operator"></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="Authorized CIDR range"></textarea></label><label>Domains<textarea id="domains" placeholder="Authorized client domain"></textarea></label><label>URLs<textarea id="urls" placeholder="Authorized client URL"></textarea></label><label>Cloud accounts<textarea id="cloud" placeholder="Authorized cloud account"></textarea></label><label>Excluded targets<textarea id="excluded-targets" placeholder="Explicitly excluded target"></textarea></label><label>Allowed MCP hosts (restricted profile)<textarea id="hosts" placeholder="Approved MCP hostname"></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</button><button id="approve-current" class="secondary" hidden>Approve scope</button><button id="assign-me" class="secondary" hidden>Assign to me</button><button id="use-current" class="secondary" hidden>Use engagement</button><button id="new-scope" class="secondary">New scope</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>
|
|
20
19
|
</main>
|
|
21
20
|
<script src="/admin.js" defer></script>
|
|
22
21
|
</html>
|
|
@@ -1,109 +1,284 @@
|
|
|
1
1
|
const $ = (id) => document.getElementById(id);
|
|
2
|
+
const token = $('token');
|
|
3
|
+
token.value = sessionStorage.getItem('gateway-token') || '';
|
|
4
|
+
let mode = 'token';
|
|
5
|
+
let role = 'admin';
|
|
6
|
+
let email = null;
|
|
2
7
|
let state = null;
|
|
3
8
|
let editing = null;
|
|
4
|
-
let
|
|
9
|
+
let viewingScopeId = null;
|
|
10
|
+
let dirty = false;
|
|
5
11
|
let bootstrapAdmins = new Set();
|
|
6
|
-
const
|
|
7
|
-
|
|
12
|
+
const techniques = [
|
|
13
|
+
['recon', 'Reconnaissance and discovery'], ['web_app', 'Web and API testing'],
|
|
14
|
+
['exploitation', 'Exploitation'], ['cloud_audit', 'Cloud assessment'],
|
|
15
|
+
['dos', 'Denial of service'], ['destructive', 'Destructive actions'],
|
|
16
|
+
['social_engineering', 'Social engineering'],
|
|
17
|
+
];
|
|
18
|
+
const presets = [
|
|
19
|
+
{ 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.' },
|
|
20
|
+
{ 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.' },
|
|
21
|
+
{ 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.' },
|
|
22
|
+
{ 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.' },
|
|
23
|
+
{ 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.' },
|
|
24
|
+
{ 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.' },
|
|
25
|
+
{ label: 'Prowler Cloud', source: 'https://prowler.com/prowler-cloud', note: 'Reference only: its hosted MCP endpoint requires an API key.' },
|
|
26
|
+
];
|
|
8
27
|
|
|
9
|
-
function status(message, error = false) {
|
|
28
|
+
function status(message, error = false) { $('status').textContent = message; $('status').classList.toggle('error', error); }
|
|
10
29
|
async function api(path, method = 'GET', data) {
|
|
11
|
-
const response = await fetch(path, { method, credentials: 'same-origin', cache: 'no-store', headers: {
|
|
12
|
-
|
|
30
|
+
const response = await fetch(path, { method, credentials: 'same-origin', cache: 'no-store', headers: {
|
|
31
|
+
...(mode === 'token' ? { Authorization: `Bearer ${token.value}` } : {}),
|
|
32
|
+
...(data !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
33
|
+
}, body: data !== undefined ? JSON.stringify(data) : undefined });
|
|
34
|
+
const result = await response.json().catch(() => ({}));
|
|
13
35
|
if (!response.ok) throw new Error(result.error || `HTTP ${response.status}`);
|
|
14
36
|
return result;
|
|
15
37
|
}
|
|
16
|
-
const lines = (id) => $(id).value.split(/\r?\n/).map(
|
|
38
|
+
const lines = (id) => $(id).value.split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
|
|
17
39
|
const setLines = (id, values) => { $(id).value = values.join('\n'); };
|
|
18
40
|
const make = (tag, value) => { const node = document.createElement(tag); node.textContent = value; return node; };
|
|
19
|
-
|
|
41
|
+
function button(label, action, style = 'secondary') {
|
|
42
|
+
const node = make('button', label); node.className = style;
|
|
43
|
+
node.addEventListener('click', () => Promise.resolve().then(action).catch((error) => status(error.message, true)));
|
|
44
|
+
return node;
|
|
45
|
+
}
|
|
46
|
+
function choiceList(container, name) {
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const [value, label] of techniques) {
|
|
49
|
+
const option = make('label', ''); const input = document.createElement('input');
|
|
50
|
+
input.type = 'checkbox'; input.name = name; input.value = value;
|
|
51
|
+
option.append(input, make('span', label)); container.append(option); result.push(input);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
const authorizedChoices = choiceList($('techniques'), 'authorized-technique');
|
|
56
|
+
const excludedChoices = choiceList($('excluded-techniques'), 'excluded-technique');
|
|
57
|
+
const checked = (choices) => choices.filter((item) => item.checked).map((item) => item.value);
|
|
58
|
+
function setChecked(choices, values) { for (const item of choices) item.checked = values.includes(item.value); }
|
|
59
|
+
function localDate(value) { const date = new Date(value); return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, 16); }
|
|
60
|
+
function draftFromForm() {
|
|
61
|
+
if (!$('starts').value || !$('ends').value) throw new Error('Enter both engagement dates.');
|
|
62
|
+
const start = new Date($('starts').value); const end = new Date($('ends').value);
|
|
63
|
+
if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime())) throw new Error('Enter valid engagement dates.');
|
|
64
|
+
return { profile: $('profile').value, allowedHosts: lines('hosts'), manifest: {
|
|
65
|
+
engagement_id: $('engagement').value.trim(), client: $('client').value.trim(), operator: $('operator').value.trim(),
|
|
66
|
+
authorized_targets: { ip_ranges: lines('ips'), domains: lines('domains'), urls: lines('urls'), cloud_accounts: lines('cloud') },
|
|
67
|
+
excluded_targets: lines('excluded-targets'), authorized_techniques: checked(authorizedChoices),
|
|
68
|
+
excluded_techniques: checked(excludedChoices), engagement_window: { start: start.toISOString(), end: end.toISOString() },
|
|
69
|
+
} };
|
|
70
|
+
}
|
|
71
|
+
function summarize(scope) {
|
|
72
|
+
const m = scope.manifest;
|
|
73
|
+
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)'}`;
|
|
74
|
+
}
|
|
75
|
+
function downloadPolicy(scope) {
|
|
76
|
+
const policy = { ...scope.manifest, gateway: { profile: scope.profile, allowed_hosts: scope.allowedHosts || [] } };
|
|
77
|
+
const filename = `${(scope.manifest.engagement_id || 'engagement').replace(/[^a-z0-9_-]/gi, '_')}.scope.json`;
|
|
78
|
+
const url = URL.createObjectURL(new Blob([`${JSON.stringify(policy, null, 2)}\n`], { type: 'application/json' }));
|
|
79
|
+
const link = document.createElement('a'); link.href = url; link.download = filename; document.body.append(link); link.click(); link.remove();
|
|
80
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
81
|
+
}
|
|
82
|
+
function updateEditorActions() {
|
|
83
|
+
const current = state?.scopes.find((scope) => scope.id === viewingScopeId);
|
|
84
|
+
$('approve-current').hidden = !(current?.status === 'draft');
|
|
85
|
+
$('approve-current').disabled = dirty;
|
|
86
|
+
$('assign-me').hidden = !(role === 'admin' && mode === 'access' && current?.status === 'approved' && email && !current.assignedUserEmails.includes(email));
|
|
87
|
+
const windowOpen = current && Date.now() >= Date.parse(current.manifest.engagement_window.start) && Date.now() <= Date.parse(current.manifest.engagement_window.end);
|
|
88
|
+
$('use-current').hidden = !(current?.status === 'approved' && windowOpen && (mode === 'token' || current.assignedUserEmails.includes(email)) && !(mode === 'access' && current.profile === 'isolated'));
|
|
89
|
+
}
|
|
90
|
+
function newScope() {
|
|
91
|
+
if (dirty && !confirm('Discard unsaved scope changes?')) return;
|
|
92
|
+
clearEditor(); $('editor-card').scrollIntoView({ behavior: 'smooth' }); $('engagement').focus();
|
|
93
|
+
}
|
|
94
|
+
async function approveScope(scope) {
|
|
95
|
+
if (scope.id === viewingScopeId && dirty) throw new Error('Save your changes before approving this scope.');
|
|
96
|
+
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;
|
|
97
|
+
await api(`/api/scopes/${scope.id}/approve`, 'POST'); await refresh();
|
|
98
|
+
const approved = state.scopes.find((item) => item.id === scope.id); if (approved) loadScope(approved);
|
|
99
|
+
status('Scope approved. Assign testers, then each tester selects the engagement.');
|
|
100
|
+
}
|
|
101
|
+
function reviewForm() {
|
|
102
|
+
try {
|
|
103
|
+
const scope = draftFromForm(); const m = scope.manifest; const warnings = [];
|
|
104
|
+
if (/^example/i.test(m.engagement_id) || /^example client$/i.test(m.client)) warnings.push('Replace the example engagement ID and client.');
|
|
105
|
+
if (m.authorized_targets.domains.some((value) => value.includes('example.'))) warnings.push('Replace documentation domains with real authorized domains.');
|
|
106
|
+
if (m.authorized_targets.urls.some((value) => value.includes('example.'))) warnings.push('Replace documentation URLs with real authorized URLs.');
|
|
107
|
+
if (Date.parse(m.engagement_window.end) <= Date.now()) warnings.push('Choose an end time in the future.');
|
|
108
|
+
$('scope-review').textContent = `${warnings.length ? `Before approval: ${warnings.join(' ')}\n\n` : ''}${summarize(scope)}`;
|
|
109
|
+
} catch (error) { $('scope-review').textContent = error.message; }
|
|
110
|
+
updateEditorActions();
|
|
111
|
+
}
|
|
112
|
+
for (const field of $('editor-card').querySelectorAll('input,select,textarea')) field.addEventListener('input', () => { dirty = true; reviewForm(); });
|
|
20
113
|
|
|
21
114
|
async function refresh() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
115
|
+
const mine = await api('/api/engagements');
|
|
116
|
+
$('my-card').hidden = false;
|
|
117
|
+
$('create-engagement').hidden = role !== 'admin';
|
|
118
|
+
$('import-policy').hidden = role !== 'admin';
|
|
119
|
+
const list = $('my-engagements'); list.replaceChildren();
|
|
120
|
+
for (const scope of mine.engagements) {
|
|
121
|
+
const active = mine.selectedScopeId === scope.id;
|
|
122
|
+
const windowOpen = Date.now() >= Date.parse(scope.manifest.engagement_window.start) && Date.now() <= Date.parse(scope.manifest.engagement_window.end);
|
|
123
|
+
const row = make('div', ''); row.className = 'row';
|
|
124
|
+
row.append(make('span', `${scope.manifest.engagement_id} · ${scope.manifest.client} · ${scope.profile} · ${windowOpen ? 'window open' : 'window closed'}${active ? ' · SELECTED' : ''}`));
|
|
125
|
+
const select = button(active ? 'Selected' : 'Select', async () => { await api('/api/my-scope', 'POST', { scopeId: scope.id }); await refresh(); });
|
|
126
|
+
select.disabled = active || !windowOpen;
|
|
127
|
+
row.append(select, button('Download scope JSON', () => downloadPolicy(scope))); list.append(row);
|
|
128
|
+
}
|
|
129
|
+
if (!mine.engagements.length) list.append(make('p', role === 'admin' ? 'No engagement assigned to you yet. Create one, or assign yourself to an approved scope below.' : 'No approved engagements have been assigned to you yet. Ask an administrator.'));
|
|
130
|
+
const selected = mine.engagements.find((item) => item.id === mine.selectedScopeId);
|
|
131
|
+
$('clear-selection').hidden = !selected;
|
|
132
|
+
const selectedWindowOpen = selected && Date.now() >= Date.parse(selected.manifest.engagement_window.start) && Date.now() <= Date.parse(selected.manifest.engagement_window.end);
|
|
133
|
+
$('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.';
|
|
134
|
+
$('connection').textContent = `MCP endpoint: ${location.origin}/mcp. ${selected ? `Current engagement: ${selected.manifest.engagement_id}.` : 'Select an engagement before using tools.'}`;
|
|
135
|
+
if (role === 'admin') {
|
|
136
|
+
state = await api('/api/state');
|
|
137
|
+
renderUsers(); renderUpstreams(); renderPresets(); renderScopes();
|
|
138
|
+
}
|
|
139
|
+
status('Gateway state loaded.');
|
|
140
|
+
}
|
|
141
|
+
function renderUsers() {
|
|
142
|
+
if (mode !== 'access') return;
|
|
143
|
+
const container = $('users'); container.replaceChildren();
|
|
144
|
+
for (const user of state.users) {
|
|
145
|
+
const row = make('div', ''); row.className = 'row'; const bootstrap = bootstrapAdmins.has(user.email);
|
|
146
|
+
row.append(make('span', `${user.email} · ${user.role}${bootstrap ? ' · bootstrap administrator' : ''}`));
|
|
147
|
+
if (!bootstrap) {
|
|
148
|
+
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(); }));
|
|
149
|
+
row.append(button('Remove', async () => { if (confirm(`Remove ${user.email} and all engagement assignments?`)) { await api(`/api/users/${user.id}`, 'DELETE'); await refresh(); } }, 'danger'));
|
|
34
150
|
}
|
|
151
|
+
container.append(row);
|
|
35
152
|
}
|
|
36
|
-
|
|
153
|
+
}
|
|
154
|
+
function renderUpstreams() {
|
|
155
|
+
const container = $('upstreams'); container.replaceChildren();
|
|
37
156
|
for (const item of state.upstreams) {
|
|
38
157
|
const row = make('div', ''); row.className = 'row';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
row.append(
|
|
42
|
-
|
|
43
|
-
|
|
158
|
+
row.append(make('span', `${item.name} · ${item.transport} · ${item.enabled ? 'On' : 'Off'} · ${item.transport === 'stdio' ? item.command : item.url}`));
|
|
159
|
+
row.append(button(item.enabled ? 'Turn off' : 'Turn on', async () => { await api(`/api/upstreams/${item.id}`, 'PATCH', { enabled: !item.enabled }); await refresh(); }));
|
|
160
|
+
row.append(button('Remove', async () => { if (confirm(`Remove server ${item.name}?`)) { await api(`/api/upstreams/${item.id}`, 'DELETE'); await refresh(); } }, 'danger'));
|
|
161
|
+
container.append(row);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function renderPresets() {
|
|
165
|
+
const container = $('presets'); container.replaceChildren();
|
|
166
|
+
for (const preset of presets) {
|
|
167
|
+
const row = make('div', ''); row.className = 'row'; const detail = make('div', `${preset.label} · ${preset.note} `);
|
|
168
|
+
const link = make('a', 'Official information'); link.href = preset.source; link.target = '_blank'; link.rel = 'noopener noreferrer'; detail.append(link);
|
|
169
|
+
row.append(detail);
|
|
170
|
+
if (preset.name) {
|
|
171
|
+
if (mode === 'access' && preset.local) row.append(make('span', 'Local gateway only'));
|
|
172
|
+
else if (state.upstreams.some((item) => item.name === preset.name)) row.append(make('span', 'Added'));
|
|
173
|
+
else row.append(button('Add server', 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(); status(`${preset.label} added and off. Turn it on when ready.`); }));
|
|
174
|
+
}
|
|
175
|
+
container.append(row);
|
|
44
176
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
177
|
+
}
|
|
178
|
+
function renderScopes() {
|
|
179
|
+
const container = $('scopes'); container.replaceChildren();
|
|
180
|
+
for (const scope of state.scopes) {
|
|
48
181
|
const row = make('div', ''); row.className = 'row';
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
182
|
+
const label = make('div', `${scope.manifest.engagement_id} · ${scope.manifest.client}`);
|
|
183
|
+
label.append(make('span', scope.status === 'draft' ? 'Needs approval' : scope.status)); label.lastChild.className = 'pill';
|
|
184
|
+
row.append(label);
|
|
185
|
+
const actions = make('div', ''); actions.className = 'actions';
|
|
186
|
+
actions.append(button(scope.status === 'draft' ? 'Edit' : 'View', () => loadScope(scope)));
|
|
187
|
+
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.'); }));
|
|
188
|
+
actions.append(button('Download JSON', () => downloadPolicy(scope)));
|
|
189
|
+
if (scope.status === 'draft') actions.append(button('Review and approve', () => approveScope(scope)));
|
|
190
|
+
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'));
|
|
191
|
+
row.append(actions); container.append(row);
|
|
192
|
+
if (scope.status === 'approved' && mode === 'access') {
|
|
193
|
+
const assignment = make('div', ''); assignment.className = 'choices';
|
|
194
|
+
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; });
|
|
195
|
+
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(); }));
|
|
196
|
+
container.append(assignment);
|
|
54
197
|
}
|
|
55
|
-
scopes.append(row);
|
|
56
198
|
}
|
|
57
|
-
|
|
58
|
-
status('Gateway state loaded.');
|
|
199
|
+
if (!state.scopes.length) container.append(make('p', 'No scopes yet. Use the editor below to create a draft.'));
|
|
59
200
|
}
|
|
60
|
-
function loadScope(
|
|
61
|
-
editing =
|
|
62
|
-
const m =
|
|
201
|
+
function loadScope(scope) {
|
|
202
|
+
editing = scope.status === 'draft' ? scope.id : null; viewingScopeId = scope.id; dirty = false;
|
|
203
|
+
const m = scope.manifest;
|
|
63
204
|
$('engagement').value = m.engagement_id; $('client').value = m.client; $('operator').value = m.operator;
|
|
64
|
-
$('starts').value = m.engagement_window.start
|
|
65
|
-
$('profile').value =
|
|
205
|
+
$('starts').value = localDate(m.engagement_window.start); $('ends').value = localDate(m.engagement_window.end);
|
|
206
|
+
$('profile').value = scope.profile; setLines('ips', m.authorized_targets.ip_ranges); setLines('domains', m.authorized_targets.domains);
|
|
66
207
|
setLines('urls', m.authorized_targets.urls); setLines('cloud', m.authorized_targets.cloud_accounts);
|
|
67
|
-
setLines('excluded-targets', m.excluded_targets); setLines('
|
|
68
|
-
|
|
69
|
-
|
|
208
|
+
setLines('excluded-targets', m.excluded_targets); setLines('hosts', scope.allowedHosts);
|
|
209
|
+
setChecked(authorizedChoices, m.authorized_techniques); setChecked(excludedChoices, m.excluded_techniques);
|
|
210
|
+
for (const field of $('editor-card').querySelectorAll('input,select,textarea')) field.disabled = scope.status !== 'draft';
|
|
211
|
+
$('save-scope').disabled = scope.status !== 'draft';
|
|
212
|
+
$('editor-help').textContent = scope.status === 'draft' ? 'Editing a saved scope. Replace any example details and choose a future end date before approval.' : 'Viewing an immutable scope. Choose Duplicate to create an editable scope.';
|
|
213
|
+
reviewForm(); $('editor-card').scrollIntoView({ behavior: 'smooth' });
|
|
70
214
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
$('
|
|
215
|
+
function clearEditor() {
|
|
216
|
+
editing = null; viewingScopeId = null; dirty = false;
|
|
217
|
+
for (const id of ['engagement', 'client', 'operator', 'starts', 'ends', 'ips', 'domains', 'urls', 'cloud', 'excluded-targets', 'hosts']) $(id).value = '';
|
|
218
|
+
$('profile').value = mode === 'access' ? 'connected' : 'isolated'; setChecked(authorizedChoices, []); setChecked(excludedChoices, []);
|
|
219
|
+
for (const field of $('editor-card').querySelectorAll('input,select,textarea')) field.disabled = false;
|
|
220
|
+
$('save-scope').disabled = false; $('editor-help').textContent = 'New scope. Enter the client, exact authorized targets, window and techniques, then Save.'; reviewForm();
|
|
221
|
+
}
|
|
222
|
+
function loadExample(kind) {
|
|
223
|
+
if (dirty && !confirm('Discard unsaved scope changes and load a template?')) return;
|
|
224
|
+
clearEditor();
|
|
225
|
+
if (kind === 'web') setChecked(authorizedChoices, ['recon', 'web_app']);
|
|
226
|
+
if (kind === 'internal') setChecked(authorizedChoices, ['recon']);
|
|
227
|
+
if (kind === 'cloud') setChecked(authorizedChoices, ['cloud_audit']);
|
|
228
|
+
$('editor-help').textContent = 'Template loaded. Enter your real engagement ID, client, operator, authorized targets and signed time window. Review the selected techniques against the rules of engagement.';
|
|
229
|
+
reviewForm();
|
|
230
|
+
}
|
|
231
|
+
$('connect').onclick = () => { sessionStorage.setItem('gateway-token', token.value); refresh().catch((error) => status(error.message, true)); };
|
|
232
|
+
$('create-engagement').onclick = newScope;
|
|
233
|
+
$('import-policy').onclick = () => $('policy-file').click();
|
|
234
|
+
$('policy-file').onchange = async () => {
|
|
235
|
+
const file = $('policy-file').files?.[0]; $('policy-file').value = '';
|
|
236
|
+
if (!file) return;
|
|
88
237
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
await api(
|
|
96
|
-
|
|
97
|
-
|
|
238
|
+
if (!file.name.toLowerCase().endsWith('.json') || file.size > 1024 * 1024 || file.size === 0) throw new Error('Choose a nonempty JSON policy file under 1 MB.');
|
|
239
|
+
const input = JSON.parse(await file.text());
|
|
240
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Policy file must contain a JSON object.');
|
|
241
|
+
const manifest = input.manifest || input;
|
|
242
|
+
const profile = input.profile || input.gateway?.profile || (mode === 'access' ? 'connected' : 'isolated');
|
|
243
|
+
const allowedHosts = input.allowedHosts || input.gateway?.allowed_hosts || [];
|
|
244
|
+
const imported = await api('/api/scopes', 'POST', { manifest, profile, allowedHosts });
|
|
245
|
+
await refresh(); loadScope(imported);
|
|
246
|
+
status('Policy imported as a saved scope. Review and approve it before assigning users.');
|
|
247
|
+
} catch (error) { status(`Could not import policy: ${error.message}`, true); }
|
|
98
248
|
};
|
|
249
|
+
$('clear-selection').onclick = () => api('/api/my-scope', 'POST', { scopeId: null }).then(refresh).catch((error) => status(error.message, true));
|
|
250
|
+
$('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); } };
|
|
251
|
+
$('add-upstream').onclick = async () => { try { const transport = $('up-transport').value; await api('/api/upstreams', 'POST', { name: $('up-name').value.trim(), enabled: false, transport,
|
|
252
|
+
...(transport === 'stdio' ? { command: $('up-target').value.trim(), args: lines('up-args') } : { url: $('up-target').value.trim(), args: [] }) });
|
|
253
|
+
$('up-name').value = ''; $('up-target').value = ''; $('up-args').value = ''; await refresh(); status('Server added and off. Turn it on when ready.'); } catch (error) { status(error.message, true); } };
|
|
254
|
+
$('new-scope').onclick = newScope;
|
|
255
|
+
for (const kind of ['web', 'internal', 'cloud']) $(`example-${kind}`).onclick = () => loadExample(kind);
|
|
256
|
+
$('save-scope').onclick = async () => { try { if (viewingScopeId && !editing) throw new Error('Duplicate an approved or archived scope to edit it.');
|
|
257
|
+
const scope = draftFromForm(); const result = await api(editing ? `/api/scopes/${editing}` : '/api/scopes', editing ? 'PUT' : 'POST', scope);
|
|
258
|
+
const saved = editing ? result.scopes.find((item) => item.id === editing) : result;
|
|
259
|
+
await refresh(); loadScope(saved); status('Scope saved. Review it, then approve and assign users.');
|
|
260
|
+
} catch (error) { status(error.message, true); } };
|
|
261
|
+
$('approve-current').onclick = () => { const scope = state?.scopes.find((item) => item.id === viewingScopeId); if (scope) approveScope(scope).catch((error) => status(error.message, true)); };
|
|
262
|
+
$('assign-me').onclick = async () => { try {
|
|
263
|
+
const scope = state?.scopes.find((item) => item.id === viewingScopeId);
|
|
264
|
+
if (!scope || !email) throw new Error('Select an approved scope first.');
|
|
265
|
+
await api(`/api/scopes/${scope.id}/assignments`, 'PUT', { emails: [...new Set([...scope.assignedUserEmails, email])] });
|
|
266
|
+
await refresh(); loadScope(state.scopes.find((item) => item.id === scope.id)); status('Engagement assigned to you. Choose Use engagement when its window opens.');
|
|
267
|
+
} catch (error) { status(error.message, true); } };
|
|
268
|
+
$('use-current').onclick = async () => { try {
|
|
269
|
+
if (!viewingScopeId) throw new Error('Select an approved scope first.');
|
|
270
|
+
await api('/api/my-scope', 'POST', { scopeId: viewingScopeId }); await refresh(); status('Engagement selected for your MCP calls.');
|
|
271
|
+
} catch (error) { status(error.message, true); } };
|
|
99
272
|
async function initialize() {
|
|
100
273
|
const response = await fetch('/api/session', { credentials: 'same-origin', cache: 'no-store' });
|
|
101
|
-
const session = await response.json();
|
|
102
|
-
|
|
103
|
-
mode = session.mode;
|
|
274
|
+
const session = await response.json(); if (!response.ok) throw new Error(session.error || `HTTP ${response.status}`);
|
|
275
|
+
mode = session.mode; role = session.role || 'admin'; email = session.email || null;
|
|
104
276
|
bootstrapAdmins = new Set(session.bootstrapAdminEmails || []);
|
|
277
|
+
$('identity').textContent = email ? `${email} · ${role}` : 'Local gateway';
|
|
105
278
|
$('token-card').hidden = mode !== 'token';
|
|
106
|
-
|
|
279
|
+
for (const card of document.querySelectorAll('.admin-only')) card.hidden = role !== 'admin' || (card.id === 'users-card' && mode !== 'access');
|
|
280
|
+
clearEditor();
|
|
107
281
|
if (mode === 'access' || token.value) await refresh();
|
|
282
|
+
reviewForm();
|
|
108
283
|
}
|
|
109
|
-
initialize().catch(
|
|
284
|
+
initialize().catch((error) => status(error.message, true));
|
|
@@ -9,7 +9,6 @@ export declare class GatewayRuntime {
|
|
|
9
9
|
private clients;
|
|
10
10
|
private pending;
|
|
11
11
|
private proxies;
|
|
12
|
-
private proxyScope?;
|
|
13
12
|
constructor(store: GatewayStore, hosted: boolean, requiredAuditSink?: ((entry: AuditEntry) => Promise<void>) | undefined, isActorAllowed?: ((email?: string) => boolean) | undefined);
|
|
14
13
|
sync(): Promise<void>;
|
|
15
14
|
private scopeProxy;
|
|
@@ -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;
|
|
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"}
|