@integsec/agentic-pentest-proxy 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/cloudflare/src/worker.mjs +2 -2
- package/cloudflare/wrangler.jsonc +1 -1
- package/dist/bin/integsec-agentic-pentest-bridge.js +2 -2
- package/dist/bin/integsec-agentic-pentest-scanners-mcp.js +21 -7
- package/dist/bin/integsec-agentic-pentest-scanners-mcp.js.map +1 -1
- package/dist/src/config.js +1 -1
- package/dist/src/extractor.d.ts.map +1 -1
- package/dist/src/extractor.js +2 -0
- package/dist/src/extractor.js.map +1 -1
- package/dist/src/gateway/admin.js +126 -18
- 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 +81 -0
- package/dist/src/gateway/command.js.map +1 -0
- package/dist/src/gateway/docs-home.html +1 -1
- package/dist/src/gateway/docs.d.ts.map +1 -1
- package/dist/src/gateway/docs.js +1 -0
- package/dist/src/gateway/docs.js.map +1 -1
- package/dist/src/gateway/jobs.d.ts +26 -0
- package/dist/src/gateway/jobs.d.ts.map +1 -0
- package/dist/src/gateway/jobs.js +97 -0
- package/dist/src/gateway/jobs.js.map +1 -0
- package/dist/src/gateway/runtime.d.ts +14 -0
- package/dist/src/gateway/runtime.d.ts.map +1 -1
- package/dist/src/gateway/runtime.js +280 -55
- 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 +69 -2
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/state.d.ts +15 -1
- package/dist/src/gateway/state.d.ts.map +1 -1
- package/dist/src/gateway/state.js +20 -6
- package/dist/src/gateway/state.js.map +1 -1
- package/dist/src/gateway/workbench-client.js +180 -0
- package/dist/src/gateway/workbench.css +3 -0
- package/dist/src/gateway/workbench.d.ts +51 -0
- package/dist/src/gateway/workbench.d.ts.map +1 -0
- package/dist/src/gateway/workbench.html +41 -0
- package/dist/src/gateway/workbench.js +157 -0
- package/dist/src/gateway/workbench.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/proxy.d.ts +3 -1
- package/dist/src/proxy.d.ts.map +1 -1
- package/dist/src/proxy.js +20 -2
- package/dist/src/proxy.js.map +1 -1
- package/dist/src/scanners/jobs.d.ts +25 -0
- package/dist/src/scanners/jobs.d.ts.map +1 -0
- package/dist/src/scanners/jobs.js +65 -0
- package/dist/src/scanners/jobs.js.map +1 -0
- package/dist/src/scanners/runner.d.ts +2 -1
- package/dist/src/scanners/runner.d.ts.map +1 -1
- package/dist/src/scanners/runner.js +10 -7
- package/dist/src/scanners/runner.js.map +1 -1
- package/dist/src/technique-checker.js +1 -1
- package/dist/src/technique-checker.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/validator.d.ts.map +1 -1
- package/dist/src/validator.js +3 -2
- package/dist/src/validator.js.map +1 -1
- package/docs/managed-gateway.md +11 -3
- package/docs/workbench.md +28 -0
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const $ = (id) => document.getElementById(id);
|
|
2
|
+
let mode = 'token';
|
|
3
|
+
let token = sessionStorage.getItem('gateway-token') || '';
|
|
4
|
+
let data = { interceptEnabled: false, timeoutSeconds: 45, events: [] };
|
|
5
|
+
let jobsData = [];
|
|
6
|
+
let selectedJobId = null;
|
|
7
|
+
let selectedJobDetail = null;
|
|
8
|
+
let selectedId = null;
|
|
9
|
+
let editorLoadedFor = null;
|
|
10
|
+
let refreshing = false;
|
|
11
|
+
let acting = false;
|
|
12
|
+
|
|
13
|
+
function notice(message, error = false) { $('status').textContent = message; $('status').classList.toggle('error', error); }
|
|
14
|
+
async function api(path, method = 'GET', payload) {
|
|
15
|
+
const response = await fetch(path, { method, cache: 'no-store', credentials: 'same-origin', headers: {
|
|
16
|
+
...(mode === 'token' ? { Authorization: `Bearer ${token}` } : {}),
|
|
17
|
+
...(payload === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
18
|
+
}, body: payload === undefined ? undefined : JSON.stringify(payload) });
|
|
19
|
+
const result = await response.json().catch(() => ({}));
|
|
20
|
+
if (!response.ok) throw new Error(result.error || `HTTP ${response.status}`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
const pretty = (value) => value === undefined ? 'Waiting for a response…' : JSON.stringify(value, null, 2);
|
|
24
|
+
function formatTime(value) { return new Date(value).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); }
|
|
25
|
+
function statusGroup(value) { return value === 'dropped' ? 'blocked' : value; }
|
|
26
|
+
function visibleEvents() {
|
|
27
|
+
const search = $('search').value.trim().toLowerCase();
|
|
28
|
+
const filter = $('filter-status').value;
|
|
29
|
+
return data.events.filter((event) => {
|
|
30
|
+
if (filter !== 'all' && statusGroup(event.status) !== filter) return false;
|
|
31
|
+
return !search || [event.tool, event.server, event.target, event.engagement].some((part) => String(part || '').toLowerCase().includes(search));
|
|
32
|
+
}).sort((a, b) => Number(b.status === 'pending') - Number(a.status === 'pending') || Date.parse(b.startedAt) - Date.parse(a.startedAt));
|
|
33
|
+
}
|
|
34
|
+
function renderFeed() {
|
|
35
|
+
const feed = $('feed'); feed.replaceChildren();
|
|
36
|
+
const events = visibleEvents();
|
|
37
|
+
for (const event of events) {
|
|
38
|
+
const item = document.createElement('button'); item.type = 'button'; item.className = `feed-item ${event.status}${event.id === selectedId ? ' selected' : ''}`;
|
|
39
|
+
item.setAttribute('role', 'listitem'); item.setAttribute('aria-label', `${event.status}: ${event.tool}`);
|
|
40
|
+
const stripe = document.createElement('span'); stripe.className = 'stripe';
|
|
41
|
+
const content = document.createElement('span'); const title = document.createElement('strong'); title.textContent = event.tool;
|
|
42
|
+
const subtitle = document.createElement('small'); subtitle.textContent = `${event.server} · ${event.target || 'No target'} · ${event.status}${event.durationMs === undefined ? '' : ` · ${event.durationMs} ms`}`;
|
|
43
|
+
content.append(title, subtitle);
|
|
44
|
+
const time = document.createElement('time'); time.dateTime = event.startedAt; time.textContent = formatTime(event.startedAt);
|
|
45
|
+
item.append(stripe, content, time);
|
|
46
|
+
item.onclick = () => { selectedId = event.id; editorLoadedFor = null; renderFeed(); renderDetail(); };
|
|
47
|
+
feed.append(item);
|
|
48
|
+
}
|
|
49
|
+
if (!events.length) { const empty = document.createElement('p'); empty.className = 'empty'; empty.textContent = data.events.length ? 'No calls match these filters.' : 'No calls yet. Use an MCP tool through this gateway to see it here.'; feed.append(empty); }
|
|
50
|
+
}
|
|
51
|
+
function renderDetail() {
|
|
52
|
+
const event = data.events.find((item) => item.id === selectedId);
|
|
53
|
+
$('detail-empty').hidden = Boolean(event); $('detail-body').hidden = !event;
|
|
54
|
+
if (!event) { $('detail-title').textContent = 'Select a call'; $('detail-state').textContent = '—'; $('detail-state').className = 'badge'; return; }
|
|
55
|
+
$('detail-title').textContent = event.tool;
|
|
56
|
+
$('detail-state').textContent = event.status; $('detail-state').className = `badge ${event.status}`;
|
|
57
|
+
$('detail-engagement').textContent = event.engagement || 'No active engagement';
|
|
58
|
+
$('detail-server').textContent = `${event.server} · ${event.transport}`;
|
|
59
|
+
$('detail-target').textContent = event.target || 'No target extracted';
|
|
60
|
+
$('detail-time').textContent = `${formatTime(event.startedAt)}${event.durationMs === undefined ? ' · in progress' : ` · ${event.durationMs} ms`}`;
|
|
61
|
+
$('detail-note').hidden = !event.note; $('detail-note').textContent = event.note || '';
|
|
62
|
+
$('original-request').textContent = pretty(event.originalRequest);
|
|
63
|
+
$('edited-section').hidden = !event.edited; $('forwarded-request').textContent = pretty(event.request);
|
|
64
|
+
$('pending-actions').hidden = event.status !== 'pending';
|
|
65
|
+
if (event.status === 'pending' && editorLoadedFor !== event.id) {
|
|
66
|
+
$('request-editor').value = pretty(event.pendingRequest); editorLoadedFor = event.id;
|
|
67
|
+
}
|
|
68
|
+
$('response').textContent = pretty(event.response);
|
|
69
|
+
for (const id of ['forward', 'forward-edited', 'drop']) $(id).disabled = acting;
|
|
70
|
+
}
|
|
71
|
+
function renderJobs() {
|
|
72
|
+
$('job-count').textContent = `${jobsData.length} job${jobsData.length === 1 ? '' : 's'}`;
|
|
73
|
+
const container = $('jobs'); container.replaceChildren();
|
|
74
|
+
if (!jobsData.length) { const empty = document.createElement('p'); empty.className = 'empty'; empty.textContent = 'No jobs for this engagement.'; container.append(empty); return; }
|
|
75
|
+
for (const job of jobsData) {
|
|
76
|
+
const row = document.createElement('div'); row.className = `job-item ${job.status}`;
|
|
77
|
+
const info = document.createElement('div');
|
|
78
|
+
const title = document.createElement('strong'); title.textContent = `${job.tool} · ${job.status}`;
|
|
79
|
+
const detail = document.createElement('small'); detail.textContent = `${formatTime(job.startedAt)} · ${job.id}`;
|
|
80
|
+
info.append(title, detail);
|
|
81
|
+
const actions = document.createElement('div'); actions.className = 'job-actions';
|
|
82
|
+
const view = document.createElement('button'); view.className = 'small secondary'; view.textContent = selectedJobId === job.id ? 'Hide' : 'View';
|
|
83
|
+
view.onclick = async () => {
|
|
84
|
+
if (selectedJobId === job.id) { selectedJobId = null; selectedJobDetail = null; renderJobs(); return; }
|
|
85
|
+
selectedJobId = job.id;
|
|
86
|
+
try { selectedJobDetail = await api(`/api/workbench/jobs/${job.id}`); renderJobs(); }
|
|
87
|
+
catch (error) { notice(error.message, true); }
|
|
88
|
+
};
|
|
89
|
+
actions.append(view);
|
|
90
|
+
if (job.status === 'running') {
|
|
91
|
+
const cancel = document.createElement('button'); cancel.className = 'small danger'; cancel.textContent = 'Cancel';
|
|
92
|
+
cancel.onclick = async () => {
|
|
93
|
+
if (!confirm(`Cancel job ${job.id}?`)) return;
|
|
94
|
+
try { await api(`/api/workbench/jobs/${job.id}/cancel`, 'POST'); await refresh(); notice('Job canceled.'); }
|
|
95
|
+
catch (error) { notice(error.message, true); }
|
|
96
|
+
};
|
|
97
|
+
actions.append(cancel);
|
|
98
|
+
}
|
|
99
|
+
row.append(info, actions);
|
|
100
|
+
if (selectedJobId === job.id && selectedJobDetail) {
|
|
101
|
+
const result = document.createElement('pre'); result.className = 'job-result'; result.textContent = pretty(selectedJobDetail);
|
|
102
|
+
row.append(result);
|
|
103
|
+
}
|
|
104
|
+
container.append(row);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function render() {
|
|
108
|
+
$('scope-label').textContent = data.activeEngagement ? `Active engagement: ${data.activeEngagement}` : 'No active engagement. Select one in the dashboard before using tools.';
|
|
109
|
+
$('intercept').checked = data.interceptEnabled;
|
|
110
|
+
$('timeout').textContent = String(data.timeoutSeconds);
|
|
111
|
+
$('intercept-help').textContent = data.interceptEnabled ? 'On · your tool calls pause for review' : 'Off · tool calls run normally';
|
|
112
|
+
$('total').textContent = String(data.events.length);
|
|
113
|
+
$('allowed').textContent = String(data.events.filter((event) => event.status === 'allowed').length);
|
|
114
|
+
$('blocked').textContent = String(data.events.filter((event) => ['blocked', 'dropped'].includes(event.status)).length);
|
|
115
|
+
$('pending').textContent = String(data.events.filter((event) => event.status === 'pending').length);
|
|
116
|
+
if (!selectedId && data.events.length) selectedId = data.events[0].id;
|
|
117
|
+
renderJobs(); renderFeed(); renderDetail();
|
|
118
|
+
}
|
|
119
|
+
async function refresh() {
|
|
120
|
+
if (refreshing) return;
|
|
121
|
+
refreshing = true;
|
|
122
|
+
try {
|
|
123
|
+
const [calls, jobs, detail] = await Promise.all([api('/api/workbench'), api('/api/workbench/jobs'),
|
|
124
|
+
selectedJobId ? api(`/api/workbench/jobs/${selectedJobId}`).catch(() => null) : Promise.resolve(null)]);
|
|
125
|
+
data = calls; jobsData = jobs.jobs; selectedJobDetail = detail; render();
|
|
126
|
+
} catch (error) { if (mode === 'token' && /admin token required/i.test(error.message)) $('token-card').hidden = false; throw error; }
|
|
127
|
+
finally { refreshing = false; }
|
|
128
|
+
}
|
|
129
|
+
async function takeAction(action, edited = false) {
|
|
130
|
+
const id = selectedId; const event = data.events.find((item) => item.id === id);
|
|
131
|
+
if (!event || event.status !== 'pending' || acting) return;
|
|
132
|
+
let payload;
|
|
133
|
+
if (action === 'forward') {
|
|
134
|
+
payload = {};
|
|
135
|
+
if (edited) {
|
|
136
|
+
let args;
|
|
137
|
+
try { args = JSON.parse($('request-editor').value); } catch { notice('Arguments must be valid JSON.', true); return; }
|
|
138
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) { notice('Arguments must be a JSON object.', true); return; }
|
|
139
|
+
payload.arguments = args;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
acting = true; renderDetail();
|
|
143
|
+
try {
|
|
144
|
+
await api(`/api/workbench/pending/${id}/${action}`, 'POST', payload);
|
|
145
|
+
notice(action === 'drop' ? 'Call dropped. The upstream did not run.' : 'Call forwarded for scope validation and execution.');
|
|
146
|
+
editorLoadedFor = null; await refresh();
|
|
147
|
+
} catch (error) { notice(error.message, true); }
|
|
148
|
+
finally { acting = false; renderDetail(); }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
$('connect').onclick = async () => {
|
|
152
|
+
token = $('token').value.trim();
|
|
153
|
+
if (!token) { notice('Enter the local administrator token.', true); return; }
|
|
154
|
+
try { await refresh(); sessionStorage.setItem('gateway-token', token); $('token-card').hidden = true; notice('Connected to the local gateway.'); }
|
|
155
|
+
catch (error) { notice(error.message, true); }
|
|
156
|
+
};
|
|
157
|
+
$('intercept').onchange = async () => {
|
|
158
|
+
const enabled = $('intercept').checked;
|
|
159
|
+
if (!enabled && data.events.some((event) => event.status === 'pending') && !confirm('Turning intercept off drops all calls waiting for your review. Continue?')) { $('intercept').checked = true; return; }
|
|
160
|
+
try { data = await api('/api/workbench/intercept', 'POST', { enabled }); render(); notice(enabled ? 'Intercept is on for your calls.' : 'Intercept is off. Waiting calls were dropped.'); }
|
|
161
|
+
catch (error) { $('intercept').checked = !enabled; notice(error.message, true); }
|
|
162
|
+
};
|
|
163
|
+
$('refresh').onclick = () => refresh().catch((error) => notice(error.message, true));
|
|
164
|
+
$('search').oninput = renderFeed;
|
|
165
|
+
$('filter-status').onchange = renderFeed;
|
|
166
|
+
$('forward').onclick = () => takeAction('forward');
|
|
167
|
+
$('forward-edited').onclick = () => takeAction('forward', true);
|
|
168
|
+
$('drop').onclick = () => takeAction('drop');
|
|
169
|
+
$('copy-request').onclick = () => { const event = data.events.find((item) => item.id === selectedId); if (event) navigator.clipboard.writeText(pretty(event.request)).then(() => notice('Request preview copied.')).catch(() => notice('Copy failed.', true)); };
|
|
170
|
+
$('copy-response').onclick = () => { const event = data.events.find((item) => item.id === selectedId); if (event?.response) navigator.clipboard.writeText(pretty(event.response)).then(() => notice('Response preview copied.')).catch(() => notice('Copy failed.', true)); };
|
|
171
|
+
|
|
172
|
+
async function initialize() {
|
|
173
|
+
const response = await fetch('/api/session', { credentials: 'same-origin', cache: 'no-store' });
|
|
174
|
+
const session = await response.json(); if (!response.ok) throw new Error(session.error || `HTTP ${response.status}`);
|
|
175
|
+
mode = session.mode; $('identity').textContent = session.email || 'Local gateway';
|
|
176
|
+
$('token-card').hidden = mode !== 'token' || Boolean(token);
|
|
177
|
+
if (mode === 'access' || token) await refresh();
|
|
178
|
+
setInterval(() => { if (mode === 'access' || token) refresh().catch((error) => notice(error.message, true)); }, 1500);
|
|
179
|
+
}
|
|
180
|
+
initialize().catch((error) => notice(error.message, true));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
:root{font:14px system-ui,sans-serif;color:#e9f3f4;background:#0a131b}*{box-sizing:border-box}body{margin:0}button,input,textarea,select{font:inherit}button{border:0;border-radius:8px;background:#28c3a8;color:#06231e;padding:10px 14px;font-weight:700;cursor:pointer}button:hover{filter:brightness(1.1)}button:disabled{opacity:.5;cursor:not-allowed}button.secondary{background:#294456;color:#dcebf0}button.danger{background:#773640;color:#fff}button.small{padding:5px 9px;font-size:12px}.topbar{min-height:72px;background:#101f29;border-bottom:1px solid #244050;padding:12px max(20px,3vw);display:flex;align-items:center;gap:24px;justify-content:space-between}.brand{display:flex;align-items:center;gap:10px}.brand strong,.brand small{display:block}.brand strong{font-size:16px}.brand small{color:#9eb7c2;font-size:11px}.mark{display:grid;place-items:center;width:34px;height:34px;border-radius:9px;background:#27c6aa;color:#07251e;font-size:12px;font-weight:900}.topbar nav{display:flex;gap:18px}.topbar a{color:#9feadd;text-decoration:none}.topbar a:hover{text-decoration:underline}.identity{color:#a4bdc7;font-size:12px}main{max-width:1600px;margin:auto;padding:24px max(20px,3vw) 32px}.intro{display:flex;justify-content:space-between;gap:24px;align-items:center;margin-bottom:18px}.intro h1{font-size:29px;line-height:1.15;margin:3px 0 7px}.intro p{color:#a9bfca;margin:0;line-height:1.45}.intro>div:first-child{max-width:720px}.eyebrow{color:#49d8bd!important;letter-spacing:.13em;font-size:10px;font-weight:800;margin:0}.switchbox{background:#142731;border:1px solid #2b4d5b;border-radius:12px;padding:14px 17px;min-width:280px}.switchbox small{display:block;color:#a6bec8;margin:5px 0 8px}.switchbox p{font-size:12px}.toggle{display:flex;align-items:center;gap:10px;font-weight:800;font-size:15px;cursor:pointer}.toggle input{appearance:none;width:37px;height:22px;margin:0;border-radius:20px;background:#395361;position:relative;cursor:pointer}.toggle input:before{content:"";display:block;position:absolute;top:3px;left:3px;width:16px;height:16px;background:#ecf5f6;border-radius:50%;transition:transform .15s}.toggle input:checked{background:#23bda3}.toggle input:checked:before{transform:translateX(15px)}.token-card{border:1px solid #42647a;background:#172b37;border-radius:10px;padding:16px;margin:14px 0}.token-card label{display:block;margin-bottom:7px;font-weight:700}.token-card div{display:flex;gap:8px}.token-card input{flex:1}.token-card p{color:#a5bdc8;font-size:12px}.token-card a{color:#92e8d8}input:not([type=checkbox]),textarea,select{background:#0c1b25;border:1px solid #3e5c6b;border-radius:8px;color:#eef6f7;padding:9px 10px;min-width:0}input:focus,textarea:focus,select:focus,button:focus-visible{outline:2px solid #55d8c0;outline-offset:2px}.status{color:#8de9d7;min-height:18px;margin:7px 0 14px;font-size:12px}.status.error{color:#ff9f9f}.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}.metrics div{background:#14242f;border:1px solid #2a4756;border-radius:10px;padding:14px 17px}.metrics strong{display:block;font-size:24px;line-height:1.1}.metrics span{display:block;color:#a4bbc6;font-size:11px;margin-top:4px}.metrics div:nth-child(2) strong{color:#59dca0}.metrics div:nth-child(3) strong{color:#ff9494}.metrics div:nth-child(4) strong{color:#ffd27a}.workspace{display:grid;grid-template-columns:minmax(360px,.95fr) minmax(420px,1.25fr);gap:15px;min-height:580px}.feed-panel,.detail-panel{background:#13232e;border:1px solid #2b4858;border-radius:12px;overflow:hidden}.panel-heading{display:flex;justify-content:space-between;align-items:center;gap:15px;padding:16px 18px;border-bottom:1px solid #294655}.panel-heading h2{margin:2px 0 0;font-size:17px}.filters{display:grid;grid-template-columns:1fr 120px;gap:9px;padding:13px 16px;border-bottom:1px solid #294655}.filters label{display:block;color:#aebfc8;font-size:11px;font-weight:700}.filters input,.filters select{display:block;width:100%;margin-top:4px}.feed{max-height:690px;overflow:auto}.feed-item{display:grid;grid-template-columns:7px minmax(0,1fr) auto;align-items:center;gap:12px;width:100%;text-align:left;border-radius:0;border-bottom:1px solid #25414e;background:transparent;color:#eaf3f5;padding:12px 15px;font-weight:400}.feed-item:hover,.feed-item.selected{background:#1d3742;filter:none}.feed-item .stripe{height:36px;border-radius:4px;background:#69808a}.feed-item.allowed .stripe{background:#3dd295}.feed-item.blocked .stripe,.feed-item.dropped .stripe{background:#ef777a}.feed-item.failed .stripe{background:#f5ad5a}.feed-item.pending .stripe{background:#ffd27a}.feed-item strong{display:block;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.feed-item small{display:block;color:#a6bbc5;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.feed-item time{color:#9eafba;font-size:11px;align-self:start}.empty,.empty-detail{color:#a3b8c1;padding:22px;line-height:1.5}.badge{border-radius:20px;background:#334754;color:#d7e5e9;padding:5px 10px;font-size:11px;font-weight:800;text-transform:uppercase}.badge.allowed{background:#174e40;color:#8eebbd}.badge.blocked,.badge.dropped{background:#592c34;color:#ffb2b4}.badge.failed{background:#634629;color:#ffd098}.badge.pending{background:#554823;color:#ffe09a}#detail-body{padding:15px 18px 22px}.facts{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin:0 0 15px}.facts div{background:#0f202a;padding:9px;border-radius:7px;min-width:0}.facts dt{color:#9bb1bb;font-size:10px;text-transform:uppercase;letter-spacing:.08em}.facts dd{margin:4px 0 0;word-break:break-word;font-size:12px}.section-heading{display:flex;justify-content:space-between;align-items:center}.section-heading h3,#edited-section h3,.pending-actions h3{font-size:13px;margin:9px 0}.detail-panel pre{background:#0b1923;border:1px solid #2d4958;border-radius:8px;margin:5px 0 16px;padding:12px;color:#afe9db;white-space:pre-wrap;word-break:break-word;max-height:300px;overflow:auto;font:12px/1.55 ui-monospace,Consolas,monospace}.notice{background:#302b21;border-left:3px solid #e6bd69;padding:10px;margin:0 0 12px;color:#f2dcae}.pending-actions{border:1px solid #846a37;border-radius:9px;padding:12px;margin:10px 0 17px;background:#2a281f}.pending-actions p{color:#ddd3b9;font-size:12px;line-height:1.4;margin:2px 0 9px}.pending-actions textarea{display:block;width:100%;min-height:155px;font:12px/1.5 ui-monospace,Consolas,monospace}.action-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:9px}.footer-note{font-size:11px;color:#8da5af;margin:18px 2px}[hidden]{display:none!important}@media(max-width:900px){.workspace{grid-template-columns:1fr}.feed{max-height:350px}.intro{display:block}.switchbox{margin-top:14px}.topbar{flex-wrap:wrap}.topbar nav{order:3;width:100%}}@media(max-width:530px){.metrics{grid-template-columns:repeat(2,1fr)}.facts{grid-template-columns:1fr}.filters{grid-template-columns:1fr 105px}.intro h1{font-size:24px}}
|
|
2
|
+
.jobs-panel{background:#13232e;border:1px solid #2b4858;border-radius:12px;margin-bottom:16px;overflow:hidden}.jobs-help{color:#a9bfca;margin:0;padding:13px 18px;font-size:12px;line-height:1.5}.jobs-help code{color:#a9f1df}.jobs{padding:0 16px 12px}.job-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center;border-top:1px solid #2b4858;padding:12px 2px}.job-item strong,.job-item small{display:block}.job-item strong{font-size:13px}.job-item small{color:#a4bbc6;margin-top:4px}.job-actions{display:flex;gap:7px}.job-result{grid-column:1/-1;margin:0!important;max-height:250px!important}.job-item.running strong{color:#ffe09a}.job-item.completed strong{color:#8eebbd}.job-item.failed strong,.job-item.canceled strong{color:#ffb2b4}@media(max-width:530px){.job-item{grid-template-columns:1fr}.job-actions{justify-content:flex-start}}
|
|
3
|
+
.intro .scope-label{margin-top:10px;color:#79e4cb;font-size:12px;font-weight:700}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type WorkbenchStatus = "running" | "pending" | "forwarding" | "allowed" | "blocked" | "failed" | "dropped";
|
|
2
|
+
export interface WorkbenchEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
user: string;
|
|
5
|
+
tool: string;
|
|
6
|
+
server: string;
|
|
7
|
+
transport: string;
|
|
8
|
+
engagement: string | null;
|
|
9
|
+
target: string | null;
|
|
10
|
+
startedAt: string;
|
|
11
|
+
finishedAt?: string;
|
|
12
|
+
durationMs?: number;
|
|
13
|
+
status: WorkbenchStatus;
|
|
14
|
+
edited: boolean;
|
|
15
|
+
originalRequest: unknown;
|
|
16
|
+
request: unknown;
|
|
17
|
+
response?: unknown;
|
|
18
|
+
note?: string;
|
|
19
|
+
}
|
|
20
|
+
type Arguments = Record<string, unknown>;
|
|
21
|
+
type InterceptResult = {
|
|
22
|
+
action: "forward";
|
|
23
|
+
args: Arguments;
|
|
24
|
+
} | {
|
|
25
|
+
action: "drop";
|
|
26
|
+
reason: string;
|
|
27
|
+
};
|
|
28
|
+
export declare const INTERCEPT_TIMEOUT_MS = 45000;
|
|
29
|
+
export declare function workbenchPreview(value: unknown): unknown;
|
|
30
|
+
export declare class Workbench {
|
|
31
|
+
private entries;
|
|
32
|
+
private enabled;
|
|
33
|
+
private pending;
|
|
34
|
+
begin(user: string, tool: string, server: string, transport: string, engagement: string | null, args: Arguments): WorkbenchEntry;
|
|
35
|
+
snapshot(user: string): {
|
|
36
|
+
interceptEnabled: boolean;
|
|
37
|
+
timeoutSeconds: number;
|
|
38
|
+
events: Array<WorkbenchEntry & {
|
|
39
|
+
pendingRequest?: Arguments;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
setIntercept(user: string, enabled: boolean): void;
|
|
43
|
+
dropServer(server: string): void;
|
|
44
|
+
intercept(user: string, entry: WorkbenchEntry, args: Arguments, signal?: AbortSignal): Promise<InterceptResult>;
|
|
45
|
+
decide(user: string, id: string, action: "forward" | "drop", args?: Arguments): WorkbenchEntry;
|
|
46
|
+
private settle;
|
|
47
|
+
finish(entry: WorkbenchEntry, result: unknown): void;
|
|
48
|
+
close(): void;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=workbench.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workbench.d.ts","sourceRoot":"","sources":["../../../src/gateway/workbench.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAClH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACzC,KAAK,eAAe,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAInG,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAwB3C,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKxD;AAsBD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAkC;IAEjD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,SAAS,GAAG,cAAc;IAehI,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,gBAAgB,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,cAAc,GAAG;YAAE,cAAc,CAAC,EAAE,SAAS,CAAA;SAAE,CAAC,CAAA;KAAE;IAU7I,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAQlD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK1B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IAgBrH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,cAAc;IAc9F,OAAO,CAAC,MAAM;IAWd,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAWpD,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<meta charset="utf-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5
|
+
<title>MCP workbench · Agentic Pentest Gateway</title>
|
|
6
|
+
<link rel="stylesheet" href="/workbench.css">
|
|
7
|
+
<header class="topbar">
|
|
8
|
+
<div class="brand"><span class="mark">IS</span><div><strong>MCP workbench</strong><small>Agentic Pentest Gateway</small></div></div>
|
|
9
|
+
<nav aria-label="Site"><a href="/admin">Dashboard</a><a href="/docs">Documentation</a><a href="https://github.com/IntegSec/agentic-pentest-proxy" rel="noopener noreferrer">GitHub ↗</a></nav>
|
|
10
|
+
<span id="identity" class="identity">Connecting…</span>
|
|
11
|
+
</header>
|
|
12
|
+
<main>
|
|
13
|
+
<section class="intro">
|
|
14
|
+
<div><p class="eyebrow">LIVE TOOL TRAFFIC</p><h1>Inspect scoped MCP tool traffic.</h1><p>Watch requests, scope decisions, and responses. Turn on intercept to review your next upstream calls before they reach a tool.</p><p id="scope-label" class="scope-label">Checking your engagement…</p></div>
|
|
15
|
+
<div class="switchbox"><label class="toggle"><input id="intercept" type="checkbox"><span>Intercept my calls</span></label><small id="intercept-help">Off · tool calls run normally</small><p>When on, your calls wait up to <span id="timeout">45</span> seconds. Unreviewed calls are dropped.</p></div>
|
|
16
|
+
</section>
|
|
17
|
+
<section id="token-card" class="token-card" hidden><label for="token">Local administrator token</label><div><input id="token" type="password" autocomplete="off" placeholder="Paste token from gateway start"><button id="connect">Connect</button></div><p>The token stays in this browser tab. You can also connect on the <a href="/admin">dashboard</a> first.</p></section>
|
|
18
|
+
<p id="status" class="status" role="status" aria-live="polite"></p>
|
|
19
|
+
<section class="metrics" aria-label="Recent call totals"><div><strong id="total">0</strong><span>Total calls</span></div><div><strong id="allowed">0</strong><span>Allowed</span></div><div><strong id="blocked">0</strong><span>Blocked or dropped</span></div><div><strong id="pending">0</strong><span>Waiting for review</span></div></section>
|
|
20
|
+
<section class="jobs-panel" aria-label="Background jobs"><div class="panel-heading"><div><p class="eyebrow">LONG CALLS</p><h2>Background jobs</h2></div><span id="job-count" class="badge">0 jobs</span></div><p class="jobs-help">Use <code>gateway__start_long_call</code> in your MCP client for a long tool call. It returns a job ID immediately. Results and cancellation are available here or through the gateway job tools.</p><div id="jobs" class="jobs"><p class="empty">No jobs for this engagement.</p></div></section>
|
|
21
|
+
<section class="workspace">
|
|
22
|
+
<div class="feed-panel"><div class="panel-heading"><div><p class="eyebrow">TRAFFIC</p><h2>Live feed</h2></div><button id="refresh" class="secondary">Refresh</button></div>
|
|
23
|
+
<div class="filters"><label>Search<input id="search" type="search" placeholder="Tool, server, or target"></label><label>Status<select id="filter-status"><option value="all">All</option><option value="pending">Waiting</option><option value="allowed">Allowed</option><option value="blocked">Blocked</option><option value="failed">Failed</option></select></label></div>
|
|
24
|
+
<div id="feed" class="feed" role="list" aria-label="MCP tool calls"><p class="empty">No calls yet. Use an MCP tool through this gateway to see it here.</p></div>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="detail-panel"><div class="panel-heading"><div><p class="eyebrow">INSPECTOR</p><h2 id="detail-title">Select a call</h2></div><span id="detail-state" class="badge">—</span></div>
|
|
27
|
+
<div id="detail-empty" class="empty-detail">Choose a call from the live feed to inspect its request, scope result, and response.</div>
|
|
28
|
+
<div id="detail-body" hidden>
|
|
29
|
+
<dl class="facts"><div><dt>Engagement</dt><dd id="detail-engagement"></dd></div><div><dt>Server / transport</dt><dd id="detail-server"></dd></div><div><dt>Target</dt><dd id="detail-target"></dd></div><div><dt>Started / duration</dt><dd id="detail-time"></dd></div></dl>
|
|
30
|
+
<div id="detail-note" class="notice" hidden></div>
|
|
31
|
+
<div class="section-heading"><h3>Original request</h3><button id="copy-request" class="small secondary">Copy JSON</button></div><pre id="original-request"></pre>
|
|
32
|
+
<div id="edited-section" hidden><h3>Forwarded request</h3><pre id="forwarded-request"></pre></div>
|
|
33
|
+
<div id="pending-actions" class="pending-actions" hidden><h3>Intercepted request</h3><p>Edit the JSON arguments if needed. The gateway checks your changes against the active engagement before forwarding.</p><textarea id="request-editor" spellcheck="false" aria-label="Tool arguments JSON"></textarea><div class="action-row"><button id="forward">Forward unchanged</button><button id="forward-edited" class="secondary">Forward edited</button><button id="drop" class="danger">Drop call</button></div></div>
|
|
34
|
+
<div class="section-heading"><h3>Response or decision</h3><button id="copy-response" class="small secondary">Copy JSON</button></div><pre id="response"></pre>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
<p class="footer-note">This view shows your own calls from the current gateway process. Its recent previews are held in memory; the gateway's audit log remains the durable record. Secret fields are redacted in completed previews.</p>
|
|
39
|
+
</main>
|
|
40
|
+
<script src="/workbench-client.js" defer></script>
|
|
41
|
+
</html>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
const MAX_ENTRIES = 300;
|
|
3
|
+
const MAX_PENDING = 32;
|
|
4
|
+
const MAX_PENDING_PER_USER = 8;
|
|
5
|
+
export const INTERCEPT_TIMEOUT_MS = 45_000;
|
|
6
|
+
function safeString(value) {
|
|
7
|
+
return value
|
|
8
|
+
.replace(/(authorization\s*:\s*(?:bearer|basic)\s+)[^\s\r\n]+/gi, "$1[redacted]")
|
|
9
|
+
.replace(/((?:api[_-]?key|token|password|cookie|client_secret)\s*[:=]\s*)[^\s,&\r\n]+/gi, "$1[redacted]")
|
|
10
|
+
.slice(0, 8192);
|
|
11
|
+
}
|
|
12
|
+
function previewValue(value, depth) {
|
|
13
|
+
if (depth > 6)
|
|
14
|
+
return "[nested data omitted]";
|
|
15
|
+
if (typeof value === "string")
|
|
16
|
+
return safeString(value);
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return value.slice(0, 50).map((item) => previewValue(item, depth + 1));
|
|
19
|
+
if (value && typeof value === "object") {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, item] of Object.entries(value).slice(0, 100)) {
|
|
22
|
+
result[key] = /authorization|password|secret|token|cookie|api.?key|private.?key/i.test(key)
|
|
23
|
+
? "[redacted]" : previewValue(item, depth + 1);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
export function workbenchPreview(value) {
|
|
30
|
+
const preview = previewValue(value, 0);
|
|
31
|
+
const serialized = JSON.stringify(preview);
|
|
32
|
+
return serialized && Buffer.byteLength(serialized) > 32 * 1024
|
|
33
|
+
? `${serialized.slice(0, 32 * 1024)}… [preview truncated]` : preview;
|
|
34
|
+
}
|
|
35
|
+
function targetFrom(args) {
|
|
36
|
+
const value = args.targetHostname ?? args.target ?? args.url ?? args.host;
|
|
37
|
+
if (typeof value !== "string")
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(value);
|
|
41
|
+
if (["https:", "http:"].includes(url.protocol))
|
|
42
|
+
return `${url.protocol}//${url.host}${url.pathname}`.slice(0, 200);
|
|
43
|
+
}
|
|
44
|
+
catch { /* Hostnames are not URLs. */ }
|
|
45
|
+
return safeString(value).slice(0, 200);
|
|
46
|
+
}
|
|
47
|
+
export class Workbench {
|
|
48
|
+
entries = [];
|
|
49
|
+
enabled = new Set();
|
|
50
|
+
pending = new Map();
|
|
51
|
+
begin(user, tool, server, transport, engagement, args) {
|
|
52
|
+
const preview = workbenchPreview(args);
|
|
53
|
+
const entry = {
|
|
54
|
+
id: randomUUID(), user, tool, server, transport, engagement, target: targetFrom(args),
|
|
55
|
+
startedAt: new Date().toISOString(), status: "running", edited: false,
|
|
56
|
+
originalRequest: preview, request: preview,
|
|
57
|
+
};
|
|
58
|
+
this.entries.push(entry);
|
|
59
|
+
if (this.entries.length > MAX_ENTRIES) {
|
|
60
|
+
const oldest = this.entries.findIndex((item) => item.status !== "pending");
|
|
61
|
+
if (oldest >= 0)
|
|
62
|
+
this.entries.splice(oldest, 1);
|
|
63
|
+
}
|
|
64
|
+
return entry;
|
|
65
|
+
}
|
|
66
|
+
snapshot(user) {
|
|
67
|
+
return {
|
|
68
|
+
interceptEnabled: this.enabled.has(user), timeoutSeconds: INTERCEPT_TIMEOUT_MS / 1000,
|
|
69
|
+
events: this.entries.filter((entry) => entry.user === user).slice(-200).reverse().map((entry) => {
|
|
70
|
+
const pending = this.pending.get(entry.id);
|
|
71
|
+
return { ...entry, ...(pending?.user === user ? { pendingRequest: structuredClone(pending.args) } : {}) };
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
setIntercept(user, enabled) {
|
|
76
|
+
if (enabled)
|
|
77
|
+
this.enabled.add(user);
|
|
78
|
+
else {
|
|
79
|
+
this.enabled.delete(user);
|
|
80
|
+
for (const [id, call] of this.pending)
|
|
81
|
+
if (call.user === user)
|
|
82
|
+
this.settle(id, { action: "drop", reason: "Interception was turned off before approval." });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
dropServer(server) {
|
|
86
|
+
for (const [id, call] of this.pending)
|
|
87
|
+
if (call.entry.server === server)
|
|
88
|
+
this.settle(id, { action: "drop", reason: "The MCP server was turned off before approval." });
|
|
89
|
+
}
|
|
90
|
+
async intercept(user, entry, args, signal) {
|
|
91
|
+
if (signal?.aborted) {
|
|
92
|
+
entry.status = "dropped";
|
|
93
|
+
entry.note = "Background job was canceled.";
|
|
94
|
+
return { action: "drop", reason: entry.note };
|
|
95
|
+
}
|
|
96
|
+
if (!this.enabled.has(user))
|
|
97
|
+
return { action: "forward", args };
|
|
98
|
+
if (this.pending.size >= MAX_PENDING || [...this.pending.values()].filter((call) => call.user === user).length >= MAX_PENDING_PER_USER) {
|
|
99
|
+
entry.status = "dropped";
|
|
100
|
+
entry.note = "Too many pending calls; request failed closed.";
|
|
101
|
+
return { action: "drop", reason: entry.note };
|
|
102
|
+
}
|
|
103
|
+
entry.status = "pending";
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
const timer = setTimeout(() => this.settle(entry.id, { action: "drop", reason: "Intercept timed out without approval." }), INTERCEPT_TIMEOUT_MS);
|
|
106
|
+
const onAbort = () => this.settle(entry.id, { action: "drop", reason: "Background job was canceled." });
|
|
107
|
+
this.pending.set(entry.id, { user, entry, args: structuredClone(args), resolve, timer, signal, onAbort });
|
|
108
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
decide(user, id, action, args) {
|
|
112
|
+
const call = this.pending.get(id);
|
|
113
|
+
if (!call || call.user !== user)
|
|
114
|
+
throw new Error("Pending call not found for this user");
|
|
115
|
+
if (action === "forward") {
|
|
116
|
+
if (args !== undefined && (!args || typeof args !== "object" || Array.isArray(args)))
|
|
117
|
+
throw new Error("Edited arguments must be a JSON object");
|
|
118
|
+
const chosen = args ?? call.args;
|
|
119
|
+
call.entry.edited = JSON.stringify(chosen) !== JSON.stringify(call.args);
|
|
120
|
+
call.entry.request = workbenchPreview(chosen);
|
|
121
|
+
call.entry.target = targetFrom(chosen);
|
|
122
|
+
this.settle(id, { action: "forward", args: structuredClone(chosen) });
|
|
123
|
+
}
|
|
124
|
+
else
|
|
125
|
+
this.settle(id, { action: "drop", reason: "Dropped in the workbench." });
|
|
126
|
+
return call.entry;
|
|
127
|
+
}
|
|
128
|
+
settle(id, decision) {
|
|
129
|
+
const call = this.pending.get(id);
|
|
130
|
+
if (!call)
|
|
131
|
+
return;
|
|
132
|
+
clearTimeout(call.timer);
|
|
133
|
+
if (call.onAbort)
|
|
134
|
+
call.signal?.removeEventListener("abort", call.onAbort);
|
|
135
|
+
this.pending.delete(id);
|
|
136
|
+
call.entry.status = decision.action === "drop" ? "dropped" : "forwarding";
|
|
137
|
+
if (decision.action === "drop")
|
|
138
|
+
call.entry.note = decision.reason;
|
|
139
|
+
call.resolve(decision);
|
|
140
|
+
}
|
|
141
|
+
finish(entry, result) {
|
|
142
|
+
entry.finishedAt = new Date().toISOString();
|
|
143
|
+
entry.durationMs = Math.max(0, Date.parse(entry.finishedAt) - Date.parse(entry.startedAt));
|
|
144
|
+
if (entry.status !== "dropped") {
|
|
145
|
+
const response = result && typeof result === "object" ? result : {};
|
|
146
|
+
const message = JSON.stringify(response.content ?? "");
|
|
147
|
+
entry.status = !response.isError ? "allowed" : /SCOPE_VIOLATION|scope|blocked|prohibited|revoked/i.test(message) ? "blocked" : "failed";
|
|
148
|
+
}
|
|
149
|
+
entry.response = workbenchPreview(result);
|
|
150
|
+
}
|
|
151
|
+
close() {
|
|
152
|
+
this.enabled.clear();
|
|
153
|
+
for (const id of this.pending.keys())
|
|
154
|
+
this.settle(id, { action: "drop", reason: "Gateway is shutting down." });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=workbench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workbench.js","sourceRoot":"","sources":["../../../src/gateway/workbench.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwBzC,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,uDAAuD,EAAE,cAAc,CAAC;SAChF,OAAO,CAAC,+EAA+E,EAAE,cAAc,CAAC;SACxG,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,KAAa;IACjD,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACjG,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YAC9D,MAAM,CAAC,GAAG,CAAC,GAAG,mEAAmE,CAAC,IAAI,CAAC,GAAG,CAAC;gBACzF,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3C,OAAO,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI;QAC5D,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,OAAO,CAAC;AACzE,CAAC;AAED,SAAS,UAAU,CAAC,IAAe;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrH,CAAC;IAAC,MAAM,CAAC,CAAC,6BAA6B,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACzC,CAAC;AAYD,MAAM,OAAO,SAAS;IACZ,OAAO,GAAqB,EAAE,CAAC;IAC/B,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAC5B,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEjD,KAAK,CAAC,IAAY,EAAE,IAAY,EAAE,MAAc,EAAE,SAAiB,EAAE,UAAyB,EAAE,IAAe;QAC7G,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,KAAK,GAAmB;YAC5B,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC;YACrF,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK;YACrE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;SAC3C,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;YAC3E,IAAI,MAAM,IAAI,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,OAAO;YACL,gBAAgB,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,oBAAoB,GAAG,IAAI;YACrF,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAC3C,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5G,CAAC,CAAC;SACH,CAAC;IACJ,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,OAAgB;QACzC,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;aAC/B,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1B,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO;gBAAE,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,8CAA8C,EAAE,CAAC,CAAC;QAC7J,CAAC;IACH,CAAC;IAED,UAAU,CAAC,MAAc;QACvB,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM;gBACrE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gDAAgD,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,KAAqB,EAAE,IAAe,EAAE,MAAoB;QACxF,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;YAAC,KAAK,CAAC,IAAI,GAAG,8BAA8B,CAAC;YAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAAC,CAAC;QAC9I,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAChE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,MAAM,IAAI,oBAAoB,EAAE,CAAC;YACvI,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;YAAC,KAAK,CAAC,IAAI,GAAG,gDAAgD,CAAC;YACxF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAChD,CAAC;QACD,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;QACzB,OAAO,IAAI,OAAO,CAAkB,CAAC,OAAO,EAAE,EAAE;YAC9C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC,EAAE,oBAAoB,CAAC,CAAC;YACjJ,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC,CAAC;YACxG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;YAC1G,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,EAAU,EAAE,MAA0B,EAAE,IAAgB;QAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACzF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAChJ,MAAM,MAAM,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;;YAAM,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAEO,MAAM,CAAC,EAAU,EAAE,QAAyB;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1E,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;QAC1E,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM;YAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC;QAClE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,KAAqB,EAAE,MAAe;QAC3C,KAAK,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5C,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QAC3F,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAkD,CAAC,CAAC,CAAC,EAAE,CAAC;YAChH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;YACvD,KAAK,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mDAAmD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1I,CAAC;QACD,KAAK,CAAC,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC,CAAC;IACjH,CAAC;CACF"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { loadManifest } from "./manifest.js";
|
|
|
4
4
|
export { loadConfig } from "./config.js";
|
|
5
5
|
export { AuditLogger } from "./audit/index.js";
|
|
6
6
|
export type { ScopeManifest, AuditEntry, ValidationResult, Decision } from "./types.js";
|
|
7
|
-
export declare const VERSION = "0.
|
|
7
|
+
export declare const VERSION = "0.4.0";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.js
CHANGED
|
@@ -3,5 +3,5 @@ export { ScopeValidator } from "./validator.js";
|
|
|
3
3
|
export { loadManifest } from "./manifest.js";
|
|
4
4
|
export { loadConfig } from "./config.js";
|
|
5
5
|
export { AuditLogger } from "./audit/index.js";
|
|
6
|
-
export const VERSION = "0.
|
|
6
|
+
export const VERSION = "0.4.0";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/src/proxy.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ScopeManifest, AuditEntry } from "./types.js";
|
|
2
2
|
import { AuditLogger } from "./audit/index.js";
|
|
3
|
+
import { type TechniqueMappings } from "./technique-checker.js";
|
|
3
4
|
import type { ProxyConfig } from "./config.js";
|
|
4
5
|
export interface HandleResult {
|
|
5
6
|
/** True when the original message may be sent to the upstream unchanged. */
|
|
@@ -16,9 +17,10 @@ export declare class ScopeEnforcementProxy {
|
|
|
16
17
|
private serverName;
|
|
17
18
|
setServerName(name: string): void;
|
|
18
19
|
getServerName(): string | undefined;
|
|
19
|
-
constructor(manifest: ScopeManifest, config: ProxyConfig, requiredAuditSink?: ((entry: AuditEntry) => Promise<void>) | undefined, requireMappedTechnique?: boolean, executionProfile?: "isolated" | "restricted" | "connected");
|
|
20
|
+
constructor(manifest: ScopeManifest, config: ProxyConfig, requiredAuditSink?: ((entry: AuditEntry) => Promise<void>) | undefined, requireMappedTechnique?: boolean, executionProfile?: "isolated" | "restricted" | "connected", additionalTechniqueMappings?: TechniqueMappings);
|
|
20
21
|
getAuditLogger(): AuditLogger;
|
|
21
22
|
registerTrivyBatch(batchId: string, actor?: string): void;
|
|
23
|
+
recordOperatorDrop(toolName: string, params: Record<string, unknown>, reason: string, actor?: string): Promise<void>;
|
|
22
24
|
handleMessage(message: unknown, actor?: string): Promise<HandleResult>;
|
|
23
25
|
/**
|
|
24
26
|
* Build a JSON-RPC parse-error response for input the proxy could not parse.
|
package/dist/src/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAA2C,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,qBAAa,qBAAqB;IAU0B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAT7F,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAqB;IAEvC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IACjC,aAAa,IAAI,MAAM,GAAG,SAAS;gBAEvB,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAmB,iBAAiB,CAAC,GAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,aAAA,EAAE,sBAAsB,UAAQ,EAAE,gBAAgB,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,WAAW,EAAE,2BAA2B,CAAC,EAAE,iBAAiB;IAchR,cAAc,IAAI,WAAW;IAC7B,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAEnD,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAapH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAoB5E;;;;OAIG;IACH,MAAM,CAAC,kBAAkB,IAAI,OAAO;YAYtB,WAAW;IA2BzB;;;OAGG;YACW,aAAa;CAmE5B"}
|
package/dist/src/proxy.js
CHANGED
|
@@ -13,17 +13,35 @@ export class ScopeEnforcementProxy {
|
|
|
13
13
|
serverName;
|
|
14
14
|
setServerName(name) { this.serverName = name; }
|
|
15
15
|
getServerName() { return this.serverName; }
|
|
16
|
-
constructor(manifest, config, requiredAuditSink, requireMappedTechnique = false, executionProfile) {
|
|
16
|
+
constructor(manifest, config, requiredAuditSink, requireMappedTechnique = false, executionProfile, additionalTechniqueMappings) {
|
|
17
17
|
this.requiredAuditSink = requiredAuditSink;
|
|
18
18
|
this.manifest = manifest;
|
|
19
19
|
this.config = config;
|
|
20
20
|
const dnsResolver = new DnsResolver(config.dnsCacheTtl);
|
|
21
|
-
const
|
|
21
|
+
const configuredMappings = config.techniqueMapPath ? loadTechniqueMappings(config.techniqueMapPath) : {};
|
|
22
|
+
for (const [category, mapping] of Object.entries(additionalTechniqueMappings ?? {})) {
|
|
23
|
+
const current = configuredMappings[category] ?? { exact: [], patterns: [] };
|
|
24
|
+
configuredMappings[category] = { exact: [...current.exact, ...mapping.exact], patterns: [...current.patterns, ...mapping.patterns] };
|
|
25
|
+
}
|
|
26
|
+
const techniqueChecker = new TechniqueChecker(configuredMappings);
|
|
22
27
|
this.validator = new ScopeValidator(manifest, dnsResolver, techniqueChecker, config.allowUnstructuredCommands, requireMappedTechnique, executionProfile);
|
|
23
28
|
this.auditLogger = new AuditLogger(config.auditLogPath);
|
|
24
29
|
}
|
|
25
30
|
getAuditLogger() { return this.auditLogger; }
|
|
26
31
|
registerTrivyBatch(batchId, actor) { this.validator.registerTrivyBatch(batchId, actor); }
|
|
32
|
+
async recordOperatorDrop(toolName, params, reason, actor) {
|
|
33
|
+
const entry = {
|
|
34
|
+
timestamp: new Date().toISOString(), engagement_id: this.manifest.engagement_id,
|
|
35
|
+
client: this.manifest.client, operator: this.manifest.operator, tool_name: toolName,
|
|
36
|
+
tool_parameters: sanitizeParams(params), extracted_target: null, resolved_ips: [],
|
|
37
|
+
decision: "BLOCKED_BY_OPERATOR", decision_reason: reason, matched_scope_item: null,
|
|
38
|
+
duration_ms: 0, proxy_version: this.config.proxyVersion, server_name: this.serverName, actor,
|
|
39
|
+
};
|
|
40
|
+
if (this.requiredAuditSink)
|
|
41
|
+
await this.requiredAuditSink(entry);
|
|
42
|
+
await this.auditLogger.log(entry);
|
|
43
|
+
await this.auditLogger.flushLocal();
|
|
44
|
+
}
|
|
27
45
|
async handleMessage(message, actor) {
|
|
28
46
|
// JSON-RPC batch: every tools/call inside must pass. If any element is
|
|
29
47
|
// blocked the whole batch is rejected so that the agent gets exactly one
|
package/dist/src/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { runScanner } from "./runner.js";
|
|
2
|
+
export declare class NucleiJobs {
|
|
3
|
+
private readonly execute;
|
|
4
|
+
private jobs;
|
|
5
|
+
constructor(execute?: typeof runScanner);
|
|
6
|
+
start(url: unknown, deadline?: unknown): {
|
|
7
|
+
scanId: string;
|
|
8
|
+
status: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
status(scanId: unknown, url: unknown): {
|
|
12
|
+
scanId: string;
|
|
13
|
+
status: string;
|
|
14
|
+
startedAt: string;
|
|
15
|
+
finishedAt?: string;
|
|
16
|
+
result?: string;
|
|
17
|
+
};
|
|
18
|
+
cancel(scanId: unknown, url: unknown): {
|
|
19
|
+
scanId: string;
|
|
20
|
+
status: string;
|
|
21
|
+
};
|
|
22
|
+
private get;
|
|
23
|
+
private prune;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=jobs.d.ts.map
|