@integsec/agentic-pentest-proxy 0.3.7 → 0.3.9
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/cloudflare/wrangler.jsonc +1 -1
- package/dist/bin/integsec-agentic-pentest-bridge.js +2 -2
- package/dist/bin/integsec-agentic-pentest-scanners-mcp.d.ts +3 -0
- package/dist/bin/integsec-agentic-pentest-scanners-mcp.d.ts.map +1 -0
- package/dist/bin/integsec-agentic-pentest-scanners-mcp.js +39 -0
- package/dist/bin/integsec-agentic-pentest-scanners-mcp.js.map +1 -0
- package/dist/src/config.js +1 -1
- package/dist/src/gateway/admin.js +132 -17
- package/dist/src/gateway/command.d.ts +20 -0
- package/dist/src/gateway/command.d.ts.map +1 -0
- package/dist/src/gateway/command.js +80 -0
- package/dist/src/gateway/command.js.map +1 -0
- package/dist/src/gateway/runtime.d.ts +9 -0
- package/dist/src/gateway/runtime.d.ts.map +1 -1
- package/dist/src/gateway/runtime.js +116 -9
- 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 +13 -1
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/state.d.ts +16 -1
- package/dist/src/gateway/state.d.ts.map +1 -1
- package/dist/src/gateway/state.js +29 -6
- 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/manifest-schema.d.ts +1 -0
- package/dist/src/manifest-schema.d.ts.map +1 -1
- package/dist/src/manifest-schema.js +1 -0
- package/dist/src/manifest-schema.js.map +1 -1
- package/dist/src/proxy.d.ts +3 -1
- package/dist/src/proxy.d.ts.map +1 -1
- package/dist/src/proxy.js +10 -4
- package/dist/src/proxy.js.map +1 -1
- package/dist/src/scanners/runner.d.ts +4 -0
- package/dist/src/scanners/runner.d.ts.map +1 -0
- package/dist/src/scanners/runner.js +95 -0
- package/dist/src/scanners/runner.js.map +1 -0
- package/dist/src/technique-checker.d.ts.map +1 -1
- package/dist/src/technique-checker.js +2 -1
- package/dist/src/technique-checker.js.map +1 -1
- package/dist/src/tui/DecisionRow.d.ts.map +1 -1
- package/dist/src/tui/DecisionRow.js +2 -1
- package/dist/src/tui/DecisionRow.js.map +1 -1
- package/dist/src/tui/destination.d.ts +4 -0
- package/dist/src/tui/destination.d.ts.map +1 -0
- package/dist/src/tui/destination.js +43 -0
- package/dist/src/tui/destination.js.map +1 -0
- package/dist/src/types.d.ts +2 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/validator.d.ts +5 -2
- package/dist/src/validator.d.ts.map +1 -1
- package/dist/src/validator.js +106 -3
- package/dist/src/validator.js.map +1 -1
- package/docs/managed-gateway.md +14 -2
- package/docs/security.md +6 -0
- package/docs/tui.md +3 -1
- package/package.json +5 -3
|
@@ -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.9" });
|
|
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.9" }, { 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());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integsec-agentic-pentest-scanners-mcp.d.ts","sourceRoot":"","sources":["../../bin/integsec-agentic-pentest-scanners-mcp.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { nmapArgs, nucleiArgs, runScanner } from "../src/scanners/runner.js";
|
|
6
|
+
const mode = process.argv[2];
|
|
7
|
+
if (mode !== "nmap" && mode !== "nuclei") {
|
|
8
|
+
console.error("Usage: integsec-agentic-pentest-scanners-mcp <nmap|nuclei>");
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
const tool = mode === "nmap" ? {
|
|
12
|
+
name: "nmap_scan", description: "Scan one approved host with Nmap TCP connect scanning. Limited to 100 ports and two minutes.",
|
|
13
|
+
inputSchema: { type: "object", properties: {
|
|
14
|
+
target: { type: "string", description: "One authorized IP address or hostname." },
|
|
15
|
+
ports: { type: "string", description: "Up to 100 TCP ports, e.g. 80,443,8000-8010. Defaults to 80,443." },
|
|
16
|
+
serviceDetection: { type: "boolean", description: "Probe open ports for service versions." },
|
|
17
|
+
}, required: ["target"], additionalProperties: false },
|
|
18
|
+
} : {
|
|
19
|
+
name: "nuclei_scan", description: "Run a bounded HTTP Nuclei template scan against one approved URL. Disables OAST and automatic updates.",
|
|
20
|
+
inputSchema: { type: "object", properties: {
|
|
21
|
+
url: { type: "string", description: "One authorized HTTP or HTTPS URL." },
|
|
22
|
+
}, required: ["url"], additionalProperties: false },
|
|
23
|
+
};
|
|
24
|
+
const server = new Server({ name: `integsec-${mode}-mcp`, version: "0.3.9" }, { capabilities: { tools: {} } });
|
|
25
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [tool] }));
|
|
26
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
27
|
+
if (request.params.name !== tool.name)
|
|
28
|
+
return { content: [{ type: "text", text: "Unknown scanner tool." }], isError: true };
|
|
29
|
+
try {
|
|
30
|
+
const input = request.params.arguments ?? {};
|
|
31
|
+
const args = mode === "nmap" ? nmapArgs(input) : nucleiArgs(input);
|
|
32
|
+
return { content: [{ type: "text", text: await runScanner(mode, args) }] };
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true };
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
await server.connect(new StdioServerTransport());
|
|
39
|
+
//# sourceMappingURL=integsec-agentic-pentest-scanners-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integsec-agentic-pentest-scanners-mcp.js","sourceRoot":"","sources":["../../bin/integsec-agentic-pentest-scanners-mcp.ts"],"names":[],"mappings":"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"}
|
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.9",
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
//# sourceMappingURL=config.js.map
|
|
@@ -9,6 +9,44 @@ let editing = null;
|
|
|
9
9
|
let viewingScopeId = null;
|
|
10
10
|
let dirty = false;
|
|
11
11
|
let bootstrapAdmins = new Set();
|
|
12
|
+
let upstreamStatuses = {};
|
|
13
|
+
const osNames = { windows: 'Windows', macos: 'macOS', linux: 'Linux (Debian/Ubuntu)' };
|
|
14
|
+
const platform = navigator.userAgentData?.platform || navigator.platform || navigator.userAgent || '';
|
|
15
|
+
const detectedOS = /win/i.test(platform) ? 'windows' : /mac/i.test(platform) ? 'macos' : 'linux';
|
|
16
|
+
let selectedOS = localStorage.getItem('gateway-docs-os');
|
|
17
|
+
if (!osNames[selectedOS]) selectedOS = detectedOS;
|
|
18
|
+
const installHelp = {
|
|
19
|
+
burp: {
|
|
20
|
+
windows: { command: "Start-Process 'https://portswigger.net/burp/releases'", note: 'Open the official installer, then enable MCP inside Burp.' },
|
|
21
|
+
macos: { command: "open 'https://portswigger.net/burp/releases'", note: 'Open the official installer, then enable MCP inside Burp.' },
|
|
22
|
+
linux: { command: "xdg-open 'https://portswigger.net/burp/releases'", note: 'Open the official installer, then enable MCP inside Burp.' },
|
|
23
|
+
},
|
|
24
|
+
semgrep: {
|
|
25
|
+
windows: { command: 'py -m pip install --user semgrep\nsemgrep --version', note: 'Requires Python and pip; confirm the Scripts folder is on PATH.' },
|
|
26
|
+
macos: { command: 'brew install semgrep\nsemgrep --version', note: 'Requires Homebrew.' },
|
|
27
|
+
linux: { command: 'pipx install semgrep\nsemgrep --version', note: 'Requires pipx; install it through your distribution first.' },
|
|
28
|
+
},
|
|
29
|
+
trivy: {
|
|
30
|
+
windows: { command: "Start-Process 'https://github.com/aquasecurity/trivy/releases/latest'", note: 'Install the official Windows archive, put trivy on PATH, then run trivy plugin install mcp.' },
|
|
31
|
+
macos: { command: 'brew install trivy\ntrivy plugin install mcp\ntrivy --version', note: 'Requires Homebrew.' },
|
|
32
|
+
linux: { command: "sudo apt-get update\nsudo apt-get install -y wget gnupg\nwget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null\necho 'deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main' | sudo tee /etc/apt/sources.list.d/trivy.list\nsudo apt-get update\nsudo apt-get install -y trivy\ntrivy plugin install mcp\ntrivy --version", note: 'Debian and Ubuntu. For other distributions, follow the official Trivy install guide.' },
|
|
33
|
+
},
|
|
34
|
+
nmap: {
|
|
35
|
+
windows: { command: "Start-Process 'https://nmap.org/download.html'", note: 'Run the official Windows installer. The MCP adapter is already in the gateway package.' },
|
|
36
|
+
macos: { command: "open 'https://nmap.org/download.html'", note: 'Run the official macOS installer. The MCP adapter is already in the gateway package.' },
|
|
37
|
+
linux: { command: 'sudo apt-get update\nsudo apt-get install nmap\nnmap --version', note: 'Debian and Ubuntu. Other distributions: use the official Nmap download page.' },
|
|
38
|
+
},
|
|
39
|
+
nuclei: {
|
|
40
|
+
windows: { command: 'go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\nnuclei -version', note: 'Requires Go; add the Go bin folder to PATH. The MCP adapter is in the gateway package.' },
|
|
41
|
+
macos: { command: 'brew install nuclei\nnuclei -version', note: 'Requires Homebrew. The MCP adapter is in the gateway package.' },
|
|
42
|
+
linux: { command: 'go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\nnuclei -version', note: 'Requires Go; add the Go bin folder to PATH. The MCP adapter is in the gateway package.' },
|
|
43
|
+
},
|
|
44
|
+
snyk: {
|
|
45
|
+
windows: { command: 'npm install -g snyk\nsnyk auth', note: 'Requires Node.js and a Snyk account.' },
|
|
46
|
+
macos: { command: 'npm install -g snyk\nsnyk auth', note: 'Requires Node.js and a Snyk account.' },
|
|
47
|
+
linux: { command: 'npm install -g snyk\nsnyk auth', note: 'Requires Node.js and a Snyk account.' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
12
50
|
const techniques = [
|
|
13
51
|
['recon', 'Reconnaissance and discovery'], ['web_app', 'Web and API testing'],
|
|
14
52
|
['exploitation', 'Exploitation'], ['cloud_audit', 'Cloud assessment'],
|
|
@@ -16,10 +54,12 @@ const techniques = [
|
|
|
16
54
|
['social_engineering', 'Social engineering'],
|
|
17
55
|
];
|
|
18
56
|
const presets = [
|
|
19
|
-
{ name: 'burp', label: 'Burp Suite', transport: 'sse', url: 'http://127.0.0.1:9876', local: true, source: 'https://
|
|
20
|
-
{ name: 'semgrep', label: 'Semgrep', transport: 'stdio', command: 'semgrep', args: ['mcp'], local: true, source: 'https://
|
|
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: '
|
|
57
|
+
{ name: 'burp', label: 'Burp Suite', transport: 'sse', url: 'http://127.0.0.1:9876', local: true, source: 'https://portswigger.net/bappstore/9952290f04ed4f628e624d0aa9dccebc', note: 'Enable the official MCP extension inside the tester’s local Burp.' },
|
|
58
|
+
{ name: 'semgrep', label: 'Semgrep', transport: 'stdio', command: 'semgrep', args: ['mcp'], local: true, source: 'https://semgrep.dev/products/community-edition/', note: 'Install and configure the official Semgrep CLI first.' },
|
|
59
|
+
{ name: 'trivy', label: 'Trivy', transport: 'stdio', command: 'trivy', args: ['mcp'], local: true, source: 'https://github.com/aquasecurity/trivy-mcp/blob/main/docs/quickstart.md', note: 'Install Trivy and its official MCP plugin first; pin and review your version.' },
|
|
60
|
+
{ name: 'nmap', label: 'Nmap', transport: 'stdio', command: 'integsec-agentic-pentest-scanners-mcp', args: ['nmap'], local: true, source: 'https://nmap.org/download.html', note: 'Install Nmap locally first. The bundled MCP adapter offers one-host, rate-limited TCP scans.' },
|
|
61
|
+
{ name: 'nuclei', label: 'Nuclei', transport: 'stdio', command: 'integsec-agentic-pentest-scanners-mcp', args: ['nuclei'], local: true, source: 'https://docs.projectdiscovery.io/opensource/nuclei/install', note: 'Install ProjectDiscovery Nuclei locally first. The bundled MCP adapter limits HTTP scans and disables OAST.' },
|
|
62
|
+
{ name: 'snyk', label: 'Snyk', transport: 'stdio', command: 'snyk', args: ['mcp', '-t', 'stdio'], local: true, source: 'https://docs.snyk.io/developer-tools/snyk-cli/install-the-snyk-cli', note: 'Install the Snyk CLI and authenticate locally first.' },
|
|
23
63
|
{ 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
64
|
{ 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
65
|
{ label: 'Prowler Cloud', source: 'https://prowler.com/prowler-cloud', note: 'Reference only: its hosted MCP endpoint requires an API key.' },
|
|
@@ -54,6 +94,7 @@ function configureServerUI() {
|
|
|
54
94
|
: 'Use the command or URL from the MCP server’s own setup instructions. Adding an entry does not install the tool.';
|
|
55
95
|
$('hosted-server-choice').hidden = !hosted;
|
|
56
96
|
$('local-presets').hidden = hosted;
|
|
97
|
+
$('command-panel').hidden = hosted;
|
|
57
98
|
$('local-setup').hidden = !hosted;
|
|
58
99
|
$('add-server-panel').hidden = hosted;
|
|
59
100
|
if (hosted) chooseServerPath('local');
|
|
@@ -75,12 +116,67 @@ async function api(path, method = 'GET', data) {
|
|
|
75
116
|
}
|
|
76
117
|
const lines = (id) => $(id).value.split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
|
|
77
118
|
const setLines = (id, values) => { $(id).value = values.join('\n'); };
|
|
119
|
+
const localPathField = document.createElement('label');
|
|
120
|
+
localPathField.textContent = 'Approved local code folders (Semgrep and Trivy; one absolute path per line)';
|
|
121
|
+
const localPathInput = document.createElement('textarea');
|
|
122
|
+
localPathInput.id = 'local-paths';
|
|
123
|
+
localPathInput.placeholder = 'Absolute folder path containing authorized source code';
|
|
124
|
+
localPathField.append(localPathInput);
|
|
125
|
+
$('hosts').closest('label').after(localPathField);
|
|
126
|
+
const allLocalLabel = document.createElement('label'); allLocalLabel.style.cssText = 'display:flex;align-items:center;gap:8px';
|
|
127
|
+
const allLocalInput = document.createElement('input'); allLocalInput.type = 'checkbox'; allLocalInput.id = 'all-local-paths';
|
|
128
|
+
allLocalLabel.append(allLocalInput, document.createTextNode('Allow all local code paths (explicit administrator approval; may expose unrelated files)'));
|
|
129
|
+
localPathField.after(allLocalLabel);
|
|
130
|
+
allLocalInput.onchange = () => { localPathInput.disabled = allLocalInput.checked; };
|
|
78
131
|
const make = (tag, value) => { const node = document.createElement(tag); node.textContent = value; return node; };
|
|
132
|
+
const osBar = make('div', ''); osBar.className = 'help'; osBar.style.cssText = 'margin:0;padding:10px 5vw;display:flex;align-items:center;gap:12px;flex-wrap:wrap';
|
|
133
|
+
const osLabel = make('label', 'Install commands for '); osLabel.style.cssText = 'display:flex;align-items:center;gap:8px';
|
|
134
|
+
const osChoice = document.createElement('select'); osChoice.id = 'docs-os'; osChoice.style.cssText = 'width:auto;margin:0';
|
|
135
|
+
for (const [value, label] of Object.entries(osNames)) { const option = make('option', label); option.value = value; osChoice.append(option); }
|
|
136
|
+
osChoice.value = selectedOS; osLabel.append(osChoice); osBar.append(osLabel, make('span', 'Choose the tester’s operating system. The browser picked a starting value.'));
|
|
137
|
+
document.querySelector('header').after(osBar);
|
|
138
|
+
const toolStyles = document.createElement('style');
|
|
139
|
+
toolStyles.textContent = '#upstreams-card{grid-column:1/-1}#presets,#local-tool-guide-items{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;margin-top:12px}#presets .row,#local-tool-guide-items .row{display:block;border:1px solid #294454;border-radius:9px;padding:14px}#presets .row>*:first-child,#local-tool-guide-items .row>*:first-child{min-width:0}#presets pre,#local-tool-guide-items pre{font-size:13px}';
|
|
140
|
+
document.head.append(toolStyles);
|
|
141
|
+
const localGuide = make('section', ''); localGuide.id = 'local-tool-guide'; localGuide.className = 'card wide'; localGuide.hidden = true;
|
|
142
|
+
localGuide.append(make('h2', 'Local MCP tools'), make('p', 'Install these on the tester’s computer, then add and turn them on in that computer’s local gateway. The hosted gateway cannot run laptop tools.'));
|
|
143
|
+
const localGuideItems = make('div', ''); localGuideItems.id = 'local-tool-guide-items'; localGuide.append(localGuideItems);
|
|
144
|
+
$('my-card').after(localGuide);
|
|
145
|
+
function updateInstallOS() {
|
|
146
|
+
const shell = selectedOS === 'windows' ? 'PowerShell' : 'a terminal';
|
|
147
|
+
$('local-command').textContent = 'npm install -g @integsec/agentic-pentest-proxy\nintegsec-gateway start';
|
|
148
|
+
$('local-command').closest('li').firstChild.textContent = `Copy these commands into ${shell}:`;
|
|
149
|
+
renderPresets();
|
|
150
|
+
}
|
|
151
|
+
osChoice.onchange = () => { selectedOS = osChoice.value; localStorage.setItem('gateway-docs-os', selectedOS); updateInstallOS(); };
|
|
79
152
|
function button(label, action, style = 'secondary') {
|
|
80
153
|
const node = make('button', label); node.className = style;
|
|
81
154
|
node.addEventListener('click', () => Promise.resolve().then(action).catch((error) => status(error.message, true)));
|
|
82
155
|
return node;
|
|
83
156
|
}
|
|
157
|
+
const commandPanel = make('section', ''); commandPanel.id = 'command-panel'; commandPanel.hidden = true;
|
|
158
|
+
commandPanel.append(make('h3', 'Add a local command as an MCP tool'),
|
|
159
|
+
make('p', 'Use an installed executable and fixed arguments. Put {target} on one argument line. The AI can supply one approved host or URL; it cannot supply flags. Review the command and its configuration before turning it on.'));
|
|
160
|
+
const commandFields = make('div', ''); commandFields.className = 'grid';
|
|
161
|
+
const commandNameLabel = make('label', 'Short name'); const commandName = document.createElement('input'); commandName.id = 'cli-name'; commandName.placeholder = 'my_scanner'; commandNameLabel.append(commandName);
|
|
162
|
+
const commandExeLabel = make('label', 'Installed executable'); const commandExe = document.createElement('input'); commandExe.id = 'cli-command'; commandExe.placeholder = 'scanner'; commandExeLabel.append(commandExe);
|
|
163
|
+
const commandTargetLabel = make('label', 'Target type'); const commandTarget = document.createElement('select'); commandTarget.id = 'cli-target';
|
|
164
|
+
for (const [value, label] of [['host', 'Hostname or IP'], ['url', 'HTTP or HTTPS URL']]) { const option = make('option', label); option.value = value; commandTarget.append(option); }
|
|
165
|
+
commandTargetLabel.append(commandTarget);
|
|
166
|
+
const commandTechniqueLabel = make('label', 'Technique category'); const commandTechnique = document.createElement('select'); commandTechnique.id = 'cli-technique';
|
|
167
|
+
for (const [value, label] of techniques) { const option = make('option', label); option.value = value; commandTechnique.append(option); }
|
|
168
|
+
commandTechniqueLabel.append(commandTechnique);
|
|
169
|
+
commandFields.append(commandNameLabel, commandExeLabel, commandTargetLabel, commandTechniqueLabel); commandPanel.append(commandFields);
|
|
170
|
+
const commandArgsLabel = make('label', 'Fixed arguments, one per line'); const commandArgs = document.createElement('textarea'); commandArgs.id = 'cli-args'; commandArgs.placeholder = '-u\n{target}\n-json'; commandArgsLabel.append(commandArgs); commandPanel.append(commandArgsLabel);
|
|
171
|
+
commandPanel.append(button('Add command (off)', async () => {
|
|
172
|
+
const name = commandName.value.trim(); const executable = commandExe.value.trim(); const args = commandArgs.value.split(/\r?\n/).map((arg) => arg.trim()).filter(Boolean);
|
|
173
|
+
if (!/^[a-z][a-z0-9_-]{0,31}$/.test(name)) throw new Error('Use a short lowercase command name starting with a letter.');
|
|
174
|
+
if (!executable) throw new Error('Enter the installed executable name or path.');
|
|
175
|
+
if (args.filter((arg) => arg === '{target}').length !== 1) throw new Error('Put {target} on exactly one argument line.');
|
|
176
|
+
await api('/api/upstreams', 'POST', { name, enabled: false, transport: 'cli', command: executable, args, targetKind: commandTarget.value, technique: commandTechnique.value });
|
|
177
|
+
commandName.value = ''; commandExe.value = ''; commandArgs.value = ''; await refresh(); status('Local command saved and off. Review it, then turn it on.');
|
|
178
|
+
}));
|
|
179
|
+
$('local-presets').after(commandPanel);
|
|
84
180
|
function choiceList(container, name) {
|
|
85
181
|
const result = [];
|
|
86
182
|
for (const [value, label] of techniques) {
|
|
@@ -102,13 +198,13 @@ function draftFromForm() {
|
|
|
102
198
|
return { profile: $('profile').value, allowedHosts: lines('hosts'), manifest: {
|
|
103
199
|
engagement_id: $('engagement').value.trim(), client: $('client').value.trim(), operator: $('operator').value.trim(),
|
|
104
200
|
authorized_targets: { ip_ranges: lines('ips'), domains: lines('domains'), urls: lines('urls'), cloud_accounts: lines('cloud') },
|
|
105
|
-
excluded_targets: lines('excluded-targets'), authorized_techniques: checked(authorizedChoices),
|
|
201
|
+
excluded_targets: lines('excluded-targets'), authorized_local_paths: allLocalInput.checked ? ['*'] : lines('local-paths'), authorized_techniques: checked(authorizedChoices),
|
|
106
202
|
excluded_techniques: checked(excludedChoices), engagement_window: { start: start.toISOString(), end: end.toISOString() },
|
|
107
203
|
} };
|
|
108
204
|
}
|
|
109
205
|
function summarize(scope) {
|
|
110
206
|
const m = scope.manifest;
|
|
111
|
-
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)'}`;
|
|
207
|
+
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)'}\nLocal code folders: ${m.authorized_local_paths?.includes('*') ? 'ALL accessible local paths' : (m.authorized_local_paths || []).join(', ') || '(none)'}\nAuthorized techniques: ${m.authorized_techniques.join(', ') || '(none)'}\nExcluded techniques: ${m.excluded_techniques.join(', ') || '(none)'}\nAllowed MCP hosts: ${scope.allowedHosts.join(', ') || '(none)'}`;
|
|
112
208
|
}
|
|
113
209
|
function downloadPolicy(scope) {
|
|
114
210
|
const policy = { ...scope.manifest, gateway: { profile: scope.profile, allowed_hosts: scope.allowedHosts || [] } };
|
|
@@ -143,6 +239,7 @@ function reviewForm() {
|
|
|
143
239
|
if (m.authorized_targets.domains.some((value) => value.includes('example.'))) warnings.push('Replace documentation domains with real authorized domains.');
|
|
144
240
|
if (m.authorized_targets.urls.some((value) => value.includes('example.'))) warnings.push('Replace documentation URLs with real authorized URLs.');
|
|
145
241
|
if (Date.parse(m.engagement_window.end) <= Date.now()) warnings.push('Choose an end time in the future.');
|
|
242
|
+
if (m.authorized_local_paths.includes('*')) warnings.push('All accessible local paths are allowed for Semgrep and Trivy. Review this broad access before approval.');
|
|
146
243
|
$('scope-review').textContent = `${warnings.length ? `Before approval: ${warnings.join(' ')}\n\n` : ''}${summarize(scope)}`;
|
|
147
244
|
} catch (error) { $('scope-review').textContent = error.message; }
|
|
148
245
|
updateEditorActions();
|
|
@@ -172,8 +269,11 @@ async function refresh() {
|
|
|
172
269
|
$('connection').textContent = `MCP endpoint: ${location.origin}/mcp. ${selected ? `Current engagement: ${selected.manifest.engagement_id}.` : 'Select an engagement before using tools.'}`;
|
|
173
270
|
if (role === 'admin') {
|
|
174
271
|
state = await api('/api/state');
|
|
175
|
-
|
|
272
|
+
upstreamStatuses = await api('/api/upstreams/status');
|
|
273
|
+
renderUsers(); renderUpstreams(); renderScopes();
|
|
274
|
+
for (const card of document.querySelectorAll('.admin-only')) card.hidden = card.id === 'users-card' && mode !== 'access';
|
|
176
275
|
}
|
|
276
|
+
renderPresets();
|
|
177
277
|
status('Gateway state loaded.');
|
|
178
278
|
}
|
|
179
279
|
function renderUsers() {
|
|
@@ -193,15 +293,20 @@ function renderUpstreams() {
|
|
|
193
293
|
const container = $('upstreams'); container.replaceChildren();
|
|
194
294
|
for (const item of state.upstreams) {
|
|
195
295
|
const row = make('div', ''); row.className = 'row';
|
|
196
|
-
|
|
197
|
-
|
|
296
|
+
const check = upstreamStatuses[item.id] || { state: item.enabled ? 'not_checked' : 'off' };
|
|
297
|
+
const connection = check.state === 'connected' ? `Connected · ${check.toolCount} tools found` : check.state === 'error' ? 'Connection failed' : check.state === 'configured' ? 'Runs on demand' : check.state === 'not_checked' ? 'Not checked' : 'Off';
|
|
298
|
+
row.append(make('span', `${item.name} · ${item.transport === 'cli' ? 'local command' : item.transport} · ${item.enabled ? 'On' : 'Off'} · ${connection} · ${['stdio', 'cli'].includes(item.transport) ? item.command : item.url}`));
|
|
299
|
+
if (check.detail && item.enabled) row.append(make('span', check.detail));
|
|
300
|
+
row.append(button(item.enabled ? 'Turn off' : 'Turn on', async () => { await api(`/api/upstreams/${item.id}`, 'PATCH', { enabled: !item.enabled }); await refresh(); status(`${item.name} turned ${item.enabled ? 'off' : 'on'} immediately. Refresh the tool list in a client that caches it.`); }));
|
|
301
|
+
if (item.enabled && item.transport !== 'cli') row.append(button('Check connection', async () => { await api(`/api/upstreams/${item.id}/check`, 'POST'); await refresh(); }));
|
|
198
302
|
row.append(button('Remove', async () => { if (confirm(`Remove server ${item.name}?`)) { await api(`/api/upstreams/${item.id}`, 'DELETE'); await refresh(); } }, 'danger'));
|
|
199
303
|
container.append(row);
|
|
200
304
|
}
|
|
201
305
|
if (!state.upstreams.length) container.append(make('p', 'No MCP servers added yet. Choose a connection path below.'));
|
|
202
306
|
}
|
|
203
307
|
function renderPresets() {
|
|
204
|
-
const container = $('presets'); container.replaceChildren();
|
|
308
|
+
const container = mode === 'access' ? localGuideItems : $('presets'); container.replaceChildren();
|
|
309
|
+
localGuide.hidden = mode !== 'access';
|
|
205
310
|
const references = $('references'); references.replaceChildren();
|
|
206
311
|
for (const preset of presets) {
|
|
207
312
|
if (!preset.name) {
|
|
@@ -210,12 +315,17 @@ function renderPresets() {
|
|
|
210
315
|
const link = make('a', 'Official information'); link.href = preset.source; link.target = '_blank'; link.rel = 'noopener noreferrer'; row.append(link);
|
|
211
316
|
references.append(row); continue;
|
|
212
317
|
}
|
|
213
|
-
if (mode === 'access') continue;
|
|
214
318
|
const row = make('div', ''); row.className = 'row'; const detail = make('div', `${preset.label} · ${preset.note} `);
|
|
215
319
|
const link = make('a', 'Official information'); link.href = preset.source; link.target = '_blank'; link.rel = 'noopener noreferrer'; detail.append(link);
|
|
320
|
+
const install = installHelp[preset.name]?.[selectedOS];
|
|
321
|
+
if (install) {
|
|
322
|
+
detail.append(make('p', install.note));
|
|
323
|
+
const code = make('code', install.command); const pre = make('pre', ''); pre.append(code); detail.append(pre);
|
|
324
|
+
detail.append(button('Copy command', async () => { await navigator.clipboard.writeText(install.command); status(`${preset.label} ${osNames[selectedOS]} command copied.`); }));
|
|
325
|
+
}
|
|
216
326
|
row.append(detail);
|
|
217
|
-
if (state
|
|
218
|
-
else row.append(button('Add server (off)', 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.`); }));
|
|
327
|
+
if (mode !== 'access' && state?.upstreams.some((item) => item.name === preset.name)) row.append(make('span', 'Added'));
|
|
328
|
+
else if (mode !== 'access') row.append(button('Add server (off)', 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.`); }));
|
|
219
329
|
container.append(row);
|
|
220
330
|
}
|
|
221
331
|
}
|
|
@@ -249,16 +359,19 @@ function loadScope(scope) {
|
|
|
249
359
|
$('starts').value = localDate(m.engagement_window.start); $('ends').value = localDate(m.engagement_window.end);
|
|
250
360
|
$('profile').value = scope.profile; setLines('ips', m.authorized_targets.ip_ranges); setLines('domains', m.authorized_targets.domains);
|
|
251
361
|
setLines('urls', m.authorized_targets.urls); setLines('cloud', m.authorized_targets.cloud_accounts);
|
|
252
|
-
setLines('excluded-targets', m.excluded_targets); setLines('hosts', scope.allowedHosts);
|
|
362
|
+
setLines('excluded-targets', m.excluded_targets); setLines('hosts', scope.allowedHosts); setLines('local-paths', (m.authorized_local_paths || []).filter((path) => path !== '*'));
|
|
363
|
+
allLocalInput.checked = m.authorized_local_paths?.includes('*') || false;
|
|
253
364
|
setChecked(authorizedChoices, m.authorized_techniques); setChecked(excludedChoices, m.excluded_techniques);
|
|
254
365
|
for (const field of $('editor-card').querySelectorAll('input,select,textarea')) field.disabled = scope.status !== 'draft';
|
|
366
|
+
localPathInput.disabled = allLocalInput.checked || scope.status !== 'draft';
|
|
255
367
|
$('save-scope').disabled = scope.status !== 'draft';
|
|
256
368
|
$('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.';
|
|
257
369
|
reviewForm(); $('editor-card').scrollIntoView({ behavior: 'smooth' });
|
|
258
370
|
}
|
|
259
371
|
function clearEditor() {
|
|
260
372
|
editing = null; viewingScopeId = null; dirty = false;
|
|
261
|
-
for (const id of ['engagement', 'client', 'operator', 'starts', 'ends', 'ips', 'domains', 'urls', 'cloud', 'excluded-targets', 'hosts']) $(id).value = '';
|
|
373
|
+
for (const id of ['engagement', 'client', 'operator', 'starts', 'ends', 'ips', 'domains', 'urls', 'cloud', 'excluded-targets', 'hosts', 'local-paths']) $(id).value = '';
|
|
374
|
+
allLocalInput.checked = false;
|
|
262
375
|
$('profile').value = mode === 'access' ? 'connected' : 'isolated'; setChecked(authorizedChoices, []); setChecked(excludedChoices, []);
|
|
263
376
|
for (const field of $('editor-card').querySelectorAll('input,select,textarea')) field.disabled = false;
|
|
264
377
|
$('save-scope').disabled = false; $('editor-help').textContent = 'New scope. Enter the client, exact authorized targets, window and techniques, then Save.'; reviewForm();
|
|
@@ -294,7 +407,7 @@ $('clear-selection').onclick = () => api('/api/my-scope', 'POST', { scopeId: nul
|
|
|
294
407
|
$('choose-local').onclick = () => chooseServerPath('local');
|
|
295
408
|
$('choose-remote').onclick = () => chooseServerPath('remote');
|
|
296
409
|
$('copy-local-command').onclick = async () => { try {
|
|
297
|
-
await navigator.clipboard.writeText($('local-command').textContent.trim()); status(
|
|
410
|
+
await navigator.clipboard.writeText($('local-command').textContent.trim()); status(`Local setup commands copied. Paste them into ${selectedOS === 'windows' ? 'PowerShell' : 'a terminal'} on the tester’s computer.`);
|
|
298
411
|
} catch { status('Copy failed. Select the commands above and copy them manually.', true); } };
|
|
299
412
|
$('up-transport').onchange = updateTransportFields;
|
|
300
413
|
$('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); } };
|
|
@@ -338,8 +451,10 @@ async function initialize() {
|
|
|
338
451
|
bootstrapAdmins = new Set(session.bootstrapAdminEmails || []);
|
|
339
452
|
$('identity').textContent = email ? `${email} · ${role}` : 'Local gateway';
|
|
340
453
|
$('token-card').hidden = mode !== 'token';
|
|
341
|
-
for (const card of document.querySelectorAll('.admin-only')) card.hidden =
|
|
454
|
+
for (const card of document.querySelectorAll('.admin-only')) card.hidden = true;
|
|
342
455
|
configureServerUI();
|
|
456
|
+
$('token-card').querySelector('p').textContent = 'Paste the admin token printed when the gateway started. It stays in this browser tab only.';
|
|
457
|
+
updateInstallOS();
|
|
343
458
|
clearEditor();
|
|
344
459
|
if (mode === 'access' || token.value) await refresh();
|
|
345
460
|
reviewForm();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Upstream } from "./state.js";
|
|
2
|
+
export declare const commandToolName = "run_local_command";
|
|
3
|
+
export declare function commandTool(upstream: Upstream): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object";
|
|
8
|
+
properties: {
|
|
9
|
+
target: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
required: string[];
|
|
15
|
+
additionalProperties: boolean;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function commandTarget(upstream: Upstream, input: Record<string, unknown>): string;
|
|
19
|
+
export declare function runCommand(upstream: Upstream, target: string, signal?: AbortSignal): Promise<string>;
|
|
20
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/gateway/command.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAK3C,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAEnD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ;;;;;;;;;;;;;;EAQ7C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAmBxF;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC1G"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
const MAX_OUTPUT = 64 * 1024;
|
|
4
|
+
const MAX_DURATION = 120_000;
|
|
5
|
+
export const commandToolName = "run_local_command";
|
|
6
|
+
export function commandTool(upstream) {
|
|
7
|
+
return {
|
|
8
|
+
name: `${upstream.name}__${commandToolName}`,
|
|
9
|
+
description: `Run the administrator-configured ${upstream.name} command against one approved ${upstream.targetKind === "url" ? "HTTP URL" : "host or IP"}. Fixed arguments only; two-minute limit.`,
|
|
10
|
+
inputSchema: { type: "object", properties: {
|
|
11
|
+
target: { type: "string", description: upstream.targetKind === "url" ? "One authorized HTTP or HTTPS URL." : "One authorized hostname or IP address." },
|
|
12
|
+
}, required: ["target"], additionalProperties: false },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function commandTarget(upstream, input) {
|
|
16
|
+
if (Object.keys(input).length !== 1 || typeof input.target !== "string" || !input.target ||
|
|
17
|
+
input.target !== input.target.trim() || input.target.length > 2048 || /[\x00-\x20\x7f]/.test(input.target))
|
|
18
|
+
throw new Error("Enter one target; extra options and control characters are not accepted.");
|
|
19
|
+
const target = input.target;
|
|
20
|
+
if (upstream.targetKind === "host") {
|
|
21
|
+
if (isIP(target))
|
|
22
|
+
return target;
|
|
23
|
+
if (!/^(?=.{1,253}$)(?:[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?\.)+[a-z](?:[a-z\d-]{0,61}[a-z\d])?$/i.test(target))
|
|
24
|
+
throw new Error("Enter one hostname or IP without a URL, range, or command options.");
|
|
25
|
+
return target;
|
|
26
|
+
}
|
|
27
|
+
if (upstream.targetKind === "url") {
|
|
28
|
+
let url;
|
|
29
|
+
try {
|
|
30
|
+
url = new URL(target);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new Error("Enter one HTTP or HTTPS URL.");
|
|
34
|
+
}
|
|
35
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || !url.hostname)
|
|
36
|
+
throw new Error("URL must be HTTP or HTTPS without credentials, a query, or a fragment.");
|
|
37
|
+
return url.href;
|
|
38
|
+
}
|
|
39
|
+
throw new Error("Command target type is missing.");
|
|
40
|
+
}
|
|
41
|
+
export async function runCommand(upstream, target, signal) {
|
|
42
|
+
if (upstream.transport !== "cli" || !upstream.command || upstream.args.filter((arg) => arg === "{target}").length !== 1)
|
|
43
|
+
throw new Error("Invalid command configuration.");
|
|
44
|
+
const args = upstream.args.map((arg) => arg === "{target}" ? target : arg);
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const child = spawn(upstream.command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"], signal });
|
|
47
|
+
let output = "";
|
|
48
|
+
let size = 0;
|
|
49
|
+
let finished = false;
|
|
50
|
+
const fail = (message) => {
|
|
51
|
+
if (finished)
|
|
52
|
+
return;
|
|
53
|
+
finished = true;
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
child.kill();
|
|
56
|
+
reject(new Error(message));
|
|
57
|
+
};
|
|
58
|
+
const timer = setTimeout(() => fail("Command exceeded the two-minute limit."), MAX_DURATION);
|
|
59
|
+
const collect = (chunk) => {
|
|
60
|
+
size += chunk.length;
|
|
61
|
+
if (size > MAX_OUTPUT)
|
|
62
|
+
return fail("Command output exceeded 64 KiB; narrow the request.");
|
|
63
|
+
output += chunk.toString("utf8");
|
|
64
|
+
};
|
|
65
|
+
child.stdout.on("data", collect);
|
|
66
|
+
child.stderr.on("data", collect);
|
|
67
|
+
child.on("error", () => fail(signal?.aborted ? "Command stopped because its server was turned off." : "Command could not start. Check the executable name and PATH on the local gateway computer."));
|
|
68
|
+
child.on("close", (code) => {
|
|
69
|
+
if (finished)
|
|
70
|
+
return;
|
|
71
|
+
finished = true;
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
if (code !== 0)
|
|
74
|
+
reject(new Error(`Command exited with code ${code}. ${output.slice(0, 2000)}`));
|
|
75
|
+
else
|
|
76
|
+
resolve(output || "Command completed with no output.");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../../src/gateway/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,8 +9,17 @@ export declare class GatewayRuntime {
|
|
|
9
9
|
private clients;
|
|
10
10
|
private pending;
|
|
11
11
|
private proxies;
|
|
12
|
+
private statuses;
|
|
13
|
+
private runningCommands;
|
|
12
14
|
constructor(store: GatewayStore, hosted: boolean, requiredAuditSink?: ((entry: AuditEntry) => Promise<void>) | undefined, isActorAllowed?: ((email?: string) => boolean) | undefined);
|
|
13
15
|
sync(): Promise<void>;
|
|
16
|
+
upstreamStatuses(): Record<string, {
|
|
17
|
+
state: "off" | "not_checked" | "connected" | "error" | "configured";
|
|
18
|
+
toolCount?: number;
|
|
19
|
+
checkedAt?: string;
|
|
20
|
+
detail?: string;
|
|
21
|
+
}>;
|
|
22
|
+
checkUpstream(id: string): Promise<void>;
|
|
14
23
|
private scopeProxy;
|
|
15
24
|
private clientFor;
|
|
16
25
|
private available;
|
|
@@ -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;AAKnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAiE,MAAM,YAAY,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;AAWzG,qBAAa,cAAc;IAOb,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;IANvL,OAAO,CAAC,OAAO,CAA4D;IAC3E,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,OAAO,CAA4C;IAC3D,OAAO,CAAC,QAAQ,CAA+G;IAC/H,OAAO,CAAC,eAAe,CAA8E;gBAEhF,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;IAqB3B,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,KAAK,GAAG,aAAa,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAK9J,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB9C,OAAO,CAAC,UAAU;YAYJ,SAAS;IA2BvB,OAAO,CAAC,SAAS;IAOjB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IA6I9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
|