@proagentstore/cli 0.4.86 → 0.4.87
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.
|
@@ -148,6 +148,36 @@ export function parseLocalBrowserEvent(raw) {
|
|
|
148
148
|
event.detail = detail;
|
|
149
149
|
return event;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* A finding, with every free-text part redacted (#947).
|
|
153
|
+
*
|
|
154
|
+
* The gap this closes: the result envelope's `summary` and `error` went through {@link redactText}
|
|
155
|
+
* and its FINDINGS did not. `title`, `evidence` and `detail` were length-capped only, and `fields`
|
|
156
|
+
* was filtered by KEY ({@link SENSITIVE_KEY}) — so a credential-shaped value under an innocuous key
|
|
157
|
+
* (`{"note": "sk-ant-api03-…"}`) survived, and a secret environment value quoted into `evidence`
|
|
158
|
+
* survived verbatim. Both are written by the same CLI, on the same machine, in the same envelope as
|
|
159
|
+
* the summary that WAS cleaned, and findings are the part that persists furthest: the run's result
|
|
160
|
+
* row, the console, MCP, and then the owner's own collection record. #947's observability rule —
|
|
161
|
+
* "do not retain … secret environment values" — was therefore breached on the longest-lived path.
|
|
162
|
+
*
|
|
163
|
+
* One definition, used by the runner (which knows the machine's secret env values and passes them)
|
|
164
|
+
* and again by the API when it parses what arrived (which does not, and catches shapes). Defence in
|
|
165
|
+
* depth on purpose: the runner is where secrets are knowable, and the cloud is where a runner that
|
|
166
|
+
* skipped the step is still caught.
|
|
167
|
+
*/
|
|
168
|
+
export function redactFinding(f, secrets = []) {
|
|
169
|
+
const fields = {};
|
|
170
|
+
for (const [k, v] of Object.entries(f.fields ?? {})) {
|
|
171
|
+
if (SENSITIVE_KEY.test(k))
|
|
172
|
+
continue;
|
|
173
|
+
fields[k] = typeof v === "string" ? redactText(v, secrets) : v;
|
|
174
|
+
}
|
|
175
|
+
return { title: redactText(f.title, secrets), url: f.url, evidence: redactText(f.evidence, secrets), fields };
|
|
176
|
+
}
|
|
177
|
+
/** The same for a source failure's free text; its `reason` is a closed vocabulary and its url is a url. */
|
|
178
|
+
export function redactSourceFailure(f, secrets = []) {
|
|
179
|
+
return f.detail === undefined ? f : { ...f, detail: redactText(f.detail, secrets) };
|
|
180
|
+
}
|
|
151
181
|
/**
|
|
152
182
|
* A validated result envelope, or the reason it is not one.
|
|
153
183
|
*
|
|
@@ -194,7 +224,9 @@ export function parseLocalBrowserResult(raw) {
|
|
|
194
224
|
else if (typeof v === "number" || typeof v === "boolean" || v === null)
|
|
195
225
|
fields[k.slice(0, 100)] = v;
|
|
196
226
|
}
|
|
197
|
-
|
|
227
|
+
// #947: capped and key-filtered is not redacted. A finding is the longest-lived thing a run
|
|
228
|
+
// produces, and its free text was the one part of this envelope nothing cleaned.
|
|
229
|
+
findings.push(redactFinding({ title, url, evidence, fields }));
|
|
198
230
|
}
|
|
199
231
|
const sourceFailures = [];
|
|
200
232
|
for (const [i, f] of o.sourceFailures.entries()) {
|
|
@@ -204,7 +236,7 @@ export function parseLocalBrowserResult(raw) {
|
|
|
204
236
|
if (!url || !reason)
|
|
205
237
|
return { error: `sourceFailures[${i}] needs url and a known reason` };
|
|
206
238
|
const detail = str(r.detail, 1000);
|
|
207
|
-
sourceFailures.push(detail ? { url, reason, detail } : { url, reason });
|
|
239
|
+
sourceFailures.push(redactSourceFailure(detail ? { url, reason, detail } : { url, reason }));
|
|
208
240
|
}
|
|
209
241
|
const out = { runId, outcome, findings, sourceFailures, summary: redactText(str(o.summary, LOCAL_BROWSER_CAPS.text) ?? ""), traceId, engineAuth };
|
|
210
242
|
// Redacted again here, whatever the runner did: an error is CLI output, and CLIs print keys.
|
|
@@ -26,7 +26,7 @@ import { fileURLToPath } from "node:url";
|
|
|
26
26
|
import { RunnerInputError } from "../errors.js";
|
|
27
27
|
import { BrowserBridge, domainWithin } from "./bridge.js";
|
|
28
28
|
import { BRIDGE_ENV } from "./bridge-stdio.js";
|
|
29
|
-
import { LOCAL_BROWSER_AUTH_MODES, LOCAL_BROWSER_ENGINES, LOCAL_BROWSER_TASK_TYPE, redactDetail, redactText, secretEnvValues, } from "./contract.js";
|
|
29
|
+
import { LOCAL_BROWSER_AUTH_MODES, LOCAL_BROWSER_ENGINES, LOCAL_BROWSER_TASK_TYPE, redactDetail, redactFinding, redactSourceFailure, redactText, secretEnvValues, } from "./contract.js";
|
|
30
30
|
import { bridgeUnused, buildEngineSpec, finalText, missingLogin, observedEngineAuth, researchPrompt, signInHelp } from "./engine.js";
|
|
31
31
|
/**
|
|
32
32
|
* Why the run could not be launched, in words the owner can act on (#944).
|
|
@@ -434,8 +434,11 @@ export class LocalBrowserRuntime {
|
|
|
434
434
|
run.result = {
|
|
435
435
|
runId: run.envelope.runId,
|
|
436
436
|
outcome: r.outcome,
|
|
437
|
-
|
|
438
|
-
|
|
437
|
+
// #947: the machine's own secret env values are knowable HERE and nowhere later, so the
|
|
438
|
+
// findings are cleaned with them — the same treatment `summary` and `error` below already
|
|
439
|
+
// had, and the part of this envelope that previously kept whatever the CLI wrote.
|
|
440
|
+
findings: (run.bridge?.findings ?? []).map((f) => redactFinding(f, run.secrets)),
|
|
441
|
+
sourceFailures: (run.bridge?.sourceFailures ?? []).map((f) => redactSourceFailure(f, run.secrets)),
|
|
439
442
|
summary: redactText((r.summary ?? "").slice(0, 4000), run.secrets),
|
|
440
443
|
traceId: run.envelope.runId,
|
|
441
444
|
engineAuth: r.engineAuth ?? run.engineAuth,
|