@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
- findings.push({ title, url, evidence, fields });
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
- findings: run.bridge?.findings ?? [],
438
- sourceFailures: run.bridge?.sourceFailures ?? [],
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.86",
3
+ "version": "0.4.87",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",