@node9/proxy 2.34.0 → 2.35.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/dist/cli.js +1023 -504
- package/dist/cli.mjs +998 -479
- package/dist/dashboard.mjs +36 -6
- package/dist/index.js +424 -133
- package/dist/index.mjs +424 -133
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -13762,6 +13762,9 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
13762
13762
|
...transcriptPathField,
|
|
13763
13763
|
...taintFields,
|
|
13764
13764
|
...exposureFields,
|
|
13765
|
+
...meta?.decidedBy && { decidedBy: meta.decidedBy },
|
|
13766
|
+
...meta?.channel && { channel: meta.channel },
|
|
13767
|
+
...meta?.cardId && { cardId: meta.cardId },
|
|
13765
13768
|
...testRun,
|
|
13766
13769
|
agent: meta?.agent,
|
|
13767
13770
|
mcpServer: meta?.mcpServer,
|
|
@@ -16849,8 +16852,204 @@ var init_state = __esm({
|
|
|
16849
16852
|
}
|
|
16850
16853
|
});
|
|
16851
16854
|
|
|
16855
|
+
// src/auth/decision-record.ts
|
|
16856
|
+
function personRecord(channel, decision, extra = {}, now = Date.now()) {
|
|
16857
|
+
return {
|
|
16858
|
+
decision,
|
|
16859
|
+
decidedBy: "person",
|
|
16860
|
+
channel,
|
|
16861
|
+
label: extra.redirect ? REDIRECT_LABEL : PERSON_LABELS[channel],
|
|
16862
|
+
...extra,
|
|
16863
|
+
decidedAt: now
|
|
16864
|
+
};
|
|
16865
|
+
}
|
|
16866
|
+
function timerRecord(timeoutMs, now = Date.now()) {
|
|
16867
|
+
return {
|
|
16868
|
+
decision: "deny",
|
|
16869
|
+
decidedBy: "timer",
|
|
16870
|
+
label: TIMER_LABEL,
|
|
16871
|
+
reason: `No response within ${Math.round(timeoutMs / 1e3)}s: auto-denied by the approval timeout.`,
|
|
16872
|
+
decidedAt: now
|
|
16873
|
+
};
|
|
16874
|
+
}
|
|
16875
|
+
function abandonedRecord(now = Date.now()) {
|
|
16876
|
+
return {
|
|
16877
|
+
decision: "deny",
|
|
16878
|
+
decidedBy: "abandoned",
|
|
16879
|
+
label: ABANDONED_LABEL,
|
|
16880
|
+
reason: "No approver was left to answer: node9 tail was closed.",
|
|
16881
|
+
decidedAt: now
|
|
16882
|
+
};
|
|
16883
|
+
}
|
|
16884
|
+
function boundedString(v, max) {
|
|
16885
|
+
return typeof v === "string" && v.length > 0 ? v.slice(0, max) : void 0;
|
|
16886
|
+
}
|
|
16887
|
+
function parseDecisionRecord(x) {
|
|
16888
|
+
if (!x || typeof x !== "object") return null;
|
|
16889
|
+
const r = x;
|
|
16890
|
+
if (r.decision !== "allow" && r.decision !== "deny") return null;
|
|
16891
|
+
if (typeof r.decidedBy !== "string" || !DECIDED_BY.has(r.decidedBy)) return null;
|
|
16892
|
+
const label3 = boundedString(r.label, 200);
|
|
16893
|
+
if (!label3) return null;
|
|
16894
|
+
const channel = typeof r.channel === "string" && CHANNELS.includes(r.channel) ? r.channel : void 0;
|
|
16895
|
+
if (r.decidedBy === "person" && (!channel || !PERSON_CHANNELS.has(channel))) return null;
|
|
16896
|
+
const out = {
|
|
16897
|
+
decision: r.decision,
|
|
16898
|
+
decidedBy: r.decidedBy,
|
|
16899
|
+
label: label3,
|
|
16900
|
+
decidedAt: typeof r.decidedAt === "number" && Number.isFinite(r.decidedAt) ? r.decidedAt : 0
|
|
16901
|
+
};
|
|
16902
|
+
if (channel) out.channel = channel;
|
|
16903
|
+
const redirect = boundedString(r.redirect, 2e3);
|
|
16904
|
+
if (redirect) out.redirect = redirect;
|
|
16905
|
+
const reason = boundedString(r.reason, 2e3);
|
|
16906
|
+
if (reason) out.reason = reason;
|
|
16907
|
+
const ruleName = boundedString(r.ruleName, 256);
|
|
16908
|
+
if (ruleName) out.ruleName = ruleName;
|
|
16909
|
+
if (typeof r.standing === "string" && STANDING.has(r.standing))
|
|
16910
|
+
out.standing = r.standing;
|
|
16911
|
+
const trustDuration = boundedString(r.trustDuration, 8);
|
|
16912
|
+
if (trustDuration) out.trustDuration = trustDuration;
|
|
16913
|
+
return out;
|
|
16914
|
+
}
|
|
16915
|
+
function legacyWaitFields(record2) {
|
|
16916
|
+
switch (record2.decidedBy) {
|
|
16917
|
+
case "person": {
|
|
16918
|
+
const source = record2.redirect ? "terminal-redirect" : record2.channel === "native" ? "native" : "terminal";
|
|
16919
|
+
const reason = record2.redirect ?? record2.reason;
|
|
16920
|
+
return { decision: record2.decision, source, ...reason && { reason } };
|
|
16921
|
+
}
|
|
16922
|
+
case "timer":
|
|
16923
|
+
return {
|
|
16924
|
+
decision: record2.decision,
|
|
16925
|
+
source: "timeout",
|
|
16926
|
+
...record2.reason && { reason: record2.reason }
|
|
16927
|
+
};
|
|
16928
|
+
case "cloud":
|
|
16929
|
+
case "policy":
|
|
16930
|
+
return {
|
|
16931
|
+
decision: record2.decision,
|
|
16932
|
+
source: record2.decidedBy,
|
|
16933
|
+
...record2.reason && { reason: record2.reason }
|
|
16934
|
+
};
|
|
16935
|
+
case "abandoned":
|
|
16936
|
+
return { decision: "abandoned" };
|
|
16937
|
+
}
|
|
16938
|
+
}
|
|
16939
|
+
function recordFromLegacyWait(body, now = Date.now()) {
|
|
16940
|
+
if (body.decision === "abandoned") return abandonedRecord(now);
|
|
16941
|
+
const decision = body.decision;
|
|
16942
|
+
switch (body.source) {
|
|
16943
|
+
case "terminal":
|
|
16944
|
+
case "browser":
|
|
16945
|
+
return personRecord("terminal", decision, body.reason ? { reason: body.reason } : {}, now);
|
|
16946
|
+
case "native":
|
|
16947
|
+
return personRecord("native", decision, {}, now);
|
|
16948
|
+
case "terminal-redirect":
|
|
16949
|
+
return personRecord("terminal", decision, body.reason ? { redirect: body.reason } : {}, now);
|
|
16950
|
+
case "timeout":
|
|
16951
|
+
return { ...timerRecord(0, now), ...body.reason && { reason: body.reason } };
|
|
16952
|
+
case "cloud":
|
|
16953
|
+
return {
|
|
16954
|
+
decision,
|
|
16955
|
+
decidedBy: "cloud",
|
|
16956
|
+
label: CLOUD_LABEL,
|
|
16957
|
+
...body.reason && { reason: body.reason },
|
|
16958
|
+
decidedAt: now
|
|
16959
|
+
};
|
|
16960
|
+
case "policy":
|
|
16961
|
+
return {
|
|
16962
|
+
decision,
|
|
16963
|
+
decidedBy: "policy",
|
|
16964
|
+
label: "Policy",
|
|
16965
|
+
...body.reason && { reason: body.reason },
|
|
16966
|
+
decidedAt: now
|
|
16967
|
+
};
|
|
16968
|
+
default:
|
|
16969
|
+
return null;
|
|
16970
|
+
}
|
|
16971
|
+
}
|
|
16972
|
+
function recordFromVerdict(v, now = Date.now()) {
|
|
16973
|
+
const decidedBy = v.checkedBy === "cloud" || v.blockedBy === "team-policy" ? "cloud" : v.blockedBy === "timeout" ? "timer" : "policy";
|
|
16974
|
+
return {
|
|
16975
|
+
decision: v.approved ? "allow" : "deny",
|
|
16976
|
+
decidedBy,
|
|
16977
|
+
label: v.blockedByLabel || (decidedBy === "cloud" ? CLOUD_LABEL : v.approved ? "Allowed by policy" : "Blocked by policy"),
|
|
16978
|
+
...v.reason && { reason: v.reason },
|
|
16979
|
+
...v.ruleHit && { ruleName: v.ruleHit },
|
|
16980
|
+
decidedAt: now
|
|
16981
|
+
};
|
|
16982
|
+
}
|
|
16983
|
+
function channelFromDecisionSource(source) {
|
|
16984
|
+
switch (source) {
|
|
16985
|
+
case void 0:
|
|
16986
|
+
case "terminal":
|
|
16987
|
+
case "browser":
|
|
16988
|
+
return { channel: "terminal", redirect: false };
|
|
16989
|
+
case "terminal-redirect":
|
|
16990
|
+
return { channel: "terminal", redirect: true };
|
|
16991
|
+
case "monitor":
|
|
16992
|
+
case "dashboard":
|
|
16993
|
+
return { channel: "monitor", redirect: false };
|
|
16994
|
+
case "native":
|
|
16995
|
+
return { channel: "native", redirect: false };
|
|
16996
|
+
default:
|
|
16997
|
+
return null;
|
|
16998
|
+
}
|
|
16999
|
+
}
|
|
17000
|
+
function recordFromLegacyResolve(decision, source, now = Date.now()) {
|
|
17001
|
+
const person = source === void 0 ? null : channelFromDecisionSource(source);
|
|
17002
|
+
if (person) return personRecord(person.channel, decision, {}, now);
|
|
17003
|
+
if (decision === "allow")
|
|
17004
|
+
return { decision, decidedBy: "cloud", label: CLOUD_LABEL, decidedAt: now };
|
|
17005
|
+
return {
|
|
17006
|
+
decision,
|
|
17007
|
+
decidedBy: "timer",
|
|
17008
|
+
label: "Resolved by the hook",
|
|
17009
|
+
reason: "An older node9 hook ended the approval without naming who decided.",
|
|
17010
|
+
decidedAt: now
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
var CHANNELS, LOCAL_UI_CHANNELS, PERSON_CHANNELS, CARD_PROTOCOL, PERSON_LABELS, REDIRECT_LABEL, TIMER_LABEL, CLOUD_LABEL, ABANDONED_LABEL, DECIDED_BY, STANDING;
|
|
17014
|
+
var init_decision_record = __esm({
|
|
17015
|
+
"src/auth/decision-record.ts"() {
|
|
17016
|
+
"use strict";
|
|
17017
|
+
CHANNELS = ["terminal", "monitor", "native", "cloud"];
|
|
17018
|
+
LOCAL_UI_CHANNELS = /* @__PURE__ */ new Set(["terminal", "monitor"]);
|
|
17019
|
+
PERSON_CHANNELS = /* @__PURE__ */ new Set([
|
|
17020
|
+
"terminal",
|
|
17021
|
+
"monitor",
|
|
17022
|
+
"native"
|
|
17023
|
+
]);
|
|
17024
|
+
CARD_PROTOCOL = 2;
|
|
17025
|
+
PERSON_LABELS = {
|
|
17026
|
+
terminal: "User Decision (Terminal (node9 tail))",
|
|
17027
|
+
monitor: "User Decision (node9 monitor)",
|
|
17028
|
+
native: "User Decision (Native)"
|
|
17029
|
+
};
|
|
17030
|
+
REDIRECT_LABEL = "Steered Redirect (Terminal)";
|
|
17031
|
+
TIMER_LABEL = "Approval Timeout";
|
|
17032
|
+
CLOUD_LABEL = "Organization Policy (SaaS)";
|
|
17033
|
+
ABANDONED_LABEL = "No Approver (node9 tail closed)";
|
|
17034
|
+
DECIDED_BY = /* @__PURE__ */ new Set([
|
|
17035
|
+
"person",
|
|
17036
|
+
"timer",
|
|
17037
|
+
"cloud",
|
|
17038
|
+
"policy",
|
|
17039
|
+
"abandoned"
|
|
17040
|
+
]);
|
|
17041
|
+
STANDING = /* @__PURE__ */ new Set([
|
|
17042
|
+
"always-allow",
|
|
17043
|
+
"always-deny",
|
|
17044
|
+
"trust",
|
|
17045
|
+
"trust-recipients"
|
|
17046
|
+
]);
|
|
17047
|
+
}
|
|
17048
|
+
});
|
|
17049
|
+
|
|
16852
17050
|
// src/auth/daemon.ts
|
|
16853
17051
|
import fs11 from "fs";
|
|
17052
|
+
import http from "http";
|
|
16854
17053
|
import net from "net";
|
|
16855
17054
|
import path11 from "path";
|
|
16856
17055
|
import os9 from "os";
|
|
@@ -16969,7 +17168,7 @@ async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
|
16969
17168
|
return false;
|
|
16970
17169
|
}
|
|
16971
17170
|
}
|
|
16972
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId) {
|
|
17171
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId, card) {
|
|
16973
17172
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16974
17173
|
const ctrl = new AbortController();
|
|
16975
17174
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -16995,19 +17194,43 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
16995
17194
|
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
16996
17195
|
...viewOnly && { viewOnly: true },
|
|
16997
17196
|
...localSmartRuleMatched && { localSmartRuleMatched: true },
|
|
16998
|
-
...sessionId && { sessionId }
|
|
17197
|
+
...sessionId && { sessionId },
|
|
17198
|
+
...cardFields(card)
|
|
16999
17199
|
}),
|
|
17000
17200
|
signal: ctrl.signal
|
|
17001
17201
|
});
|
|
17002
17202
|
if (!res.ok) throw new Error("Daemon fail");
|
|
17003
|
-
|
|
17004
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
17203
|
+
return registeredCard(await res.json());
|
|
17005
17204
|
} finally {
|
|
17006
17205
|
clearTimeout(timer);
|
|
17007
17206
|
}
|
|
17008
17207
|
}
|
|
17208
|
+
function cardFields(card) {
|
|
17209
|
+
return {
|
|
17210
|
+
// A review card: the hook already decided this call needs a person; the
|
|
17211
|
+
// daemon never checks it again (approval-card design, defect 1).
|
|
17212
|
+
kind: "review",
|
|
17213
|
+
// Protocol 2 makes the card's /wait require the internal token: claim it
|
|
17214
|
+
// only when this process can read the token (a daemon under another uid
|
|
17215
|
+
// is running but its pid file probe fails with EPERM), or every wait
|
|
17216
|
+
// would get 403 and the review would be denied unseen.
|
|
17217
|
+
...getInternalToken() && { protocol: CARD_PROTOCOL },
|
|
17218
|
+
...card?.channels && { channels: card.channels },
|
|
17219
|
+
...typeof card?.timeoutMs === "number" && { timeoutMs: card.timeoutMs }
|
|
17220
|
+
};
|
|
17221
|
+
}
|
|
17222
|
+
function registeredCard(body) {
|
|
17223
|
+
const b = body;
|
|
17224
|
+
return {
|
|
17225
|
+
id: b.id,
|
|
17226
|
+
allowCount: b.allowCount ?? 1,
|
|
17227
|
+
...b.protocol === CARD_PROTOCOL && {
|
|
17228
|
+
protocol: CARD_PROTOCOL,
|
|
17229
|
+
deadline: typeof b.deadline === "number" ? b.deadline : null
|
|
17230
|
+
}
|
|
17231
|
+
};
|
|
17232
|
+
}
|
|
17009
17233
|
async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
17010
|
-
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
17011
17234
|
const waitCtrl = new AbortController();
|
|
17012
17235
|
let capped = false;
|
|
17013
17236
|
const waitTimer = maxWaitMs > 0 ? setTimeout(() => {
|
|
@@ -17017,14 +17240,18 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
17017
17240
|
const onAbort = () => waitCtrl.abort();
|
|
17018
17241
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
17019
17242
|
try {
|
|
17020
|
-
const
|
|
17243
|
+
const { status, body } = await daemonGet(`/wait/${id}`, waitCtrl.signal).catch(
|
|
17021
17244
|
(err2) => {
|
|
17022
17245
|
if (capped && !signal?.aborted) throw new Error("node9 daemon did not answer in time");
|
|
17023
17246
|
throw err2;
|
|
17024
17247
|
}
|
|
17025
17248
|
);
|
|
17026
|
-
if (
|
|
17027
|
-
|
|
17249
|
+
if (status === 409) throw new Error("node9 card was withdrawn");
|
|
17250
|
+
if (status !== 200) return { decision: "deny" };
|
|
17251
|
+
const parsed = JSON.parse(body);
|
|
17252
|
+
const record2 = parseDecisionRecord(parsed.record);
|
|
17253
|
+
if (record2) return { ...legacyWaitFields(record2), record: record2 };
|
|
17254
|
+
const { decision, source, reason } = parsed;
|
|
17028
17255
|
if (decision === "allow") return { decision: "allow", source };
|
|
17029
17256
|
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
17030
17257
|
return { decision: "deny", source, reason };
|
|
@@ -17033,7 +17260,29 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
17033
17260
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
17034
17261
|
}
|
|
17035
17262
|
}
|
|
17036
|
-
|
|
17263
|
+
function daemonGet(urlPath, signal, token = getInternalToken()) {
|
|
17264
|
+
return new Promise((resolve2, reject) => {
|
|
17265
|
+
const req = http.get(
|
|
17266
|
+
{
|
|
17267
|
+
host: DAEMON_HOST,
|
|
17268
|
+
port: DAEMON_PORT,
|
|
17269
|
+
path: urlPath,
|
|
17270
|
+
signal,
|
|
17271
|
+
agent: false,
|
|
17272
|
+
headers: token ? { "X-Node9-Internal": token } : {}
|
|
17273
|
+
},
|
|
17274
|
+
(res) => {
|
|
17275
|
+
let body = "";
|
|
17276
|
+
res.setEncoding("utf8");
|
|
17277
|
+
res.on("data", (chunk2) => body += chunk2);
|
|
17278
|
+
res.on("end", () => resolve2({ status: res.statusCode ?? 0, body }));
|
|
17279
|
+
res.on("error", reject);
|
|
17280
|
+
}
|
|
17281
|
+
);
|
|
17282
|
+
req.on("error", reject);
|
|
17283
|
+
});
|
|
17284
|
+
}
|
|
17285
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent, extra) {
|
|
17037
17286
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
17038
17287
|
const res = await fetch(`${base2}/check`, {
|
|
17039
17288
|
method: "POST",
|
|
@@ -17049,13 +17298,16 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId
|
|
|
17049
17298
|
// event via socket. Same contract as registerDaemonEntry — without
|
|
17050
17299
|
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
17051
17300
|
fromCLI: socketActivitySent !== false,
|
|
17052
|
-
activityId
|
|
17301
|
+
activityId,
|
|
17302
|
+
...extra?.cwd && { cwd: extra.cwd },
|
|
17303
|
+
...extra?.recoveryCommand && { recoveryCommand: extra.recoveryCommand },
|
|
17304
|
+
...extra?.sessionId && { sessionId: extra.sessionId },
|
|
17305
|
+
...cardFields(extra?.card)
|
|
17053
17306
|
}),
|
|
17054
17307
|
signal: AbortSignal.timeout(3e3)
|
|
17055
17308
|
});
|
|
17056
17309
|
if (!res.ok) throw new Error("Daemon unreachable");
|
|
17057
|
-
|
|
17058
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
17310
|
+
return registeredCard(await res.json());
|
|
17059
17311
|
}
|
|
17060
17312
|
async function notifyTaint(filePath, source, fromEid) {
|
|
17061
17313
|
if (!isDaemonRunning()) return;
|
|
@@ -17239,19 +17491,24 @@ async function clearSessionExposure(opts) {
|
|
|
17239
17491
|
return { ok: false, cleared: 0, daemonUnavailable: true };
|
|
17240
17492
|
}
|
|
17241
17493
|
}
|
|
17242
|
-
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
17494
|
+
async function resolveViaDaemon(id, decision, internalToken, source, record2) {
|
|
17243
17495
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
17244
|
-
await fetch(`${base2}/resolve/${id}`, {
|
|
17496
|
+
const res = await fetch(`${base2}/resolve/${id}`, {
|
|
17245
17497
|
method: "POST",
|
|
17246
17498
|
headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
|
|
17247
|
-
body: JSON.stringify({ decision, ...source && { source } }),
|
|
17499
|
+
body: JSON.stringify({ decision, ...source && { source }, ...record2 && { record: record2 } }),
|
|
17248
17500
|
signal: AbortSignal.timeout(3e3)
|
|
17249
17501
|
});
|
|
17502
|
+
if (!res.ok) return null;
|
|
17503
|
+
const body = await res.json().catch(() => null);
|
|
17504
|
+
const winner = parseDecisionRecord(body?.record);
|
|
17505
|
+
return winner && typeof body?.won === "boolean" ? { won: body.won, record: winner } : null;
|
|
17250
17506
|
}
|
|
17251
17507
|
var ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
|
|
17252
17508
|
var init_daemon = __esm({
|
|
17253
17509
|
"src/auth/daemon.ts"() {
|
|
17254
17510
|
"use strict";
|
|
17511
|
+
init_decision_record();
|
|
17255
17512
|
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path11.join(os9.tmpdir(), "node9-activity.sock");
|
|
17256
17513
|
DEFAULT_DAEMON_PORT = 7391;
|
|
17257
17514
|
DAEMON_PORT = resolveDaemonPort();
|
|
@@ -19524,6 +19781,13 @@ var init_check = __esm({
|
|
|
19524
19781
|
|
|
19525
19782
|
// src/policy/negotiation.ts
|
|
19526
19783
|
function buildNegotiationMessage(blockedByLabel, isHumanDecision2, humanReason, recoveryCommand, context) {
|
|
19784
|
+
if (isHumanDecision2 && context?.redirect && humanReason) {
|
|
19785
|
+
return `NODE9: The user stopped this action and gave you an instruction.
|
|
19786
|
+
INSTRUCTION FROM THE USER: ${humanReason}
|
|
19787
|
+
INSTRUCTIONS:
|
|
19788
|
+
- Follow the user's instruction above.
|
|
19789
|
+
- Retry the original action only if the instruction says you may.`;
|
|
19790
|
+
}
|
|
19527
19791
|
if (isHumanDecision2) {
|
|
19528
19792
|
return `NODE9: The human user rejected this action.
|
|
19529
19793
|
REASON: ${humanReason || "No specific reason provided."}
|
|
@@ -19614,7 +19878,8 @@ function buildReviewMessage(blockedByLabel, ruleDescription, reason) {
|
|
|
19614
19878
|
const sentence2 = /[.!?\u2026]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
19615
19879
|
return `Node9 flagged this for your review: ${sentence2} Approve to proceed, or deny to cancel.`;
|
|
19616
19880
|
}
|
|
19617
|
-
function isHumanDecision(label3, blockedBy) {
|
|
19881
|
+
function isHumanDecision(label3, blockedBy, decidedBy) {
|
|
19882
|
+
if (decidedBy !== void 0) return decidedBy === "person";
|
|
19618
19883
|
if (blockedBy !== void 0 && blockedBy !== "local-decision") return false;
|
|
19619
19884
|
const l = label3.toLowerCase();
|
|
19620
19885
|
return l.includes("user") || l.includes("daemon") || l.includes("decision");
|
|
@@ -19646,26 +19911,103 @@ var init_negotiation = __esm({
|
|
|
19646
19911
|
}
|
|
19647
19912
|
});
|
|
19648
19913
|
|
|
19649
|
-
// src/auth/
|
|
19650
|
-
function
|
|
19651
|
-
|
|
19652
|
-
|
|
19914
|
+
// src/auth/card-result.ts
|
|
19915
|
+
function authResultFromRecord(r, ctx = {}) {
|
|
19916
|
+
const who = { decidedBy: r.decidedBy, ...r.channel && { channel: r.channel } };
|
|
19917
|
+
switch (r.decidedBy) {
|
|
19918
|
+
case "person": {
|
|
19919
|
+
const channel = r.channel ?? "terminal";
|
|
19920
|
+
const decisionSource = channel === "native" ? "native" : "terminal";
|
|
19921
|
+
if (r.decision === "allow") {
|
|
19922
|
+
return { approved: true, checkedBy: "daemon", decisionSource, ...who };
|
|
19923
|
+
}
|
|
19924
|
+
return {
|
|
19925
|
+
approved: false,
|
|
19926
|
+
reason: r.redirect || `The human user rejected this action via the Node9 ${CHANNEL_NAMES[channel]}.`,
|
|
19927
|
+
blockedBy: "local-decision",
|
|
19928
|
+
blockedByLabel: r.label,
|
|
19929
|
+
decisionSource,
|
|
19930
|
+
...who,
|
|
19931
|
+
...r.redirect && { redirect: r.redirect }
|
|
19932
|
+
};
|
|
19933
|
+
}
|
|
19934
|
+
case "timer":
|
|
19935
|
+
if (r.decision === "deny" && ctx.downgradedBlock)
|
|
19936
|
+
return { ...ctx.downgradedBlock, blockedBy: "timeout", ...who };
|
|
19937
|
+
return r.decision === "allow" ? { approved: true, ...who } : {
|
|
19938
|
+
approved: false,
|
|
19939
|
+
reason: ctx.timeoutReason || r.reason || "No response: auto-denied after the approval timeout.",
|
|
19940
|
+
blockedBy: "timeout",
|
|
19941
|
+
blockedByLabel: TIMER_LABEL,
|
|
19942
|
+
...who
|
|
19943
|
+
};
|
|
19944
|
+
case "cloud":
|
|
19945
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "cloud", decisionSource: "cloud", ...who } : {
|
|
19946
|
+
approved: false,
|
|
19947
|
+
reason: r.reason || "Action rejected by organization administrator via Slack.",
|
|
19948
|
+
blockedBy: "team-policy",
|
|
19949
|
+
blockedByLabel: CLOUD_LABEL,
|
|
19950
|
+
decisionSource: "cloud",
|
|
19951
|
+
...who
|
|
19952
|
+
};
|
|
19953
|
+
case "policy":
|
|
19954
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "local-policy", ...who } : {
|
|
19955
|
+
approved: false,
|
|
19956
|
+
reason: r.reason || "Action blocked by policy.",
|
|
19957
|
+
blockedBy: "local-config",
|
|
19958
|
+
blockedByLabel: r.label,
|
|
19959
|
+
...r.ruleName && { ruleHit: r.ruleName },
|
|
19960
|
+
...who
|
|
19961
|
+
};
|
|
19962
|
+
case "abandoned":
|
|
19963
|
+
return {
|
|
19964
|
+
approved: false,
|
|
19965
|
+
reason: r.reason || "No approver was left to answer.",
|
|
19966
|
+
blockedBy: "no-approval-mechanism",
|
|
19967
|
+
blockedByLabel: ABANDONED_LABEL,
|
|
19968
|
+
...who
|
|
19969
|
+
};
|
|
19970
|
+
}
|
|
19971
|
+
}
|
|
19972
|
+
function approvalUnavailable(reason) {
|
|
19973
|
+
return {
|
|
19974
|
+
approved: false,
|
|
19975
|
+
reason: reason || "node9 could not get an approval decision for this call.",
|
|
19976
|
+
blockedBy: "no-approval-mechanism",
|
|
19977
|
+
blockedByLabel: "Approval Unavailable"
|
|
19978
|
+
};
|
|
19979
|
+
}
|
|
19980
|
+
function decidedByOf(res) {
|
|
19981
|
+
if (res.decidedBy) return res.decidedBy;
|
|
19982
|
+
if (res.blockedBy === "timeout") return "timer";
|
|
19983
|
+
if (res.checkedBy === "cloud" || res.blockedBy === "team-policy") return "cloud";
|
|
19653
19984
|
return "policy";
|
|
19654
19985
|
}
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
|
|
19986
|
+
function recordFromAuthResult(res, now = Date.now()) {
|
|
19987
|
+
const decidedBy = decidedByOf(res);
|
|
19988
|
+
const channel = res.channel ?? (decidedBy === "cloud" ? "cloud" : decidedBy === "person" ? "native" : void 0);
|
|
19989
|
+
return {
|
|
19990
|
+
decision: res.approved ? "allow" : "deny",
|
|
19991
|
+
decidedBy,
|
|
19992
|
+
...channel && { channel },
|
|
19993
|
+
label: res.blockedByLabel || (decidedBy === "cloud" ? CLOUD_LABEL : decidedBy === "timer" ? TIMER_LABEL : res.approved ? "Allowed" : "Blocked"),
|
|
19994
|
+
...res.reason && { reason: res.reason.slice(0, 2e3) },
|
|
19995
|
+
...res.ruleHit && { ruleName: res.ruleHit },
|
|
19996
|
+
...res.redirect && { redirect: res.redirect },
|
|
19997
|
+
decidedAt: now
|
|
19998
|
+
};
|
|
19999
|
+
}
|
|
20000
|
+
var CHANNEL_NAMES;
|
|
20001
|
+
var init_card_result = __esm({
|
|
20002
|
+
"src/auth/card-result.ts"() {
|
|
19658
20003
|
"use strict";
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
"
|
|
19662
|
-
"
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19666
|
-
...HUMAN_DECISION_SOURCES,
|
|
19667
|
-
REDIRECT_SOURCE
|
|
19668
|
-
]);
|
|
20004
|
+
init_decision_record();
|
|
20005
|
+
CHANNEL_NAMES = {
|
|
20006
|
+
terminal: "Terminal (node9 tail)",
|
|
20007
|
+
monitor: "node9 monitor",
|
|
20008
|
+
native: "system dialog window",
|
|
20009
|
+
cloud: "organization"
|
|
20010
|
+
};
|
|
19669
20011
|
}
|
|
19670
20012
|
});
|
|
19671
20013
|
|
|
@@ -20696,28 +21038,22 @@ ${packageReview}`;
|
|
|
20696
21038
|
const { signal } = abortController;
|
|
20697
21039
|
const racePromises = [];
|
|
20698
21040
|
const approvalTimeoutMs = config.settings.approvalTimeoutMs ?? 0;
|
|
20699
|
-
if (approvalTimeoutMs > 0) {
|
|
20700
|
-
racePromises.push(
|
|
20701
|
-
new Promise((resolve2, reject) => {
|
|
20702
|
-
const timer = setTimeout(() => {
|
|
20703
|
-
resolve2({
|
|
20704
|
-
approved: false,
|
|
20705
|
-
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
20706
|
-
blockedBy: "timeout",
|
|
20707
|
-
blockedByLabel: "Approval Timeout"
|
|
20708
|
-
});
|
|
20709
|
-
}, approvalTimeoutMs);
|
|
20710
|
-
signal.addEventListener("abort", () => {
|
|
20711
|
-
clearTimeout(timer);
|
|
20712
|
-
reject(new Error("Aborted"));
|
|
20713
|
-
});
|
|
20714
|
-
})
|
|
20715
|
-
);
|
|
20716
|
-
}
|
|
20717
21041
|
let viewerId = null;
|
|
20718
21042
|
const internalToken = getInternalToken();
|
|
20719
21043
|
let daemonEntryId = null;
|
|
20720
21044
|
let daemonAllowCount = 1;
|
|
21045
|
+
let cardDeadline;
|
|
21046
|
+
const cardOptions = {
|
|
21047
|
+
channels: [
|
|
21048
|
+
"terminal",
|
|
21049
|
+
"monitor",
|
|
21050
|
+
// Only when the popup can show: a card listing a channel nobody can
|
|
21051
|
+
// use is never abandoned when node9 tail closes, and waits unanswered.
|
|
21052
|
+
...approvers.native && !isManual && !options?.calledFromDaemon && (process.platform === "darwin" || process.platform === "win32" || !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY)) ? ["native"] : [],
|
|
21053
|
+
...cloudEnforced && cloudRequestId && cloudMayVote ? ["cloud"] : []
|
|
21054
|
+
],
|
|
21055
|
+
timeoutMs: approvalTimeoutMs
|
|
21056
|
+
};
|
|
20721
21057
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
20722
21058
|
if (cloudEnforced && cloudRequestId) {
|
|
20723
21059
|
const viewer = await notifyDaemonViewer(
|
|
@@ -20726,11 +21062,20 @@ ${packageReview}`;
|
|
|
20726
21062
|
meta,
|
|
20727
21063
|
riskMetadata,
|
|
20728
21064
|
options?.activityId,
|
|
20729
|
-
options?.socketActivitySent
|
|
21065
|
+
options?.socketActivitySent,
|
|
21066
|
+
{
|
|
21067
|
+
cwd: options?.cwd,
|
|
21068
|
+
recoveryCommand: statefulRecoveryCommand,
|
|
21069
|
+
sessionId: meta?.sessionId,
|
|
21070
|
+
card: cardOptions
|
|
21071
|
+
}
|
|
20730
21072
|
).catch(() => null);
|
|
20731
21073
|
viewerId = viewer?.id ?? null;
|
|
20732
21074
|
daemonEntryId = viewerId;
|
|
20733
|
-
if (viewer)
|
|
21075
|
+
if (viewer) {
|
|
21076
|
+
daemonAllowCount = viewer.allowCount;
|
|
21077
|
+
cardDeadline = viewer.deadline;
|
|
21078
|
+
}
|
|
20734
21079
|
} else {
|
|
20735
21080
|
try {
|
|
20736
21081
|
const entry = await registerDaemonEntry(
|
|
@@ -20754,14 +21099,35 @@ ${packageReview}`;
|
|
|
20754
21099
|
void 0,
|
|
20755
21100
|
localSmartRuleMatched || options?.localSmartRuleMatched,
|
|
20756
21101
|
options?.socketActivitySent,
|
|
20757
|
-
meta?.sessionId
|
|
21102
|
+
meta?.sessionId,
|
|
21103
|
+
cardOptions
|
|
20758
21104
|
);
|
|
20759
21105
|
daemonEntryId = entry.id;
|
|
20760
21106
|
daemonAllowCount = entry.allowCount;
|
|
21107
|
+
cardDeadline = entry.deadline;
|
|
20761
21108
|
} catch {
|
|
20762
21109
|
}
|
|
20763
21110
|
}
|
|
20764
21111
|
}
|
|
21112
|
+
if (approvalTimeoutMs > 0 && cardDeadline === void 0) {
|
|
21113
|
+
racePromises.push(
|
|
21114
|
+
new Promise((resolve2, reject) => {
|
|
21115
|
+
const timer = setTimeout(() => {
|
|
21116
|
+
resolve2({
|
|
21117
|
+
approved: false,
|
|
21118
|
+
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
21119
|
+
blockedBy: "timeout",
|
|
21120
|
+
blockedByLabel: "Approval Timeout",
|
|
21121
|
+
decidedBy: "timer"
|
|
21122
|
+
});
|
|
21123
|
+
}, approvalTimeoutMs);
|
|
21124
|
+
signal.addEventListener("abort", () => {
|
|
21125
|
+
clearTimeout(timer);
|
|
21126
|
+
reject(new Error("Aborted"));
|
|
21127
|
+
});
|
|
21128
|
+
})
|
|
21129
|
+
);
|
|
21130
|
+
}
|
|
20765
21131
|
if (cloudEnforced && cloudRequestId && cloudMayVote) {
|
|
20766
21132
|
racePromises.push(
|
|
20767
21133
|
(async () => {
|
|
@@ -20772,7 +21138,9 @@ ${packageReview}`;
|
|
|
20772
21138
|
reason: cloudResult.approved ? void 0 : cloudResult.reason || "Action rejected by organization administrator via Slack.",
|
|
20773
21139
|
checkedBy: cloudResult.approved ? "cloud" : void 0,
|
|
20774
21140
|
blockedBy: cloudResult.approved ? void 0 : "team-policy",
|
|
20775
|
-
blockedByLabel: "Organization Policy (SaaS)"
|
|
21141
|
+
blockedByLabel: "Organization Policy (SaaS)",
|
|
21142
|
+
decidedBy: "cloud",
|
|
21143
|
+
channel: "cloud"
|
|
20776
21144
|
};
|
|
20777
21145
|
} catch (err2) {
|
|
20778
21146
|
const error = err2;
|
|
@@ -20802,14 +21170,31 @@ ${packageReview}`;
|
|
|
20802
21170
|
if (config.policySource !== "workspace") {
|
|
20803
21171
|
for (const d of exposureDestinations) addTrustedRecipient(d, "approval");
|
|
20804
21172
|
}
|
|
20805
|
-
return {
|
|
21173
|
+
return {
|
|
21174
|
+
approved: true,
|
|
21175
|
+
checkedBy: "daemon",
|
|
21176
|
+
decisionSource: "native",
|
|
21177
|
+
decidedBy: "person",
|
|
21178
|
+
channel: "native"
|
|
21179
|
+
};
|
|
20806
21180
|
}
|
|
20807
21181
|
if (decision === "always_allow") {
|
|
20808
21182
|
if (standingTrustApplies()) {
|
|
20809
21183
|
writeTrustSession(toolName, 36e5, args);
|
|
20810
|
-
return {
|
|
21184
|
+
return {
|
|
21185
|
+
approved: true,
|
|
21186
|
+
checkedBy: "trust",
|
|
21187
|
+
decidedBy: "person",
|
|
21188
|
+
channel: "native"
|
|
21189
|
+
};
|
|
20811
21190
|
}
|
|
20812
|
-
return {
|
|
21191
|
+
return {
|
|
21192
|
+
approved: true,
|
|
21193
|
+
checkedBy: "daemon",
|
|
21194
|
+
decisionSource: "native",
|
|
21195
|
+
decidedBy: "person",
|
|
21196
|
+
channel: "native"
|
|
21197
|
+
};
|
|
20813
21198
|
}
|
|
20814
21199
|
const isApproved = decision === "allow";
|
|
20815
21200
|
return {
|
|
@@ -20818,75 +21203,32 @@ ${packageReview}`;
|
|
|
20818
21203
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
20819
21204
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
20820
21205
|
blockedByLabel: "User Decision (Native)",
|
|
20821
|
-
decisionSource: "native"
|
|
21206
|
+
decisionSource: "native",
|
|
21207
|
+
decidedBy: "person",
|
|
21208
|
+
channel: "native"
|
|
20822
21209
|
};
|
|
20823
21210
|
})()
|
|
20824
21211
|
);
|
|
20825
21212
|
}
|
|
21213
|
+
const fromCard = /* @__PURE__ */ new WeakSet();
|
|
21214
|
+
const timeoutReason = approvalTimeoutMs > 0 ? approvalTimeoutReason(approvalTimeoutMs) : void 0;
|
|
20826
21215
|
if (daemonEntryId && approvers.terminal) {
|
|
20827
21216
|
racePromises.push(
|
|
20828
21217
|
(async () => {
|
|
20829
|
-
const
|
|
20830
|
-
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
20837
|
-
|
|
20838
|
-
if (
|
|
20839
|
-
|
|
20840
|
-
const
|
|
20841
|
-
|
|
20842
|
-
|
|
20843
|
-
return {
|
|
20844
|
-
approved: false,
|
|
20845
|
-
reason: daemonReason || "Action rejected by organization administrator via Slack.",
|
|
20846
|
-
blockedBy: "team-policy",
|
|
20847
|
-
blockedByLabel: "Organization Policy (SaaS)",
|
|
20848
|
-
decisionSource: "cloud"
|
|
20849
|
-
};
|
|
20850
|
-
const timedOut = decisionSource === "timeout";
|
|
20851
|
-
if (downgradedBlock)
|
|
20852
|
-
return {
|
|
20853
|
-
...downgradedBlock,
|
|
20854
|
-
blockedBy: timedOut ? "timeout" : "local-config"
|
|
20855
|
-
};
|
|
20856
|
-
if (timedOut)
|
|
20857
|
-
return {
|
|
20858
|
-
approved: false,
|
|
20859
|
-
reason: daemonReason || "No response: auto-denied after the approval timeout.",
|
|
20860
|
-
blockedBy: "timeout",
|
|
20861
|
-
blockedByLabel: "Approval Timeout"
|
|
20862
|
-
};
|
|
20863
|
-
if (decisionSource === "policy")
|
|
20864
|
-
return {
|
|
20865
|
-
approved: false,
|
|
20866
|
-
reason: daemonReason || "Action blocked by policy.",
|
|
20867
|
-
blockedBy: "local-config",
|
|
20868
|
-
blockedByLabel: explainableLabel
|
|
20869
|
-
};
|
|
20870
|
-
return {
|
|
20871
|
-
approved: false,
|
|
20872
|
-
reason: daemonReason || "node9 could not get an approval decision for this call.",
|
|
20873
|
-
blockedBy: "no-approval-mechanism",
|
|
20874
|
-
blockedByLabel: "Approval Unavailable"
|
|
20875
|
-
};
|
|
20876
|
-
}
|
|
20877
|
-
if (isApproved && decisionSource === "cloud")
|
|
20878
|
-
return { approved: true, checkedBy: "cloud", decisionSource: "cloud" };
|
|
20879
|
-
if (isApproved && decisionSource === "policy")
|
|
20880
|
-
return { approved: true, checkedBy: "local-policy" };
|
|
20881
|
-
const via = "Terminal (node9 tail)";
|
|
20882
|
-
return {
|
|
20883
|
-
approved: isApproved,
|
|
20884
|
-
reason: isApproved ? void 0 : isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`,
|
|
20885
|
-
checkedBy: isApproved ? "daemon" : void 0,
|
|
20886
|
-
blockedBy: isApproved ? void 0 : "local-decision",
|
|
20887
|
-
blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
|
|
20888
|
-
decisionSource: "terminal"
|
|
20889
|
-
};
|
|
21218
|
+
const cap = cardDeadline === void 0 ? daemonWaitCapMs(config.settings.approvalTimeoutMs) : cardDeadline === null ? 0 : Math.max(cardDeadline - Date.now(), 0) + 5e3;
|
|
21219
|
+
const outcome = await waitForDaemonDecision(daemonEntryId, signal, cap);
|
|
21220
|
+
const legacy = !outcome.record;
|
|
21221
|
+
const record2 = outcome.record ?? recordFromLegacyWait(outcome);
|
|
21222
|
+
if (downgradedBlock && (!record2 || legacy && record2.decidedBy === "policy" && record2.decision === "deny")) {
|
|
21223
|
+
const softened = { ...downgradedBlock, blockedBy: "local-config" };
|
|
21224
|
+
fromCard.add(softened);
|
|
21225
|
+
return softened;
|
|
21226
|
+
}
|
|
21227
|
+
if (!record2) return approvalUnavailable(outcome.reason);
|
|
21228
|
+
if (legacy && record2.decidedBy === "policy") record2.label = explainableLabel;
|
|
21229
|
+
const result = authResultFromRecord(record2, { downgradedBlock, timeoutReason });
|
|
21230
|
+
fromCard.add(result);
|
|
21231
|
+
return result;
|
|
20890
21232
|
})()
|
|
20891
21233
|
);
|
|
20892
21234
|
}
|
|
@@ -20918,7 +21260,7 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20918
21260
|
blockedByLabel: explainableLabel
|
|
20919
21261
|
};
|
|
20920
21262
|
}
|
|
20921
|
-
const
|
|
21263
|
+
const raceResult = await new Promise((resolve2) => {
|
|
20922
21264
|
let resolved = false;
|
|
20923
21265
|
let failures = 0;
|
|
20924
21266
|
const total = racePromises.length;
|
|
@@ -20926,14 +21268,6 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20926
21268
|
if (!resolved) {
|
|
20927
21269
|
resolved = true;
|
|
20928
21270
|
abortController.abort();
|
|
20929
|
-
if (daemonEntryId && internalToken) {
|
|
20930
|
-
resolveViaDaemon(
|
|
20931
|
-
daemonEntryId,
|
|
20932
|
-
res.approved ? "allow" : "deny",
|
|
20933
|
-
internalToken,
|
|
20934
|
-
res.decisionSource
|
|
20935
|
-
).catch(() => null);
|
|
20936
|
-
}
|
|
20937
21271
|
resolve2(res);
|
|
20938
21272
|
}
|
|
20939
21273
|
};
|
|
@@ -20941,26 +21275,29 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20941
21275
|
p.then(finish).catch((err2) => {
|
|
20942
21276
|
if (err2.name === "AbortError" || err2.message?.includes("canceled") || err2.message?.includes("Aborted"))
|
|
20943
21277
|
return;
|
|
20944
|
-
if (err2.message === "Abandoned") {
|
|
20945
|
-
finish({
|
|
20946
|
-
approved: false,
|
|
20947
|
-
reason: "Browser dashboard closed without making a decision.",
|
|
20948
|
-
blockedBy: "local-decision",
|
|
20949
|
-
blockedByLabel: "Browser Dashboard (Abandoned)"
|
|
20950
|
-
});
|
|
20951
|
-
return;
|
|
20952
|
-
}
|
|
20953
21278
|
failures++;
|
|
20954
21279
|
if (failures === total && !resolved) {
|
|
20955
|
-
finish(
|
|
21280
|
+
finish(approvalUnavailable("All approval channels failed or disconnected."));
|
|
20956
21281
|
}
|
|
20957
21282
|
});
|
|
20958
21283
|
}
|
|
20959
21284
|
});
|
|
21285
|
+
let finalResult = raceResult;
|
|
21286
|
+
if (daemonEntryId && internalToken && !fromCard.has(raceResult)) {
|
|
21287
|
+
const reply = await resolveViaDaemon(
|
|
21288
|
+
daemonEntryId,
|
|
21289
|
+
raceResult.approved ? "allow" : "deny",
|
|
21290
|
+
internalToken,
|
|
21291
|
+
raceResult.decisionSource,
|
|
21292
|
+
recordFromAuthResult(raceResult)
|
|
21293
|
+
).catch(() => null);
|
|
21294
|
+
if (reply && !reply.won)
|
|
21295
|
+
finalResult = authResultFromRecord(reply.record, { downgradedBlock, timeoutReason });
|
|
21296
|
+
}
|
|
20960
21297
|
if (finalResult.approved && exposureKeys.length > 0 && meta?.sessionId && ucPolicy?.afterExposure === "ask-once") {
|
|
20961
21298
|
await approveSessionExposure(meta.sessionId, exposureKeys);
|
|
20962
21299
|
}
|
|
20963
|
-
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
|
|
21300
|
+
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud" && finalResult.decidedBy !== "cloud") {
|
|
20964
21301
|
await resolveNode9SaaS(
|
|
20965
21302
|
cloudRequestId,
|
|
20966
21303
|
creds,
|
|
@@ -20984,7 +21321,12 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20984
21321
|
{
|
|
20985
21322
|
...ownRowMeta(finalResult.approved),
|
|
20986
21323
|
...cloudRequestId && { cloudRequestId },
|
|
20987
|
-
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` }
|
|
21324
|
+
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` },
|
|
21325
|
+
// Who decided, from the decision record, and the card: the daemon
|
|
21326
|
+
// writes its own row for the same card, and the report counts one.
|
|
21327
|
+
...finalResult.decidedBy && { decidedBy: finalResult.decidedBy },
|
|
21328
|
+
...finalResult.channel && { channel: finalResult.channel },
|
|
21329
|
+
...daemonEntryId && { cardId: daemonEntryId }
|
|
20988
21330
|
},
|
|
20989
21331
|
hashAuditArgs
|
|
20990
21332
|
);
|
|
@@ -21036,7 +21378,8 @@ var init_orchestrator = __esm({
|
|
|
21036
21378
|
init_jail();
|
|
21037
21379
|
init_safe_text();
|
|
21038
21380
|
init_negotiation();
|
|
21039
|
-
|
|
21381
|
+
init_decision_record();
|
|
21382
|
+
init_card_result();
|
|
21040
21383
|
SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
|
|
21041
21384
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
21042
21385
|
"write",
|
|
@@ -30673,12 +31016,206 @@ var init_session_history = __esm({
|
|
|
30673
31016
|
}
|
|
30674
31017
|
});
|
|
30675
31018
|
|
|
31019
|
+
// src/daemon/cards.ts
|
|
31020
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
31021
|
+
var CARD_RETENTION_MS, MAX_CARD_TIMEOUT_MS, CardStore;
|
|
31022
|
+
var init_cards = __esm({
|
|
31023
|
+
"src/daemon/cards.ts"() {
|
|
31024
|
+
"use strict";
|
|
31025
|
+
init_decision_record();
|
|
31026
|
+
CARD_RETENTION_MS = 6e4;
|
|
31027
|
+
MAX_CARD_TIMEOUT_MS = 24 * 60 * 6e4;
|
|
31028
|
+
CardStore = class {
|
|
31029
|
+
slots = /* @__PURE__ */ new Map();
|
|
31030
|
+
now;
|
|
31031
|
+
setTimer;
|
|
31032
|
+
clearTimer;
|
|
31033
|
+
deps;
|
|
31034
|
+
constructor(deps = {}) {
|
|
31035
|
+
this.deps = deps;
|
|
31036
|
+
this.now = deps.now ?? Date.now;
|
|
31037
|
+
this.setTimer = deps.setTimer ?? ((fn, ms) => {
|
|
31038
|
+
const t = setTimeout(fn, ms);
|
|
31039
|
+
t.unref?.();
|
|
31040
|
+
return t;
|
|
31041
|
+
});
|
|
31042
|
+
this.clearTimer = deps.clearTimer ?? ((h) => clearTimeout(h));
|
|
31043
|
+
}
|
|
31044
|
+
register(request2, opts) {
|
|
31045
|
+
const id = opts.id && !this.slots.has(opts.id) ? opts.id : randomUUID5();
|
|
31046
|
+
const createdAt = this.now();
|
|
31047
|
+
const timeoutMs = opts.timeoutMs && opts.timeoutMs > 0 ? Math.min(opts.timeoutMs, MAX_CARD_TIMEOUT_MS) : null;
|
|
31048
|
+
const card = {
|
|
31049
|
+
id,
|
|
31050
|
+
kind: opts.kind,
|
|
31051
|
+
state: "open",
|
|
31052
|
+
request: request2,
|
|
31053
|
+
channels: [...opts.channels],
|
|
31054
|
+
createdAt,
|
|
31055
|
+
deadline: timeoutMs === null ? null : createdAt + timeoutMs
|
|
31056
|
+
};
|
|
31057
|
+
const slot = { card, waiters: /* @__PURE__ */ new Set(), everWaited: false };
|
|
31058
|
+
if (timeoutMs !== null) {
|
|
31059
|
+
slot.timer = this.setTimer(() => {
|
|
31060
|
+
slot.timer = void 0;
|
|
31061
|
+
this.decide(id, timerRecord(timeoutMs, this.now()));
|
|
31062
|
+
}, timeoutMs);
|
|
31063
|
+
}
|
|
31064
|
+
this.slots.set(id, slot);
|
|
31065
|
+
return card;
|
|
31066
|
+
}
|
|
31067
|
+
get(id) {
|
|
31068
|
+
return this.slots.get(id)?.card;
|
|
31069
|
+
}
|
|
31070
|
+
/** Cards still waiting for a decision, oldest first. */
|
|
31071
|
+
open() {
|
|
31072
|
+
return [...this.slots.values()].filter((s) => s.card.state === "open").map((s) => s.card);
|
|
31073
|
+
}
|
|
31074
|
+
/**
|
|
31075
|
+
* The ONLY way a card gets a decision. The first call wins; a later call
|
|
31076
|
+
* gets the winning record back with `won: false`. A withdrawn card can
|
|
31077
|
+
* still be decided (its asker, an older hook, may report its own winner
|
|
31078
|
+
* after closing its wait). Null when the card does not exist.
|
|
31079
|
+
*/
|
|
31080
|
+
decide(id, record2) {
|
|
31081
|
+
const slot = this.slots.get(id);
|
|
31082
|
+
if (!slot) return null;
|
|
31083
|
+
const { card } = slot;
|
|
31084
|
+
if (card.state === "decided") return { record: card.record, won: false };
|
|
31085
|
+
if (slot.timer !== void 0) {
|
|
31086
|
+
this.clearTimer(slot.timer);
|
|
31087
|
+
slot.timer = void 0;
|
|
31088
|
+
}
|
|
31089
|
+
card.state = "decided";
|
|
31090
|
+
card.record = record2;
|
|
31091
|
+
card.closedAt = this.now();
|
|
31092
|
+
this.deps.onDecided?.(card);
|
|
31093
|
+
if (slot.waiters.size > 0) {
|
|
31094
|
+
const waiters = [...slot.waiters];
|
|
31095
|
+
slot.waiters.clear();
|
|
31096
|
+
for (const w of waiters) w({ kind: "decided", record: record2 });
|
|
31097
|
+
this.slots.delete(id);
|
|
31098
|
+
}
|
|
31099
|
+
return { record: record2, won: true };
|
|
31100
|
+
}
|
|
31101
|
+
/** The asker left before any decision. False when the card was not open. */
|
|
31102
|
+
withdraw(id) {
|
|
31103
|
+
const slot = this.slots.get(id);
|
|
31104
|
+
if (!slot || slot.card.state !== "open") return false;
|
|
31105
|
+
if (slot.timer !== void 0) {
|
|
31106
|
+
this.clearTimer(slot.timer);
|
|
31107
|
+
slot.timer = void 0;
|
|
31108
|
+
}
|
|
31109
|
+
slot.card.state = "withdrawn";
|
|
31110
|
+
slot.card.closedAt = this.now();
|
|
31111
|
+
const waiters = [...slot.waiters];
|
|
31112
|
+
slot.waiters.clear();
|
|
31113
|
+
for (const w of waiters) w({ kind: "withdrawn" });
|
|
31114
|
+
this.deps.onWithdrawn?.(slot.card);
|
|
31115
|
+
return true;
|
|
31116
|
+
}
|
|
31117
|
+
/**
|
|
31118
|
+
* Wait for the card's decision. Any number of waiters. A decided card
|
|
31119
|
+
* answers at once, and is removed once read. When the last waiter of an
|
|
31120
|
+
* open card goes away (its signal aborts), the card is withdrawn: nobody is
|
|
31121
|
+
* left to act on a decision.
|
|
31122
|
+
*/
|
|
31123
|
+
wait(id, signal) {
|
|
31124
|
+
const slot = this.slots.get(id);
|
|
31125
|
+
if (!slot) return Promise.resolve({ kind: "missing" });
|
|
31126
|
+
const { card } = slot;
|
|
31127
|
+
if (card.state === "decided") {
|
|
31128
|
+
this.slots.delete(id);
|
|
31129
|
+
return Promise.resolve({ kind: "decided", record: card.record });
|
|
31130
|
+
}
|
|
31131
|
+
if (card.state === "withdrawn") return Promise.resolve({ kind: "withdrawn" });
|
|
31132
|
+
slot.everWaited = true;
|
|
31133
|
+
return new Promise((resolve2) => {
|
|
31134
|
+
const waiter = (o) => {
|
|
31135
|
+
signal?.removeEventListener("abort", onAbort);
|
|
31136
|
+
resolve2(o);
|
|
31137
|
+
};
|
|
31138
|
+
const onAbort = () => {
|
|
31139
|
+
if (!slot.waiters.delete(waiter)) return;
|
|
31140
|
+
resolve2({ kind: "withdrawn" });
|
|
31141
|
+
if (slot.waiters.size === 0 && card.state === "open") this.withdraw(id);
|
|
31142
|
+
};
|
|
31143
|
+
slot.waiters.add(waiter);
|
|
31144
|
+
if (signal) {
|
|
31145
|
+
if (signal.aborted) onAbort();
|
|
31146
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
31147
|
+
}
|
|
31148
|
+
});
|
|
31149
|
+
}
|
|
31150
|
+
/**
|
|
31151
|
+
* The daemon's local UIs all went away. Open cards that only they could
|
|
31152
|
+
* answer are decided as abandoned; a card that Slack or the native popup
|
|
31153
|
+
* can still answer stays open. Returns how many were abandoned.
|
|
31154
|
+
*/
|
|
31155
|
+
abandonLocalOnly() {
|
|
31156
|
+
let n = 0;
|
|
31157
|
+
for (const card of this.open()) {
|
|
31158
|
+
const localOnly = card.channels.length > 0 && card.channels.every((c) => LOCAL_UI_CHANNELS.has(c));
|
|
31159
|
+
if (!localOnly) continue;
|
|
31160
|
+
if (this.decide(card.id, abandonedRecord(this.now()))?.won) n++;
|
|
31161
|
+
}
|
|
31162
|
+
return n;
|
|
31163
|
+
}
|
|
31164
|
+
/**
|
|
31165
|
+
* Housekeeping, run every few seconds:
|
|
31166
|
+
* - a decided card nobody read and a withdrawn card are removed after the
|
|
31167
|
+
* retention;
|
|
31168
|
+
* - an asked card that nobody ever waited on (its asker died between
|
|
31169
|
+
* registering and waiting) is withdrawn after the retention.
|
|
31170
|
+
*/
|
|
31171
|
+
sweep() {
|
|
31172
|
+
const now = this.now();
|
|
31173
|
+
for (const [id, slot] of this.slots) {
|
|
31174
|
+
const { card } = slot;
|
|
31175
|
+
if (card.state !== "open") {
|
|
31176
|
+
if (now - (card.closedAt ?? card.createdAt) >= CARD_RETENTION_MS) this.slots.delete(id);
|
|
31177
|
+
continue;
|
|
31178
|
+
}
|
|
31179
|
+
if (card.kind !== "discovery" && !slot.everWaited && now - card.createdAt >= CARD_RETENTION_MS)
|
|
31180
|
+
this.withdraw(id);
|
|
31181
|
+
}
|
|
31182
|
+
}
|
|
31183
|
+
/** Cards in any state (tests and diagnostics). */
|
|
31184
|
+
size() {
|
|
31185
|
+
return this.slots.size;
|
|
31186
|
+
}
|
|
31187
|
+
};
|
|
31188
|
+
}
|
|
31189
|
+
});
|
|
31190
|
+
|
|
30676
31191
|
// src/daemon/state.ts
|
|
30677
31192
|
import net2 from "net";
|
|
30678
31193
|
import fs49 from "fs";
|
|
30679
31194
|
import path49 from "path";
|
|
30680
31195
|
import os40 from "os";
|
|
30681
|
-
import { randomUUID as
|
|
31196
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
31197
|
+
function onCardDecided(card) {
|
|
31198
|
+
const r = card.record;
|
|
31199
|
+
broadcast("remove", { id: card.id, decision: r.decision, decidedBy: r.decidedBy });
|
|
31200
|
+
if (card.kind === "discovery") return;
|
|
31201
|
+
appendAuditLog({
|
|
31202
|
+
toolName: card.request.toolName,
|
|
31203
|
+
args: card.request.args,
|
|
31204
|
+
decision: r.decidedBy === "timer" ? "auto-deny" : r.standing === "trust" && r.trustDuration ? `trust:${r.trustDuration}` : r.decision,
|
|
31205
|
+
decidedBy: r.decidedBy,
|
|
31206
|
+
...r.channel && { channel: r.channel },
|
|
31207
|
+
cardId: card.id
|
|
31208
|
+
});
|
|
31209
|
+
if (r.decidedBy !== "person") return;
|
|
31210
|
+
const tool = card.request.toolName;
|
|
31211
|
+
if (r.decision === "allow" && r.standing !== "always-allow" && r.standing !== "trust") {
|
|
31212
|
+
insightCounts.set(tool, (insightCounts.get(tool) ?? 0) + 1);
|
|
31213
|
+
saveInsightCounts();
|
|
31214
|
+
} else if (r.decision === "deny") {
|
|
31215
|
+
insightCounts.delete(tool);
|
|
31216
|
+
saveInsightCounts();
|
|
31217
|
+
}
|
|
31218
|
+
}
|
|
30682
31219
|
function loadInsightCounts() {
|
|
30683
31220
|
try {
|
|
30684
31221
|
if (!fs49.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
@@ -30745,7 +31282,10 @@ function appendAuditLog(data) {
|
|
|
30745
31282
|
tool: data.toolName,
|
|
30746
31283
|
args: maskPersonalArgs(redactArgs(data.args)),
|
|
30747
31284
|
decision: data.decision,
|
|
30748
|
-
source: "daemon"
|
|
31285
|
+
source: "daemon",
|
|
31286
|
+
...data.decidedBy && { decidedBy: data.decidedBy },
|
|
31287
|
+
...data.channel && { channel: data.channel },
|
|
31288
|
+
...data.cardId && { cardId: data.cardId }
|
|
30749
31289
|
};
|
|
30750
31290
|
const dir2 = path49.dirname(AUDIT_LOG_FILE);
|
|
30751
31291
|
if (!fs49.existsSync(dir2)) fs49.mkdirSync(dir2, { recursive: true });
|
|
@@ -30878,7 +31418,7 @@ function broadcastForensic(finding) {
|
|
|
30878
31418
|
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
30879
31419
|
const event = {
|
|
30880
31420
|
type: "forensic",
|
|
30881
|
-
id: `fnd_${
|
|
31421
|
+
id: `fnd_${randomUUID6()}`,
|
|
30882
31422
|
ts: Date.now(),
|
|
30883
31423
|
sessionId: finding.sessionId,
|
|
30884
31424
|
category: finding.type,
|
|
@@ -30889,14 +31429,8 @@ function broadcastForensic(finding) {
|
|
|
30889
31429
|
}
|
|
30890
31430
|
function abandonPending() {
|
|
30891
31431
|
setAbandonTimer(null);
|
|
30892
|
-
|
|
30893
|
-
|
|
30894
|
-
if (entry.waiter) entry.waiter("abandoned");
|
|
30895
|
-
else entry.earlyDecision = "abandoned";
|
|
30896
|
-
pending.delete(id);
|
|
30897
|
-
broadcast("remove", { id });
|
|
30898
|
-
});
|
|
30899
|
-
if (autoStarted) {
|
|
31432
|
+
cards.abandonLocalOnly();
|
|
31433
|
+
if (autoStarted && cards.open().length === 0) {
|
|
30900
31434
|
try {
|
|
30901
31435
|
fs49.unlinkSync(DAEMON_PID_FILE);
|
|
30902
31436
|
} catch {
|
|
@@ -31060,7 +31594,7 @@ function bindActivitySocket() {
|
|
|
31060
31594
|
});
|
|
31061
31595
|
activitySocketServer = unixServer;
|
|
31062
31596
|
}
|
|
31063
|
-
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE,
|
|
31597
|
+
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, sseClients, cards, suggestionTracker, taintStore, sessionTaintStore, sessionExposureStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, CRITICAL_FORENSIC_CATEGORIES, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
31064
31598
|
var init_state2 = __esm({
|
|
31065
31599
|
"src/daemon/state.ts"() {
|
|
31066
31600
|
"use strict";
|
|
@@ -31073,6 +31607,7 @@ var init_state2 = __esm({
|
|
|
31073
31607
|
init_session_counters();
|
|
31074
31608
|
init_session_history();
|
|
31075
31609
|
init_atomic_write();
|
|
31610
|
+
init_cards();
|
|
31076
31611
|
homeDir = os40.homedir();
|
|
31077
31612
|
DAEMON_PID_FILE = path49.join(homeDir, ".node9", "daemon.pid");
|
|
31078
31613
|
DECISIONS_FILE = path49.join(homeDir, ".node9", "decisions.json");
|
|
@@ -31081,8 +31616,11 @@ var init_state2 = __esm({
|
|
|
31081
31616
|
GLOBAL_CONFIG_FILE = path49.join(homeDir, ".node9", "config.json");
|
|
31082
31617
|
CREDENTIALS_FILE = path49.join(homeDir, ".node9", "credentials.json");
|
|
31083
31618
|
INSIGHT_COUNTS_FILE = path49.join(homeDir, ".node9", "insight-counts.json");
|
|
31084
|
-
pending = /* @__PURE__ */ new Map();
|
|
31085
31619
|
sseClients = /* @__PURE__ */ new Set();
|
|
31620
|
+
cards = new CardStore({
|
|
31621
|
+
onDecided: onCardDecided,
|
|
31622
|
+
onWithdrawn: (card) => broadcast("remove", { id: card.id })
|
|
31623
|
+
});
|
|
31086
31624
|
suggestionTracker = new SuggestionTracker(3);
|
|
31087
31625
|
taintStore = new TaintStore();
|
|
31088
31626
|
sessionTaintStore = new SessionTaintStore();
|
|
@@ -32395,7 +32933,7 @@ var init_posture = __esm({
|
|
|
32395
32933
|
});
|
|
32396
32934
|
|
|
32397
32935
|
// src/posture/ship.ts
|
|
32398
|
-
import
|
|
32936
|
+
import http2 from "http";
|
|
32399
32937
|
import https2 from "https";
|
|
32400
32938
|
import { URL as URL2 } from "url";
|
|
32401
32939
|
function buildShipBody(result) {
|
|
@@ -32436,7 +32974,7 @@ async function shipPosture(result, creds) {
|
|
|
32436
32974
|
if (!url) return false;
|
|
32437
32975
|
const body = JSON.stringify(buildShipBody(result));
|
|
32438
32976
|
const parsed = new URL2(url);
|
|
32439
|
-
const transport = parsed.protocol === "http:" ?
|
|
32977
|
+
const transport = parsed.protocol === "http:" ? http2 : https2;
|
|
32440
32978
|
return new Promise((resolve2) => {
|
|
32441
32979
|
const req = transport.request(
|
|
32442
32980
|
{
|
|
@@ -32635,7 +33173,7 @@ var init_mcp_status = __esm({
|
|
|
32635
33173
|
});
|
|
32636
33174
|
|
|
32637
33175
|
// src/policy-snapshot/ship.ts
|
|
32638
|
-
import
|
|
33176
|
+
import http3 from "http";
|
|
32639
33177
|
import https3 from "https";
|
|
32640
33178
|
import { URL as URL3 } from "url";
|
|
32641
33179
|
function policySnapshotUrlFrom(apiUrl) {
|
|
@@ -32646,7 +33184,7 @@ async function shipPolicySnapshot(body, creds) {
|
|
|
32646
33184
|
if (!url) return false;
|
|
32647
33185
|
const payload = JSON.stringify(body);
|
|
32648
33186
|
const parsed = new URL3(url);
|
|
32649
|
-
const transport = parsed.protocol === "http:" ?
|
|
33187
|
+
const transport = parsed.protocol === "http:" ? http3 : https3;
|
|
32650
33188
|
return new Promise((resolve2) => {
|
|
32651
33189
|
const req = transport.request(
|
|
32652
33190
|
{
|
|
@@ -32714,17 +33252,17 @@ var init_machine_id = __esm({
|
|
|
32714
33252
|
// src/daemon/sync-trigger.ts
|
|
32715
33253
|
function createSyncTrigger(sync) {
|
|
32716
33254
|
let running2;
|
|
32717
|
-
let
|
|
33255
|
+
let pending = false;
|
|
32718
33256
|
return (invalidate = true) => {
|
|
32719
33257
|
if (running2) {
|
|
32720
|
-
if (invalidate)
|
|
33258
|
+
if (invalidate) pending = true;
|
|
32721
33259
|
return running2;
|
|
32722
33260
|
}
|
|
32723
33261
|
running2 = (async () => {
|
|
32724
33262
|
do {
|
|
32725
|
-
|
|
33263
|
+
pending = false;
|
|
32726
33264
|
await sync();
|
|
32727
|
-
} while (
|
|
33265
|
+
} while (pending);
|
|
32728
33266
|
})().finally(() => {
|
|
32729
33267
|
running2 = void 0;
|
|
32730
33268
|
});
|
|
@@ -33632,7 +34170,11 @@ function buildWireRows(chunk2) {
|
|
|
33632
34170
|
...typeof parsed.taintFromEid === "string" ? { taintFromEid: parsed.taintFromEid } : {},
|
|
33633
34171
|
...typeof parsed.taintSource === "string" ? { taintSource: parsed.taintSource } : {},
|
|
33634
34172
|
...exposureOf(parsed.exposure) ? { exposure: exposureOf(parsed.exposure) } : {},
|
|
33635
|
-
...typeof parsed.exposureFromEid === "string" ? { exposureFromEid: parsed.exposureFromEid } : {}
|
|
34173
|
+
...typeof parsed.exposureFromEid === "string" ? { exposureFromEid: parsed.exposureFromEid } : {},
|
|
34174
|
+
// Approval cards. Only the known values: the ingest's schema is a
|
|
34175
|
+
// closed vocabulary and rejects the whole batch for anything else.
|
|
34176
|
+
...typeof parsed.decidedBy === "string" && DECIDED_BY_VALUES.has(parsed.decidedBy) ? { decidedBy: parsed.decidedBy } : {},
|
|
34177
|
+
...typeof parsed.channel === "string" && CHANNELS.includes(parsed.channel) ? { channel: parsed.channel } : {}
|
|
33636
34178
|
});
|
|
33637
34179
|
}
|
|
33638
34180
|
return { rows, consumed: lastNl + 1 };
|
|
@@ -33784,15 +34326,23 @@ function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHI
|
|
|
33784
34326
|
};
|
|
33785
34327
|
}
|
|
33786
34328
|
}
|
|
33787
|
-
var AUDIT_SHIP_WATERMARK, DEFAULT_INTERVAL_MS, MAX_BATCH, MAX_CHUNK_BYTES, MAX_CHUNKS_PER_TICK, FETCH_TIMEOUT_MS, SKIP_CHECKED_BY, shipperStarted;
|
|
34329
|
+
var DECIDED_BY_VALUES, AUDIT_SHIP_WATERMARK, DEFAULT_INTERVAL_MS, MAX_BATCH, MAX_CHUNK_BYTES, MAX_CHUNKS_PER_TICK, FETCH_TIMEOUT_MS, SKIP_CHECKED_BY, shipperStarted;
|
|
33788
34330
|
var init_audit_shipper = __esm({
|
|
33789
34331
|
"src/daemon/audit-shipper.ts"() {
|
|
33790
34332
|
"use strict";
|
|
33791
34333
|
init_audit();
|
|
34334
|
+
init_decision_record();
|
|
33792
34335
|
init_config();
|
|
33793
34336
|
init_sync();
|
|
33794
34337
|
init_cloud();
|
|
33795
34338
|
init_dist();
|
|
34339
|
+
DECIDED_BY_VALUES = /* @__PURE__ */ new Set([
|
|
34340
|
+
"person",
|
|
34341
|
+
"timer",
|
|
34342
|
+
"cloud",
|
|
34343
|
+
"policy",
|
|
34344
|
+
"abandoned"
|
|
34345
|
+
]);
|
|
33796
34346
|
AUDIT_SHIP_WATERMARK = path55.join(os47.homedir(), ".node9", "audit-ship.json");
|
|
33797
34347
|
DEFAULT_INTERVAL_MS = 2e4;
|
|
33798
34348
|
MAX_BATCH = 500;
|
|
@@ -33817,6 +34367,18 @@ function classifyDecision(a, b) {
|
|
|
33817
34367
|
}
|
|
33818
34368
|
if (d === "dlp") return { outcome: "info", label: "Finding", raw };
|
|
33819
34369
|
if (d === "mcp-discovered") return { outcome: "info", label: "Info", raw };
|
|
34370
|
+
const decidedBy = isRow ? a.decidedBy : void 0;
|
|
34371
|
+
if (typeof decidedBy === "string") {
|
|
34372
|
+
const allowed = d === "allow" || d === "allowed" || d.startsWith("trust:");
|
|
34373
|
+
const denied = d === "deny" || d === "auto-deny" || d === "block";
|
|
34374
|
+
if (allowed || denied) {
|
|
34375
|
+
if (decidedBy === "person")
|
|
34376
|
+
return allowed ? { outcome: "allow", label: "Approved", raw } : { outcome: "deny", label: "Denied", raw };
|
|
34377
|
+
if (decidedBy === "timer" || decidedBy === "abandoned")
|
|
34378
|
+
return { outcome: "deny", label: "Timed out", raw };
|
|
34379
|
+
return allowed ? { outcome: "allow", label: "Auto-allowed", raw } : { outcome: "deny", label: "Blocked", raw };
|
|
34380
|
+
}
|
|
34381
|
+
}
|
|
33820
34382
|
if (d === "allow" || d === "allowed") {
|
|
33821
34383
|
if (src === "post-hook") return { outcome: "allow", label: "Ran", raw };
|
|
33822
34384
|
if (HUMAN_SOURCES.has(src)) {
|
|
@@ -35243,11 +35805,11 @@ var init_heartbeat = __esm({
|
|
|
35243
35805
|
});
|
|
35244
35806
|
|
|
35245
35807
|
// src/daemon/server.ts
|
|
35246
|
-
import
|
|
35808
|
+
import http4 from "http";
|
|
35247
35809
|
import fs63 from "fs";
|
|
35248
35810
|
import path61 from "path";
|
|
35249
35811
|
import os52 from "os";
|
|
35250
|
-
import { randomUUID as
|
|
35812
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
35251
35813
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
35252
35814
|
import chalk7 from "chalk";
|
|
35253
35815
|
function buildDaemonReport(allEntries, period, now) {
|
|
@@ -35320,14 +35882,40 @@ function buildDaemonReport(allEntries, period, now) {
|
|
|
35320
35882
|
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
35321
35883
|
};
|
|
35322
35884
|
}
|
|
35323
|
-
function
|
|
35324
|
-
const ms =
|
|
35325
|
-
if (ms === 0) return null;
|
|
35885
|
+
function configTimeoutMs(config) {
|
|
35886
|
+
const ms = config.settings.approvalTimeoutMs;
|
|
35326
35887
|
return ms ?? AUTO_DENY_MS;
|
|
35327
35888
|
}
|
|
35328
|
-
function
|
|
35329
|
-
|
|
35330
|
-
|
|
35889
|
+
function parseChannels(v) {
|
|
35890
|
+
if (!Array.isArray(v)) return null;
|
|
35891
|
+
const out = v.filter((c) => CHANNELS.includes(c));
|
|
35892
|
+
return out.length > 0 ? [...new Set(out)] : null;
|
|
35893
|
+
}
|
|
35894
|
+
function inferChannels(kind, slackDelegated) {
|
|
35895
|
+
if (kind === "evaluate") return ["terminal", "monitor"];
|
|
35896
|
+
return slackDelegated ? ["terminal", "monitor", "cloud"] : ["terminal", "monitor", "native", "cloud"];
|
|
35897
|
+
}
|
|
35898
|
+
function cardView(card) {
|
|
35899
|
+
const r = card.request;
|
|
35900
|
+
return {
|
|
35901
|
+
id: card.id,
|
|
35902
|
+
toolName: r.toolName,
|
|
35903
|
+
args: r.args,
|
|
35904
|
+
riskMetadata: r.riskMetadata,
|
|
35905
|
+
...r.recoveryCommand && { recoveryCommand: r.recoveryCommand },
|
|
35906
|
+
...r.viewOnly && { viewOnly: true },
|
|
35907
|
+
slackDelegated: card.channels.includes("cloud") && !card.channels.includes("native"),
|
|
35908
|
+
timestamp: card.createdAt,
|
|
35909
|
+
deadline: card.deadline,
|
|
35910
|
+
agent: r.agent,
|
|
35911
|
+
mcpServer: r.mcpServer,
|
|
35912
|
+
...card.kind === "discovery" && {
|
|
35913
|
+
type: "mcp-discovery",
|
|
35914
|
+
serverKey: r.serverKey,
|
|
35915
|
+
mcpTools: r.mcpTools
|
|
35916
|
+
},
|
|
35917
|
+
allowCount: (insightCounts.get(r.toolName) ?? 0) + 1
|
|
35918
|
+
};
|
|
35331
35919
|
}
|
|
35332
35920
|
function startDaemon() {
|
|
35333
35921
|
try {
|
|
@@ -35349,7 +35937,7 @@ function startDaemon() {
|
|
|
35349
35937
|
recordStartupState("failed", "startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
35350
35938
|
process.exit(1);
|
|
35351
35939
|
}
|
|
35352
|
-
const internalToken =
|
|
35940
|
+
const internalToken = randomUUID8();
|
|
35353
35941
|
const applyTrustRecipients = (entry) => {
|
|
35354
35942
|
const dests = entry.riskMetadata?.exposureDestinations ?? [];
|
|
35355
35943
|
if (dests.length === 0) return;
|
|
@@ -35394,8 +35982,9 @@ function startDaemon() {
|
|
|
35394
35982
|
idleTimer.unref();
|
|
35395
35983
|
}
|
|
35396
35984
|
resetIdleTimer();
|
|
35985
|
+
setInterval(() => cards.sweep(), 1e4).unref();
|
|
35397
35986
|
const allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${DAEMON_PORT}`, `localhost:${DAEMON_PORT}`]);
|
|
35398
|
-
const server =
|
|
35987
|
+
const server = http4.createServer(async (req, res) => {
|
|
35399
35988
|
const host2 = req.headers.host ?? "";
|
|
35400
35989
|
if (!allowedHosts.has(host2)) {
|
|
35401
35990
|
res.writeHead(421, { "Content-Type": "text/plain" });
|
|
@@ -35425,21 +36014,8 @@ function startDaemon() {
|
|
|
35425
36014
|
res.write(
|
|
35426
36015
|
`event: init
|
|
35427
36016
|
data: ${JSON.stringify({
|
|
35428
|
-
requests:
|
|
35429
|
-
|
|
35430
|
-
toolName: e.toolName,
|
|
35431
|
-
args: e.args,
|
|
35432
|
-
riskMetadata: e.riskMetadata,
|
|
35433
|
-
...e.recoveryCommand && { recoveryCommand: e.recoveryCommand },
|
|
35434
|
-
...e.viewOnly && { viewOnly: true },
|
|
35435
|
-
slackDelegated: e.slackDelegated,
|
|
35436
|
-
timestamp: e.timestamp,
|
|
35437
|
-
agent: e.agent,
|
|
35438
|
-
mcpServer: e.mcpServer,
|
|
35439
|
-
allowCount: (insightCounts.get(e.toolName) ?? 0) + 1
|
|
35440
|
-
})),
|
|
35441
|
-
orgName: getOrgName(),
|
|
35442
|
-
autoDenyMs: autoDenyMs()
|
|
36017
|
+
requests: cards.open().map(cardView),
|
|
36018
|
+
orgName: getOrgName()
|
|
35443
36019
|
})}
|
|
35444
36020
|
|
|
35445
36021
|
`
|
|
@@ -35460,7 +36036,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
35460
36036
|
}
|
|
35461
36037
|
return req.on("close", () => {
|
|
35462
36038
|
sseClients.delete(sseClient);
|
|
35463
|
-
if (sseClients.size === 0 &&
|
|
36039
|
+
if (sseClients.size === 0 && cards.open().length > 0) {
|
|
35464
36040
|
setAbandonTimer(setTimeout(abandonPending, getHadBrowserClient() ? 1e4 : 15e3));
|
|
35465
36041
|
}
|
|
35466
36042
|
});
|
|
@@ -35471,137 +36047,81 @@ data: ${JSON.stringify(item.data)}
|
|
|
35471
36047
|
_resetConfigCache();
|
|
35472
36048
|
const body = await readBody(req);
|
|
35473
36049
|
if (body.length > 65536) return res.writeHead(413).end();
|
|
35474
|
-
const
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
|
|
35479
|
-
|
|
35480
|
-
|
|
35481
|
-
|
|
35482
|
-
|
|
35483
|
-
|
|
35484
|
-
fromCLI = false,
|
|
35485
|
-
activityId,
|
|
35486
|
-
cwd,
|
|
35487
|
-
localSmartRuleMatched = false,
|
|
35488
|
-
sessionId
|
|
35489
|
-
} = JSON.parse(body);
|
|
35490
|
-
const id = fromCLI && typeof activityId === "string" && activityId || randomUUID7();
|
|
35491
|
-
const entry = {
|
|
35492
|
-
id,
|
|
36050
|
+
const parsed = JSON.parse(body);
|
|
36051
|
+
const { toolName, args, agent, mcpServer, riskMetadata, recoveryCommand, activityId, cwd } = parsed;
|
|
36052
|
+
if (typeof toolName !== "string" || !toolName) return res.writeHead(400).end();
|
|
36053
|
+
const fromCLI = parsed.fromCLI === true;
|
|
36054
|
+
const kind = parsed.kind === "review" || parsed.kind === "evaluate" ? parsed.kind : "fromCLI" in parsed ? "review" : "evaluate";
|
|
36055
|
+
const projectCwd = typeof cwd === "string" && path61.isAbsolute(cwd) ? cwd : void 0;
|
|
36056
|
+
const projectConfig = getConfig(projectCwd);
|
|
36057
|
+
const timeoutMs = typeof parsed.timeoutMs === "number" && Number.isFinite(parsed.timeoutMs) && parsed.timeoutMs >= 0 ? parsed.timeoutMs : configTimeoutMs(projectConfig);
|
|
36058
|
+
const sessionId = parsed.sessionId;
|
|
36059
|
+
const request2 = {
|
|
35493
36060
|
toolName,
|
|
35494
36061
|
args,
|
|
35495
|
-
riskMetadata
|
|
35496
|
-
...typeof sessionId === "string" && sessionId.length <= 256 && { sessionId },
|
|
36062
|
+
...riskMetadata && typeof riskMetadata === "object" ? { riskMetadata } : {},
|
|
35497
36063
|
...typeof recoveryCommand === "string" && recoveryCommand && { recoveryCommand },
|
|
35498
|
-
...viewOnly && { viewOnly: true },
|
|
35499
|
-
|
|
35500
|
-
|
|
35501
|
-
|
|
35502
|
-
|
|
35503
|
-
|
|
35504
|
-
waiter: null,
|
|
35505
|
-
timer: armAutoDeny(() => {
|
|
35506
|
-
if (pending.has(id)) {
|
|
35507
|
-
const e = pending.get(id);
|
|
35508
|
-
appendAuditLog({
|
|
35509
|
-
toolName: e.toolName,
|
|
35510
|
-
args: e.args,
|
|
35511
|
-
decision: "auto-deny"
|
|
35512
|
-
});
|
|
35513
|
-
e.decisionSource = "timeout";
|
|
35514
|
-
if (e.waiter) e.waiter("deny", "No response \u2014 auto-denied after timeout");
|
|
35515
|
-
else {
|
|
35516
|
-
e.earlyDecision = "deny";
|
|
35517
|
-
e.earlyReason = "No response \u2014 auto-denied after timeout";
|
|
35518
|
-
}
|
|
35519
|
-
pending.delete(id);
|
|
35520
|
-
broadcast("remove", { id, decision: "deny" });
|
|
35521
|
-
}
|
|
35522
|
-
})
|
|
36064
|
+
...parsed.viewOnly === true && { viewOnly: true },
|
|
36065
|
+
...typeof agent === "string" && { agent },
|
|
36066
|
+
...typeof mcpServer === "string" && { mcpServer },
|
|
36067
|
+
...typeof sessionId === "string" && sessionId.length <= 256 && { sessionId },
|
|
36068
|
+
...projectCwd && { cwd: projectCwd },
|
|
36069
|
+
...parsed.protocol === CARD_PROTOCOL && { protocol: CARD_PROTOCOL }
|
|
35523
36070
|
};
|
|
35524
|
-
|
|
36071
|
+
const card = cards.register(request2, {
|
|
36072
|
+
id: fromCLI && typeof activityId === "string" && activityId ? activityId : void 0,
|
|
36073
|
+
kind,
|
|
36074
|
+
channels: parseChannels(parsed.channels) ?? inferChannels(kind, parsed.slackDelegated === true),
|
|
36075
|
+
timeoutMs
|
|
36076
|
+
});
|
|
35525
36077
|
if (!fromCLI) {
|
|
35526
36078
|
broadcast("activity", {
|
|
35527
|
-
id,
|
|
35528
|
-
ts:
|
|
36079
|
+
id: card.id,
|
|
36080
|
+
ts: card.createdAt,
|
|
35529
36081
|
tool: toolName,
|
|
35530
36082
|
args: redactArgs(args),
|
|
35531
36083
|
status: "pending",
|
|
35532
|
-
agent:
|
|
35533
|
-
mcpServer:
|
|
36084
|
+
agent: request2.agent,
|
|
36085
|
+
mcpServer: request2.mcpServer
|
|
35534
36086
|
});
|
|
35535
36087
|
}
|
|
35536
|
-
const projectCwd = typeof cwd === "string" && path61.isAbsolute(cwd) ? cwd : void 0;
|
|
35537
|
-
const projectConfig = getConfig(projectCwd);
|
|
35538
36088
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
35539
36089
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
35540
36090
|
if (browserEnabled || terminalEnabled) {
|
|
35541
|
-
broadcast("add", {
|
|
35542
|
-
id,
|
|
35543
|
-
toolName,
|
|
35544
|
-
args,
|
|
35545
|
-
riskMetadata: entry.riskMetadata,
|
|
35546
|
-
...entry.recoveryCommand && { recoveryCommand: entry.recoveryCommand },
|
|
35547
|
-
...entry.viewOnly && { viewOnly: true },
|
|
35548
|
-
slackDelegated: entry.slackDelegated,
|
|
35549
|
-
agent: entry.agent,
|
|
35550
|
-
mcpServer: entry.mcpServer,
|
|
35551
|
-
interactive: terminalEnabled,
|
|
35552
|
-
// allowCount = what this count will be if the user allows.
|
|
35553
|
-
// Terminal uses this to show the 💡 insight line on the Nth consecutive approval.
|
|
35554
|
-
allowCount: (insightCounts.get(toolName) ?? 0) + 1
|
|
35555
|
-
});
|
|
36091
|
+
broadcast("add", { ...cardView(card), interactive: terminalEnabled });
|
|
35556
36092
|
}
|
|
35557
36093
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
35558
|
-
res.end(
|
|
35559
|
-
|
|
36094
|
+
res.end(
|
|
36095
|
+
JSON.stringify({
|
|
36096
|
+
id: card.id,
|
|
36097
|
+
allowCount: (insightCounts.get(toolName) ?? 0) + 1,
|
|
36098
|
+
protocol: CARD_PROTOCOL,
|
|
36099
|
+
deadline: card.deadline
|
|
36100
|
+
})
|
|
36101
|
+
);
|
|
36102
|
+
if (kind !== "evaluate") return;
|
|
35560
36103
|
authorizeHeadless(
|
|
35561
36104
|
toolName,
|
|
35562
36105
|
args,
|
|
36106
|
+
{ agent: request2.agent, mcpServer: request2.mcpServer },
|
|
35563
36107
|
{
|
|
35564
|
-
|
|
35565
|
-
|
|
35566
|
-
|
|
35567
|
-
|
|
36108
|
+
calledFromDaemon: true,
|
|
36109
|
+
...projectCwd && { cwd: projectCwd },
|
|
36110
|
+
localSmartRuleMatched: parsed.localSmartRuleMatched === true
|
|
36111
|
+
}
|
|
35568
36112
|
).then((result) => {
|
|
35569
|
-
|
|
35570
|
-
if (
|
|
35571
|
-
if (result.noApprovalMechanism) return;
|
|
35572
|
-
if (result.checkedBy === "audit") return;
|
|
35573
|
-
if (e.earlyDecision !== null) return;
|
|
36113
|
+
if (cards.get(card.id)?.state !== "open") return;
|
|
36114
|
+
if (result.noApprovalMechanism || result.checkedBy === "audit") return;
|
|
35574
36115
|
broadcast("activity-result", {
|
|
35575
|
-
id,
|
|
36116
|
+
id: card.id,
|
|
35576
36117
|
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
|
|
35577
36118
|
label: result.blockedByLabel,
|
|
35578
|
-
agent:
|
|
35579
|
-
mcpServer:
|
|
36119
|
+
agent: request2.agent,
|
|
36120
|
+
mcpServer: request2.mcpServer
|
|
35580
36121
|
});
|
|
35581
|
-
|
|
35582
|
-
const decision = result.approved ? "allow" : "deny";
|
|
35583
|
-
appendAuditLog({ toolName: e.toolName, args: e.args, decision });
|
|
35584
|
-
e.decisionSource = backgroundPassSource(result);
|
|
35585
|
-
if (e.waiter) {
|
|
35586
|
-
e.waiter(decision, result.reason);
|
|
35587
|
-
pending.delete(id);
|
|
35588
|
-
broadcast("remove", { id, decision });
|
|
35589
|
-
} else {
|
|
35590
|
-
e.earlyDecision = decision;
|
|
35591
|
-
e.earlyReason = result.reason;
|
|
35592
|
-
}
|
|
36122
|
+
cards.decide(card.id, recordFromVerdict(result));
|
|
35593
36123
|
}).catch((err2) => {
|
|
35594
|
-
|
|
35595
|
-
if (!e) return;
|
|
35596
|
-
clearTimeout(e.timer);
|
|
35597
|
-
const reason = err2?.reason || "No response \u2014 request timed out";
|
|
35598
|
-
if (e.waiter) e.waiter("deny", reason);
|
|
35599
|
-
else {
|
|
35600
|
-
e.earlyDecision = "deny";
|
|
35601
|
-
e.earlyReason = reason;
|
|
35602
|
-
}
|
|
35603
|
-
pending.delete(id);
|
|
35604
|
-
broadcast("remove", { id, decision: "deny" });
|
|
36124
|
+
console.error(chalk7.red("[node9 daemon] check of an evaluate card failed:"), err2);
|
|
35605
36125
|
});
|
|
35606
36126
|
return;
|
|
35607
36127
|
} catch {
|
|
@@ -35610,36 +36130,33 @@ data: ${JSON.stringify(item.data)}
|
|
|
35610
36130
|
}
|
|
35611
36131
|
if (req.method === "GET" && pathname.startsWith("/wait/")) {
|
|
35612
36132
|
const id = pathname.split("/").pop();
|
|
35613
|
-
const
|
|
35614
|
-
if (!
|
|
35615
|
-
if (
|
|
35616
|
-
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
35623
|
-
|
|
35624
|
-
if (
|
|
35625
|
-
|
|
35626
|
-
|
|
35627
|
-
|
|
35628
|
-
|
|
35629
|
-
|
|
35630
|
-
|
|
35631
|
-
|
|
35632
|
-
|
|
35633
|
-
|
|
35634
|
-
|
|
35635
|
-
|
|
35636
|
-
|
|
35637
|
-
|
|
35638
|
-
|
|
35639
|
-
|
|
35640
|
-
broadcast("remove", { id });
|
|
35641
|
-
}
|
|
35642
|
-
}, 200);
|
|
36133
|
+
const card = cards.get(id);
|
|
36134
|
+
if (!card) return res.writeHead(404).end();
|
|
36135
|
+
if (card.request.protocol === CARD_PROTOCOL && !validToken(req))
|
|
36136
|
+
return res.writeHead(403).end();
|
|
36137
|
+
const ctrl = new AbortController();
|
|
36138
|
+
let answered = false;
|
|
36139
|
+
res.on("close", () => {
|
|
36140
|
+
if (!answered) ctrl.abort();
|
|
36141
|
+
});
|
|
36142
|
+
void cards.wait(id, ctrl.signal).then((outcome) => {
|
|
36143
|
+
answered = true;
|
|
36144
|
+
if (res.writableEnded || res.destroyed) return;
|
|
36145
|
+
if (outcome.kind === "decided") {
|
|
36146
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36147
|
+
res.end(
|
|
36148
|
+
JSON.stringify({
|
|
36149
|
+
protocol: CARD_PROTOCOL,
|
|
36150
|
+
record: outcome.record,
|
|
36151
|
+
...legacyWaitFields(outcome.record)
|
|
36152
|
+
})
|
|
36153
|
+
);
|
|
36154
|
+
} else if (outcome.kind === "withdrawn") {
|
|
36155
|
+
res.writeHead(409, { "Content-Type": "application/json" });
|
|
36156
|
+
res.end(JSON.stringify({ protocol: CARD_PROTOCOL, withdrawn: true }));
|
|
36157
|
+
} else {
|
|
36158
|
+
res.writeHead(404).end();
|
|
36159
|
+
}
|
|
35643
36160
|
});
|
|
35644
36161
|
return;
|
|
35645
36162
|
}
|
|
@@ -35647,68 +36164,63 @@ data: ${JSON.stringify(item.data)}
|
|
|
35647
36164
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
35648
36165
|
try {
|
|
35649
36166
|
const id = pathname.split("/").pop();
|
|
35650
|
-
const
|
|
35651
|
-
if (!
|
|
35652
|
-
if (
|
|
36167
|
+
const card = cards.get(id);
|
|
36168
|
+
if (!card) return res.writeHead(404).end();
|
|
36169
|
+
if (card.state === "decided") {
|
|
35653
36170
|
res.writeHead(409, { "Content-Type": "application/json" });
|
|
35654
|
-
return res.end(
|
|
36171
|
+
return res.end(
|
|
36172
|
+
JSON.stringify({ conflict: true, decision: card.record.decision, record: card.record })
|
|
36173
|
+
);
|
|
35655
36174
|
}
|
|
36175
|
+
if (card.request.viewOnly) return res.writeHead(409).end();
|
|
35656
36176
|
const { decision, persist, trustDuration, reason, source, trustRecipients } = JSON.parse(
|
|
35657
36177
|
await readBody(req)
|
|
35658
36178
|
);
|
|
35659
|
-
|
|
35660
|
-
|
|
36179
|
+
const via = channelFromDecisionSource(source);
|
|
36180
|
+
if (!via) return res.writeHead(400).end();
|
|
36181
|
+
const isTrust = decision === "trust";
|
|
36182
|
+
if (decision !== "allow" && decision !== "deny" && !isTrust)
|
|
36183
|
+
return res.writeHead(400).end();
|
|
36184
|
+
if (isTrust && (typeof trustDuration !== "string" || !(trustDuration in TRUST_DURATIONS)))
|
|
36185
|
+
return res.writeHead(400).end();
|
|
36186
|
+
if (persist === true && isTrust) return res.writeHead(400).end();
|
|
36187
|
+
const verdict = isTrust ? "allow" : decision;
|
|
36188
|
+
const redirect = via.redirect && typeof reason === "string" && reason ? reason.slice(0, 2e3) : void 0;
|
|
36189
|
+
const record2 = personRecord(via.channel, verdict, {
|
|
36190
|
+
...redirect && { redirect },
|
|
36191
|
+
...isTrust ? { standing: "trust", trustDuration } : persist === true ? {
|
|
36192
|
+
standing: verdict === "allow" ? "always-allow" : "always-deny"
|
|
36193
|
+
} : verdict === "allow" && trustRecipients === true ? { standing: "trust-recipients" } : {}
|
|
36194
|
+
});
|
|
36195
|
+
const outcome = cards.decide(id, record2);
|
|
36196
|
+
if (!outcome) return res.writeHead(404).end();
|
|
36197
|
+
if (!outcome.won) {
|
|
36198
|
+
res.writeHead(409, { "Content-Type": "application/json" });
|
|
36199
|
+
return res.end(
|
|
36200
|
+
JSON.stringify({
|
|
36201
|
+
conflict: true,
|
|
36202
|
+
decision: outcome.record.decision,
|
|
36203
|
+
record: outcome.record
|
|
36204
|
+
})
|
|
36205
|
+
);
|
|
35661
36206
|
}
|
|
35662
|
-
if (
|
|
35663
|
-
const ms = TRUST_DURATIONS[trustDuration]
|
|
35664
|
-
|
|
35665
|
-
|
|
35666
|
-
|
|
35667
|
-
toolName
|
|
35668
|
-
|
|
35669
|
-
|
|
35670
|
-
|
|
35671
|
-
|
|
35672
|
-
|
|
35673
|
-
entry.waiter("allow");
|
|
35674
|
-
pending.delete(id);
|
|
35675
|
-
broadcast("remove", { id, decision: "allow" });
|
|
35676
|
-
} else {
|
|
35677
|
-
entry.earlyDecision = "allow";
|
|
35678
|
-
broadcast("remove", { id, decision: "allow" });
|
|
35679
|
-
entry.timer = setTimeout(() => pending.delete(id), 3e4);
|
|
35680
|
-
}
|
|
35681
|
-
res.writeHead(200);
|
|
35682
|
-
return res.end(JSON.stringify({ ok: true }));
|
|
36207
|
+
if (isTrust) {
|
|
36208
|
+
const ms = TRUST_DURATIONS[trustDuration];
|
|
36209
|
+
writeTrustEntry(
|
|
36210
|
+
card.request.toolName,
|
|
36211
|
+
ms,
|
|
36212
|
+
extractCommandPattern(card.request.toolName, card.request.args)
|
|
36213
|
+
);
|
|
36214
|
+
} else if (persist === true) {
|
|
36215
|
+
writePersistentDecision(card.request.toolName, verdict);
|
|
36216
|
+
} else if (verdict === "allow" && trustRecipients === true) {
|
|
36217
|
+
applyTrustRecipients(card.request);
|
|
35683
36218
|
}
|
|
35684
|
-
|
|
35685
|
-
|
|
35686
|
-
appendAuditLog({
|
|
35687
|
-
toolName: entry.toolName,
|
|
35688
|
-
args: entry.args,
|
|
35689
|
-
decision: resolvedDecision
|
|
35690
|
-
});
|
|
35691
|
-
clearTimeout(entry.timer);
|
|
35692
|
-
if (resolvedDecision === "allow" && !persist) {
|
|
35693
|
-
insightCounts.set(entry.toolName, (insightCounts.get(entry.toolName) ?? 0) + 1);
|
|
35694
|
-
saveInsightCounts();
|
|
35695
|
-
} else if (resolvedDecision === "deny") {
|
|
35696
|
-
insightCounts.delete(entry.toolName);
|
|
35697
|
-
saveInsightCounts();
|
|
35698
|
-
}
|
|
35699
|
-
if (source && DECISION_ROUTE_SOURCES.has(source)) entry.decisionSource = source;
|
|
35700
|
-
if (entry.waiter) {
|
|
35701
|
-
entry.waiter(resolvedDecision, reason);
|
|
35702
|
-
pending.delete(id);
|
|
35703
|
-
broadcast("remove", { id, decision: resolvedDecision });
|
|
35704
|
-
} else {
|
|
35705
|
-
entry.earlyDecision = resolvedDecision;
|
|
35706
|
-
entry.earlyReason = reason;
|
|
35707
|
-
broadcast("remove", { id, decision: resolvedDecision });
|
|
35708
|
-
entry.timer = setTimeout(() => pending.delete(id), 3e4);
|
|
36219
|
+
if (card.kind === "discovery" && verdict === "allow" && card.request.serverKey) {
|
|
36220
|
+
approveServer(card.request.serverKey, []);
|
|
35709
36221
|
}
|
|
35710
|
-
res.writeHead(200);
|
|
35711
|
-
return res.end(JSON.stringify({ ok: true }));
|
|
36222
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36223
|
+
return res.end(JSON.stringify({ ok: true, record: outcome.record }));
|
|
35712
36224
|
} catch {
|
|
35713
36225
|
res.writeHead(400).end();
|
|
35714
36226
|
}
|
|
@@ -35813,34 +36325,18 @@ data: ${JSON.stringify(item.data)}
|
|
|
35813
36325
|
}
|
|
35814
36326
|
}
|
|
35815
36327
|
if (req.method === "POST" && pathname.startsWith("/resolve/")) {
|
|
35816
|
-
|
|
35817
|
-
if (internalAuth !== internalToken) return res.writeHead(403).end();
|
|
36328
|
+
if (!validToken(req)) return res.writeHead(403).end();
|
|
35818
36329
|
try {
|
|
35819
36330
|
const id = pathname.split("/").pop();
|
|
35820
|
-
|
|
35821
|
-
|
|
35822
|
-
const
|
|
35823
|
-
const
|
|
35824
|
-
|
|
35825
|
-
|
|
35826
|
-
|
|
35827
|
-
|
|
35828
|
-
|
|
35829
|
-
clearTimeout(entry.timer);
|
|
35830
|
-
if (resolvedResolveDecision === "allow") {
|
|
35831
|
-
insightCounts.set(entry.toolName, (insightCounts.get(entry.toolName) ?? 0) + 1);
|
|
35832
|
-
saveInsightCounts();
|
|
35833
|
-
} else {
|
|
35834
|
-
insightCounts.delete(entry.toolName);
|
|
35835
|
-
saveInsightCounts();
|
|
35836
|
-
}
|
|
35837
|
-
if (source && HUMAN_DECISION_SOURCES.has(source)) entry.decisionSource = source;
|
|
35838
|
-
if (entry.waiter) entry.waiter(resolvedResolveDecision);
|
|
35839
|
-
else entry.earlyDecision = resolvedResolveDecision;
|
|
35840
|
-
pending.delete(id);
|
|
35841
|
-
broadcast("remove", { id, decision: resolvedResolveDecision });
|
|
35842
|
-
res.writeHead(200);
|
|
35843
|
-
return res.end(JSON.stringify({ ok: true }));
|
|
36331
|
+
if (!cards.get(id)) return res.writeHead(404).end();
|
|
36332
|
+
const body = JSON.parse(await readBody(req));
|
|
36333
|
+
const record2 = parseDecisionRecord(body.record) ?? recordFromLegacyResolve(body.decision === "allow" ? "allow" : "deny", body.source);
|
|
36334
|
+
const outcome = cards.decide(id, record2);
|
|
36335
|
+
if (!outcome) return res.writeHead(404).end();
|
|
36336
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36337
|
+
return res.end(
|
|
36338
|
+
JSON.stringify({ protocol: CARD_PROTOCOL, won: outcome.won, record: outcome.record })
|
|
36339
|
+
);
|
|
35844
36340
|
} catch {
|
|
35845
36341
|
res.writeHead(400).end();
|
|
35846
36342
|
}
|
|
@@ -36185,35 +36681,16 @@ data: ${JSON.stringify(item.data)}
|
|
|
36185
36681
|
args: { toolCount: tools.length, status },
|
|
36186
36682
|
decision: "mcp-discovered"
|
|
36187
36683
|
});
|
|
36188
|
-
const
|
|
36189
|
-
|
|
36190
|
-
|
|
36191
|
-
|
|
36192
|
-
|
|
36193
|
-
|
|
36194
|
-
|
|
36195
|
-
|
|
36196
|
-
|
|
36197
|
-
|
|
36198
|
-
timer: setTimeout(() => {
|
|
36199
|
-
if (pending.has(id)) {
|
|
36200
|
-
pending.delete(id);
|
|
36201
|
-
broadcast("remove", { id, decision: "deny" });
|
|
36202
|
-
}
|
|
36203
|
-
}, 6e4),
|
|
36204
|
-
// 60s timeout for discovery
|
|
36205
|
-
waiter: null,
|
|
36206
|
-
earlyDecision: null
|
|
36207
|
-
};
|
|
36208
|
-
pending.set(id, entry);
|
|
36209
|
-
broadcast("add", {
|
|
36210
|
-
id,
|
|
36211
|
-
type: "mcp-discovery",
|
|
36212
|
-
serverKey,
|
|
36213
|
-
mcpTools: tools,
|
|
36214
|
-
toolName: "mcp-discovery",
|
|
36215
|
-
args: { serverKey, toolCount: tools.length }
|
|
36216
|
-
});
|
|
36684
|
+
const card = cards.register(
|
|
36685
|
+
{
|
|
36686
|
+
toolName: "mcp-discovery",
|
|
36687
|
+
args: { serverKey, toolCount: tools.length },
|
|
36688
|
+
serverKey,
|
|
36689
|
+
mcpTools: tools
|
|
36690
|
+
},
|
|
36691
|
+
{ kind: "discovery", channels: ["terminal", "monitor"], timeoutMs: 6e4 }
|
|
36692
|
+
);
|
|
36693
|
+
broadcast("add", cardView(card));
|
|
36217
36694
|
}
|
|
36218
36695
|
res.writeHead(200).end();
|
|
36219
36696
|
return;
|
|
@@ -36246,20 +36723,22 @@ data: ${JSON.stringify(item.data)}
|
|
|
36246
36723
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
36247
36724
|
try {
|
|
36248
36725
|
const { id, serverKey, disabledTools } = JSON.parse(await readBody(req));
|
|
36249
|
-
const
|
|
36250
|
-
if (!
|
|
36726
|
+
const card = typeof id === "string" ? cards.get(id) : void 0;
|
|
36727
|
+
if (!card || card.kind !== "discovery" || card.request.serverKey !== serverKey) {
|
|
36251
36728
|
res.writeHead(404).end();
|
|
36252
36729
|
return;
|
|
36253
36730
|
}
|
|
36254
|
-
|
|
36731
|
+
const outcome = cards.decide(card.id, personRecord("terminal", "allow"));
|
|
36732
|
+
if (!outcome?.won) {
|
|
36733
|
+
res.writeHead(409).end();
|
|
36734
|
+
return;
|
|
36735
|
+
}
|
|
36255
36736
|
approveServer(serverKey, disabledTools);
|
|
36256
36737
|
appendAuditLog({
|
|
36257
36738
|
toolName: `mcp-server:${serverKey}`,
|
|
36258
36739
|
args: { disabledTools },
|
|
36259
36740
|
decision: "allow"
|
|
36260
36741
|
});
|
|
36261
|
-
pending.delete(id);
|
|
36262
|
-
broadcast("remove", { id, decision: "allow" });
|
|
36263
36742
|
res.writeHead(200).end();
|
|
36264
36743
|
return;
|
|
36265
36744
|
} catch {
|
|
@@ -36492,7 +36971,7 @@ var init_server = __esm({
|
|
|
36492
36971
|
"use strict";
|
|
36493
36972
|
init_migrate();
|
|
36494
36973
|
init_trusted_recipients();
|
|
36495
|
-
|
|
36974
|
+
init_decision_record();
|
|
36496
36975
|
init_core();
|
|
36497
36976
|
init_keyed_guard();
|
|
36498
36977
|
init_scan();
|
|
@@ -36883,14 +37362,14 @@ var init_open_browser = __esm({
|
|
|
36883
37362
|
});
|
|
36884
37363
|
|
|
36885
37364
|
// src/utils/post-json.ts
|
|
36886
|
-
import * as
|
|
37365
|
+
import * as http5 from "http";
|
|
36887
37366
|
import * as https5 from "https";
|
|
36888
37367
|
import { URL as URL4 } from "url";
|
|
36889
37368
|
function postJson2(url, body, bearer) {
|
|
36890
37369
|
return new Promise((resolve2, reject) => {
|
|
36891
37370
|
const payload = JSON.stringify(body);
|
|
36892
37371
|
const u = new URL4(url);
|
|
36893
|
-
const lib = u.protocol === "http:" ?
|
|
37372
|
+
const lib = u.protocol === "http:" ? http5 : https5;
|
|
36894
37373
|
const req = lib.request(
|
|
36895
37374
|
{
|
|
36896
37375
|
method: "POST",
|
|
@@ -46289,7 +46768,7 @@ var require_client = __commonJS({
|
|
|
46289
46768
|
"use strict";
|
|
46290
46769
|
var assert = __require("assert");
|
|
46291
46770
|
var net3 = __require("net");
|
|
46292
|
-
var
|
|
46771
|
+
var http9 = __require("http");
|
|
46293
46772
|
var util = require_util();
|
|
46294
46773
|
var { ClientStats } = require_stats();
|
|
46295
46774
|
var { channels } = require_diagnostics();
|
|
@@ -46347,7 +46826,7 @@ var require_client = __commonJS({
|
|
|
46347
46826
|
var connectH1 = require_client_h1();
|
|
46348
46827
|
var connectH2 = require_client_h2();
|
|
46349
46828
|
var kClosedResolve = /* @__PURE__ */ Symbol("kClosedResolve");
|
|
46350
|
-
var getDefaultNodeMaxHeaderSize =
|
|
46829
|
+
var getDefaultNodeMaxHeaderSize = http9 && http9.maxHeaderSize && Number.isInteger(http9.maxHeaderSize) && http9.maxHeaderSize > 0 ? () => http9.maxHeaderSize : () => {
|
|
46351
46830
|
throw new InvalidArgumentError("http module not available or http.maxHeaderSize invalid");
|
|
46352
46831
|
};
|
|
46353
46832
|
var noop = () => {
|
|
@@ -46964,8 +47443,8 @@ var require_pool_base = __commonJS({
|
|
|
46964
47443
|
}
|
|
46965
47444
|
get [kPending]() {
|
|
46966
47445
|
let ret = this[kQueued];
|
|
46967
|
-
for (const { [kPending]:
|
|
46968
|
-
ret +=
|
|
47446
|
+
for (const { [kPending]: pending } of this[kClients]) {
|
|
47447
|
+
ret += pending;
|
|
46969
47448
|
}
|
|
46970
47449
|
return ret;
|
|
46971
47450
|
}
|
|
@@ -51394,19 +51873,19 @@ var require_mock_agent = __commonJS({
|
|
|
51394
51873
|
}
|
|
51395
51874
|
pendingInterceptors() {
|
|
51396
51875
|
const mockAgentClients = this[kClients];
|
|
51397
|
-
return Array.from(mockAgentClients.entries()).flatMap(([origin, result]) => result.dispatcher[kDispatches].map((dispatch) => ({ ...dispatch, origin }))).filter(({ pending
|
|
51876
|
+
return Array.from(mockAgentClients.entries()).flatMap(([origin, result]) => result.dispatcher[kDispatches].map((dispatch) => ({ ...dispatch, origin }))).filter(({ pending }) => pending);
|
|
51398
51877
|
}
|
|
51399
51878
|
assertNoPendingInterceptors({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) {
|
|
51400
|
-
const
|
|
51401
|
-
if (
|
|
51879
|
+
const pending = this.pendingInterceptors();
|
|
51880
|
+
if (pending.length === 0) {
|
|
51402
51881
|
return;
|
|
51403
51882
|
}
|
|
51404
51883
|
throw new UndiciError(
|
|
51405
|
-
|
|
51884
|
+
pending.length === 1 ? `1 interceptor is pending:
|
|
51406
51885
|
|
|
51407
|
-
${pendingInterceptorsFormatter.format(
|
|
51886
|
+
${pendingInterceptorsFormatter.format(pending)}`.trim() : `${pending.length} interceptors are pending:
|
|
51408
51887
|
|
|
51409
|
-
${pendingInterceptorsFormatter.format(
|
|
51888
|
+
${pendingInterceptorsFormatter.format(pending)}`.trim()
|
|
51410
51889
|
);
|
|
51411
51890
|
}
|
|
51412
51891
|
};
|
|
@@ -63715,12 +64194,13 @@ var init_first_run = __esm({
|
|
|
63715
64194
|
var tail_exports = {};
|
|
63716
64195
|
__export(tail_exports, {
|
|
63717
64196
|
agentLabel: () => agentLabel,
|
|
64197
|
+
deadlineLine: () => deadlineLine,
|
|
63718
64198
|
eventsUrl: () => eventsUrl,
|
|
63719
64199
|
sessionTag: () => sessionTag,
|
|
63720
64200
|
shortenPathSummary: () => shortenPathSummary,
|
|
63721
64201
|
startTail: () => startTail
|
|
63722
64202
|
});
|
|
63723
|
-
import
|
|
64203
|
+
import http7 from "http";
|
|
63724
64204
|
import chalk47 from "chalk";
|
|
63725
64205
|
import fs97 from "fs";
|
|
63726
64206
|
import os80 from "os";
|
|
@@ -63932,7 +64412,7 @@ function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
|
63932
64412
|
if (opts?.trustRecipients) bodyObj.trustRecipients = true;
|
|
63933
64413
|
if (opts?.reason) bodyObj.reason = opts.reason;
|
|
63934
64414
|
const body = JSON.stringify(bodyObj);
|
|
63935
|
-
const req =
|
|
64415
|
+
const req = http7.request(
|
|
63936
64416
|
{
|
|
63937
64417
|
hostname: "127.0.0.1",
|
|
63938
64418
|
port,
|
|
@@ -63948,9 +64428,21 @@ function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
|
63948
64428
|
}
|
|
63949
64429
|
},
|
|
63950
64430
|
(res) => {
|
|
63951
|
-
|
|
63952
|
-
|
|
63953
|
-
|
|
64431
|
+
let text = "";
|
|
64432
|
+
res.setEncoding("utf8");
|
|
64433
|
+
res.on("data", (c) => text += c);
|
|
64434
|
+
res.on("end", () => {
|
|
64435
|
+
if (res.statusCode === 200) return resolve2(null);
|
|
64436
|
+
if (res.statusCode === 409) {
|
|
64437
|
+
try {
|
|
64438
|
+
const r = JSON.parse(text).record;
|
|
64439
|
+
if (r?.decision && r.label) return resolve2({ decision: r.decision, label: r.label });
|
|
64440
|
+
} catch {
|
|
64441
|
+
}
|
|
64442
|
+
return resolve2(null);
|
|
64443
|
+
}
|
|
64444
|
+
reject(new Error(`POST /decision returned ${res.statusCode}`));
|
|
64445
|
+
});
|
|
63954
64446
|
}
|
|
63955
64447
|
);
|
|
63956
64448
|
req.on("error", reject);
|
|
@@ -63979,6 +64471,15 @@ function cleanBlockedBy(raw) {
|
|
|
63979
64471
|
if (smartMatch) return smartMatch[1];
|
|
63980
64472
|
return raw;
|
|
63981
64473
|
}
|
|
64474
|
+
function deadlineLine(deadline, now = Date.now()) {
|
|
64475
|
+
if (deadline === void 0) return null;
|
|
64476
|
+
if (deadline === null) return "No timeout: waits for your answer";
|
|
64477
|
+
const left = Math.max(0, Math.round((deadline - now) / 1e3));
|
|
64478
|
+
const m = Math.floor(left / 60);
|
|
64479
|
+
const sec = left % 60;
|
|
64480
|
+
const at = new Date(deadline).toLocaleTimeString([], { hour12: false });
|
|
64481
|
+
return `Auto-denies in ${m > 0 ? `${m}m ` : ""}${sec}s (at ${at}) if nobody answers`;
|
|
64482
|
+
}
|
|
63982
64483
|
function buildCardLines(req, localCount = 0) {
|
|
63983
64484
|
if (req.recoveryCommand) {
|
|
63984
64485
|
return buildRecoveryCardLines(req);
|
|
@@ -64018,8 +64519,10 @@ function buildCardLines(req, localCount = 0) {
|
|
|
64018
64519
|
`${CYAN}\u2551${RESET} ${YELLOW}\u{1F4A1}${RESET} Approved ${localCount}\xD7 before \u2014 ${BOLD}[a]${RESET}${YELLOW} creates a permanent rule${RESET}`
|
|
64019
64520
|
);
|
|
64020
64521
|
}
|
|
64522
|
+
const timing = deadlineLine(req.deadline);
|
|
64021
64523
|
lines.push(
|
|
64022
64524
|
`${CYAN}\u255A${RESET}`,
|
|
64525
|
+
...timing ? [` ${GRAY}${timing}${RESET}`] : [],
|
|
64023
64526
|
``,
|
|
64024
64527
|
exposure.length > 0 ? ` ${BOLD}${GREEN}[\u21B5/y]${RESET} Allow ${BOLD}${RED}[n]${RESET} Deny ${BOLD}${CYAN}[s]${RESET} Allow + trust ${exposure.length === 1 ? "this address" : "these addresses"}` : ` ${BOLD}${GREEN}[\u21B5/y]${RESET} Allow ${BOLD}${RED}[n]${RESET} Deny ${BOLD}${YELLOW}[a]${RESET} Always Allow ${BOLD}${CYAN}[t]${RESET} Trust 30m`,
|
|
64025
64528
|
``
|
|
@@ -64036,7 +64539,7 @@ function buildRecoveryCardLines(req) {
|
|
|
64036
64539
|
` ${BOLD}${YELLOW}[2]${RESET} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
|
|
64037
64540
|
` ${BOLD}${RED}[3]${RESET} Deny & stop ${GRAY}(hard block)${RESET}`,
|
|
64038
64541
|
``,
|
|
64039
|
-
` ${GRAY}
|
|
64542
|
+
` ${GRAY}${deadlineLine(req.deadline) ?? "Auto-denies at the approval timeout if nobody answers"}${RESET}`,
|
|
64040
64543
|
` Select [1-3]: `
|
|
64041
64544
|
];
|
|
64042
64545
|
return [
|
|
@@ -64083,7 +64586,7 @@ async function startTail(options = {}) {
|
|
|
64083
64586
|
const port = await ensureDaemon();
|
|
64084
64587
|
if (options.clear) {
|
|
64085
64588
|
const result = await new Promise((resolve2) => {
|
|
64086
|
-
const req2 =
|
|
64589
|
+
const req2 = http7.request(
|
|
64087
64590
|
{ method: "POST", hostname: "127.0.0.1", port, path: "/events/clear" },
|
|
64088
64591
|
(res) => {
|
|
64089
64592
|
const status = res.statusCode ?? 0;
|
|
@@ -64246,7 +64749,13 @@ async function startTail(options = {}) {
|
|
|
64246
64749
|
} else {
|
|
64247
64750
|
httpDecision = action;
|
|
64248
64751
|
}
|
|
64249
|
-
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).
|
|
64752
|
+
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).then((won) => {
|
|
64753
|
+
if (won && !cardActive)
|
|
64754
|
+
process.stdout.write(
|
|
64755
|
+
` ${YELLOW}\u21B3 Already decided before your answer: ${won.decision.toUpperCase()} (${safeMessage(won.label, 120)})${RESET}
|
|
64756
|
+
`
|
|
64757
|
+
);
|
|
64758
|
+
}).catch((err2) => {
|
|
64250
64759
|
try {
|
|
64251
64760
|
fs97.appendFileSync(
|
|
64252
64761
|
path94.join(os80.homedir(), ".node9", "hook-debug.log"),
|
|
@@ -64371,7 +64880,7 @@ async function startTail(options = {}) {
|
|
|
64371
64880
|
}, STALL_THRESHOLD_MS / 2);
|
|
64372
64881
|
stallWatchdog.unref();
|
|
64373
64882
|
const sseUrl = eventsUrl(port, canApprove);
|
|
64374
|
-
const req =
|
|
64883
|
+
const req = http7.get(
|
|
64375
64884
|
sseUrl,
|
|
64376
64885
|
{
|
|
64377
64886
|
headers: authToken ? { "X-Node9-Internal": authToken } : {}
|
|
@@ -64589,7 +65098,7 @@ __export(hud_exports, {
|
|
|
64589
65098
|
import fs98 from "fs";
|
|
64590
65099
|
import path95 from "path";
|
|
64591
65100
|
import os81 from "os";
|
|
64592
|
-
import
|
|
65101
|
+
import http8 from "http";
|
|
64593
65102
|
async function readStdin() {
|
|
64594
65103
|
const chunks = [];
|
|
64595
65104
|
for await (const chunk2 of process.stdin) {
|
|
@@ -64607,7 +65116,7 @@ function queryDaemon() {
|
|
|
64607
65116
|
return new Promise((resolve2) => {
|
|
64608
65117
|
const timeout = setTimeout(() => resolve2(null), 50);
|
|
64609
65118
|
try {
|
|
64610
|
-
const req =
|
|
65119
|
+
const req = http8.get(
|
|
64611
65120
|
`http://${DAEMON_HOST}:${DAEMON_PORT}/status`,
|
|
64612
65121
|
{ timeout: 50 },
|
|
64613
65122
|
(res) => {
|
|
@@ -65078,7 +65587,7 @@ async function runProxy(targetCommand) {
|
|
|
65078
65587
|
console.error(chalk9.gray(` Reason: ${result.reason || "Security Policy"}
|
|
65079
65588
|
`));
|
|
65080
65589
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
65081
|
-
const isHuman = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
65590
|
+
const isHuman = isHumanDecision(blockedByLabel, result.blockedBy, result.decidedBy);
|
|
65082
65591
|
const aiInstruction = buildNegotiationMessage(
|
|
65083
65592
|
blockedByLabel,
|
|
65084
65593
|
isHuman,
|
|
@@ -65086,7 +65595,8 @@ async function runProxy(targetCommand) {
|
|
|
65086
65595
|
void 0,
|
|
65087
65596
|
{
|
|
65088
65597
|
kind: result.blockKind,
|
|
65089
|
-
path: result.blockedPath
|
|
65598
|
+
path: result.blockedPath,
|
|
65599
|
+
redirect: !!result.redirect
|
|
65090
65600
|
}
|
|
65091
65601
|
);
|
|
65092
65602
|
const errorResponse = {
|
|
@@ -65760,7 +66270,11 @@ RAW: ${raw}
|
|
|
65760
66270
|
const mcpServer = mcpMatch?.[1];
|
|
65761
66271
|
const sendBlock = (msg, result2) => {
|
|
65762
66272
|
const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || (isKeyedForPolicy() ? "Workspace Policy" : "Local Security Policy");
|
|
65763
|
-
const humanDecision = isHumanDecision(
|
|
66273
|
+
const humanDecision = isHumanDecision(
|
|
66274
|
+
blockedByContext,
|
|
66275
|
+
result2?.blockedBy,
|
|
66276
|
+
result2?.decidedBy
|
|
66277
|
+
);
|
|
65764
66278
|
let ttyFd = null;
|
|
65765
66279
|
try {
|
|
65766
66280
|
ttyFd = fs70.openSync("/dev/tty", "w");
|
|
@@ -65800,7 +66314,7 @@ RAW: ${raw}
|
|
|
65800
66314
|
humanDecision,
|
|
65801
66315
|
msg,
|
|
65802
66316
|
result2?.recoveryCommand,
|
|
65803
|
-
{ kind: result2?.blockKind, path: result2?.blockedPath }
|
|
66317
|
+
{ kind: result2?.blockKind, path: result2?.blockedPath, redirect: !!result2?.redirect }
|
|
65804
66318
|
);
|
|
65805
66319
|
if (agent === "Antigravity") {
|
|
65806
66320
|
process.stdout.write(
|
|
@@ -68243,11 +68757,15 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
68243
68757
|
let dimAppsBlocked = 0;
|
|
68244
68758
|
let dimPinMismatch = 0;
|
|
68245
68759
|
let dimInjectionBlocked = 0;
|
|
68760
|
+
const hookCardIds = new Set(
|
|
68761
|
+
entries.filter((e) => e.source !== "daemon" && e.cardId).map((e) => e.cardId)
|
|
68762
|
+
);
|
|
68246
68763
|
for (const e of entries) {
|
|
68764
|
+
if (e.source === "daemon" && e.cardId && hookCardIds.has(e.cardId)) continue;
|
|
68247
68765
|
if (superseded.has(supersedeKey(e))) continue;
|
|
68248
68766
|
const view = viewOf(e);
|
|
68249
68767
|
const dateKey = e.ts.slice(0, 10);
|
|
68250
|
-
const userInteracted = e.source === "daemon";
|
|
68768
|
+
const userInteracted = e.decidedBy ? e.decidedBy === "person" : e.source === "daemon";
|
|
68251
68769
|
if (view.observed) {
|
|
68252
68770
|
if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
68253
68771
|
} else if (view.info) {
|
|
@@ -68255,7 +68773,8 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
68255
68773
|
if (view.ran) userApproved++;
|
|
68256
68774
|
else userDenied++;
|
|
68257
68775
|
} else if (view.blocked) {
|
|
68258
|
-
if (e.checkedBy === "timeout")
|
|
68776
|
+
if (e.checkedBy === "timeout" || e.decidedBy === "timer" || e.decidedBy === "abandoned")
|
|
68777
|
+
timedOut++;
|
|
68259
68778
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
68260
68779
|
else if (e.checkedBy === "local-decision") userDenied++;
|
|
68261
68780
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
@@ -69245,7 +69764,7 @@ function registerHealCommand(program2) {
|
|
|
69245
69764
|
init_onboarding();
|
|
69246
69765
|
init_cloud_endpoints();
|
|
69247
69766
|
init_safe_text();
|
|
69248
|
-
import
|
|
69767
|
+
import http6 from "http";
|
|
69249
69768
|
import https8 from "https";
|
|
69250
69769
|
import { URL as URL5 } from "url";
|
|
69251
69770
|
import chalk22 from "chalk";
|
|
@@ -69256,7 +69775,7 @@ function postConnect(url, token) {
|
|
|
69256
69775
|
return new Promise((resolve2, reject) => {
|
|
69257
69776
|
const body = JSON.stringify({ token });
|
|
69258
69777
|
const u = new URL5(url);
|
|
69259
|
-
const lib = u.protocol === "http:" ?
|
|
69778
|
+
const lib = u.protocol === "http:" ? http6 : https8;
|
|
69260
69779
|
const req = lib.request(
|
|
69261
69780
|
{
|
|
69262
69781
|
method: "POST",
|
|
@@ -69395,7 +69914,7 @@ init_safe_text();
|
|
|
69395
69914
|
import readline3 from "readline";
|
|
69396
69915
|
import chalk24 from "chalk";
|
|
69397
69916
|
import { spawn as spawn8 } from "child_process";
|
|
69398
|
-
import { randomUUID as
|
|
69917
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
69399
69918
|
import { execa as execa2 } from "execa";
|
|
69400
69919
|
|
|
69401
69920
|
// src/mcp-gateway/result-scan.ts
|
|
@@ -69661,7 +70180,7 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
69661
70180
|
return false;
|
|
69662
70181
|
}
|
|
69663
70182
|
};
|
|
69664
|
-
const gatewaySessionId = `mcp-gateway-${
|
|
70183
|
+
const gatewaySessionId = `mcp-gateway-${randomUUID9()}`;
|
|
69665
70184
|
const readOnlyTools = /* @__PURE__ */ new Set();
|
|
69666
70185
|
let clientName;
|
|
69667
70186
|
const agentIn = readline3.createInterface({ input: process.stdin, terminal: false });
|
|
@@ -69760,13 +70279,13 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
69760
70279
|
console.error(chalk24.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
69761
70280
|
`));
|
|
69762
70281
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
69763
|
-
const humanDecision = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
70282
|
+
const humanDecision = isHumanDecision(blockedByLabel, result.blockedBy, result.decidedBy);
|
|
69764
70283
|
const aiInstruction = buildNegotiationMessage(
|
|
69765
70284
|
blockedByLabel,
|
|
69766
70285
|
humanDecision,
|
|
69767
70286
|
result.reason,
|
|
69768
70287
|
void 0,
|
|
69769
|
-
{ kind: result.blockKind, path: result.blockedPath }
|
|
70288
|
+
{ kind: result.blockKind, path: result.blockedPath, redirect: !!result.redirect }
|
|
69770
70289
|
);
|
|
69771
70290
|
const errorResponse = {
|
|
69772
70291
|
jsonrpc: "2.0",
|
|
@@ -71033,7 +71552,7 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
71033
71552
|
}
|
|
71034
71553
|
const ungoverned = rows.filter((e) => e.connection === "ungoverned").length;
|
|
71035
71554
|
const unlaunchable = rows.filter((e) => e.connection === "unlaunchable").length;
|
|
71036
|
-
const
|
|
71555
|
+
const pending = rows.filter((e) => e.connection === "pending-launch").length;
|
|
71037
71556
|
if (ungoverned > 0) {
|
|
71038
71557
|
console.error(
|
|
71039
71558
|
chalk26.yellow(
|
|
@@ -71042,9 +71561,9 @@ ${ungoverned} ungoverned \u2014 run ` + chalk26.bold("node9 mcp gateway --all")
|
|
|
71042
71561
|
)
|
|
71043
71562
|
);
|
|
71044
71563
|
}
|
|
71045
|
-
if (
|
|
71564
|
+
if (pending > 0) {
|
|
71046
71565
|
console.error(
|
|
71047
|
-
chalk26.cyan(`${
|
|
71566
|
+
chalk26.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
|
|
71048
71567
|
);
|
|
71049
71568
|
}
|
|
71050
71569
|
if (unlaunchable > 0) {
|