residoo 0.14.0 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "residoo",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Find secrets leaking through your AI coding agent's session history. Zero network calls in the scan path, zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "CloudRoam (https://cloudroam.io)",
package/src/cli.js CHANGED
@@ -175,7 +175,8 @@ Watch:
175
175
  --verify same opt-in vendor check as scan --verify,
176
176
  applied to each newly found credential once,
177
177
  never to one already seen
178
- --include-noisy, --include-suppressed, --no-color same meaning as scan
178
+ --include-noisy, --include-suppressed, --include-pii, --no-color
179
+ same meaning as scan
179
180
  Ctrl+C stops cleanly and prints a session summary (skipped with --json,
180
181
  where the same information is one final NDJSON event).
181
182
 
@@ -663,6 +664,7 @@ async function runWatch(args) {
663
664
  const includeSuppressed = args.includes("--include-suppressed");
664
665
  const verify = args.includes("--verify");
665
666
  const noColor = args.includes("--no-color");
667
+ const includePii = args.includes("--include-pii");
666
668
 
667
669
  let intervalSeconds = 5;
668
670
  const intervalArg = argValue(args, "--interval");
@@ -688,7 +690,7 @@ async function runWatch(args) {
688
690
 
689
691
  const { promise, stop } = startWatch({
690
692
  sources,
691
- options: { includeNoisy, includeSuppressed, verify, noColor, json: wantsJson, pollMs: intervalSeconds * 1000 },
693
+ options: { includeNoisy, includeSuppressed, verify, noColor, includePii, json: wantsJson, pollMs: intervalSeconds * 1000 },
692
694
  });
693
695
 
694
696
  const printFinalSummary = (stats) => {
package/src/mcpTools.js CHANGED
@@ -50,11 +50,20 @@ function rejectUnknownKeys(args, allowed) {
50
50
  return errs;
51
51
  }
52
52
 
53
- /** Shared arg shape for residoo_scan/residoo_check: includeNoisy, includeSuppressed, maxEntries. */
53
+ /**
54
+ * Shared arg shape for residoo_scan/residoo_check: includeNoisy,
55
+ * includeSuppressed, includePii, maxEntries. includePii is exposed here
56
+ * (unlike ocr or verify, see this file's own header comment on verify's
57
+ * exclusion) because it is architecturally identical to includeNoisy --
58
+ * local-only, no network call, no external process, just a different
59
+ * detection category (see pii.js) -- not the network/live-secret trust
60
+ * boundary verify's own exclusion is specifically about.
61
+ */
54
62
  function validateSweepArgs(args, allowedKeys) {
55
63
  const errs = rejectUnknownKeys(args, allowedKeys);
56
64
  if (args.includeNoisy !== undefined && typeof args.includeNoisy !== "boolean") errs.push("includeNoisy must be a boolean");
57
65
  if (args.includeSuppressed !== undefined && typeof args.includeSuppressed !== "boolean") errs.push("includeSuppressed must be a boolean");
66
+ if (args.includePii !== undefined && typeof args.includePii !== "boolean") errs.push("includePii must be a boolean");
58
67
  let maxEntries = 25;
59
68
  if (args.maxEntries !== undefined) {
60
69
  if (typeof args.maxEntries !== "number" || !Number.isInteger(args.maxEntries) || args.maxEntries < 1 || args.maxEntries > 200) {
@@ -63,7 +72,10 @@ function validateSweepArgs(args, allowedKeys) {
63
72
  maxEntries = args.maxEntries;
64
73
  }
65
74
  }
66
- return { errs, includeNoisy: args.includeNoisy === true, includeSuppressed: args.includeSuppressed === true, maxEntries };
75
+ return {
76
+ errs, includeNoisy: args.includeNoisy === true, includeSuppressed: args.includeSuppressed === true,
77
+ includePii: args.includePii === true, maxEntries,
78
+ };
67
79
  }
68
80
 
69
81
  /** Drop the full step-by-step runbook (redundant once per shared rule id across many entries -- call residoo_explain for that) and any null-valued optional field. */
@@ -122,8 +134,8 @@ function buildTools({ sources }) {
122
134
  let checkStarted = false;
123
135
 
124
136
  async function handleScan(args) {
125
- const SCAN_KEYS = new Set(["projectDir", "includeNoisy", "includeSuppressed", "maxEntries"]);
126
- const { errs, includeNoisy, includeSuppressed, maxEntries } = validateSweepArgs(args, SCAN_KEYS);
137
+ const SCAN_KEYS = new Set(["projectDir", "includeNoisy", "includeSuppressed", "includePii", "maxEntries"]);
138
+ const { errs, includeNoisy, includeSuppressed, includePii, maxEntries } = validateSweepArgs(args, SCAN_KEYS);
127
139
  if (args.projectDir !== undefined && typeof args.projectDir !== "string") errs.push("projectDir must be a string");
128
140
  if (errs.length) return errorResult(`Invalid arguments: ${errs.join("; ")}`);
129
141
 
@@ -140,7 +152,7 @@ function buildTools({ sources }) {
140
152
  scanSources = sources;
141
153
  }
142
154
 
143
- const result = await scan({ sources: scanSources, includeNoisy, includeSuppressed, verify: false, noColor: true });
155
+ const result = await scan({ sources: scanSources, includeNoisy, includeSuppressed, includePii, verify: false, noColor: true });
144
156
  const acks = loadAcks();
145
157
  const dismissed = loadDismissed();
146
158
  const rotation = renderRotation(result.findings, acks, dismissed);
@@ -167,8 +179,8 @@ function buildTools({ sources }) {
167
179
  }
168
180
 
169
181
  async function handleCheck(args) {
170
- const CHECK_KEYS = new Set(["includeNoisy", "includeSuppressed", "maxEntries"]);
171
- const { errs, includeNoisy, includeSuppressed, maxEntries } = validateSweepArgs(args, CHECK_KEYS);
182
+ const CHECK_KEYS = new Set(["includeNoisy", "includeSuppressed", "includePii", "maxEntries"]);
183
+ const { errs, includeNoisy, includeSuppressed, includePii, maxEntries } = validateSweepArgs(args, CHECK_KEYS);
172
184
  if (errs.length) return errorResult(`Invalid arguments: ${errs.join("; ")}`);
173
185
 
174
186
  const firstCheckThisSession = !checkStarted;
@@ -179,7 +191,7 @@ function buildTools({ sources }) {
179
191
  const emit = (e) => events.push(e);
180
192
  const stats = await sweepOnce({
181
193
  sources, tracked: checkTracked, seen: checkSeen, ledger,
182
- options: { includeNoisy, includeSuppressed, verify: false, noColor: true }, emit,
194
+ options: { includeNoisy, includeSuppressed, includePii, verify: false, noColor: true }, emit,
183
195
  });
184
196
 
185
197
  const allNew = events.filter((e) => e.type === "finding");
@@ -353,6 +365,7 @@ function buildTools({ sources }) {
353
365
  projectDir: { type: "string", description: "Absolute path to a project/repo directory to scan instead of the machine-wide transcript stores (same as `residoo scan --project <dir>`). Omit for the default machine-wide scan." },
354
366
  includeNoisy: { type: "boolean", default: false, description: "Also run residoo's two low-confidence heuristic rules (generic password/secret assignments) -- catches more, false-positives more. Off by default." },
355
367
  includeSuppressed: { type: "boolean", default: false, description: "Include matches normally hidden because they look like vendor-documented example values or placeholder text. Off by default." },
368
+ includePii: { type: "boolean", default: false, description: "Also scan for PII (US Social Security Numbers, Luhn-validated credit card numbers, checksum-validated IBANs) -- a different risk category from a credential, not a lower confidence bar. Off by default; residoo is deliberately credentials-only otherwise." },
356
369
  maxEntries: { type: "integer", minimum: 1, maximum: 200, default: 25, description: "Cap on distinct findings returned in full detail, pending-first. Counts in the response are always exact even when the entry list is truncated." },
357
370
  },
358
371
  required: [],
@@ -368,6 +381,7 @@ function buildTools({ sources }) {
368
381
  properties: {
369
382
  includeNoisy: { type: "boolean", default: false, description: "Same meaning as residoo_scan." },
370
383
  includeSuppressed: { type: "boolean", default: false, description: "Same meaning as residoo_scan." },
384
+ includePii: { type: "boolean", default: false, description: "Same meaning as residoo_scan." },
371
385
  maxEntries: { type: "integer", minimum: 1, maximum: 200, default: 25, description: "Cap on new findings / re-exposures returned in full detail. Counts are always exact even when truncated." },
372
386
  },
373
387
  required: [],
package/src/watch.js CHANGED
@@ -266,14 +266,14 @@ function makeSyntheticSource(realId, batchesByFile) {
266
266
  * `verify` is always forced off here: seeding a dedup cache must never be
267
267
  * the reason a live vendor API gets hit.
268
268
  */
269
- async function baselineSeed(source, sourceId, file, sizeBytes, mtimeMs, seen, includeNoisy, includeSuppressed, noColor) {
269
+ async function baselineSeed(source, sourceId, file, sizeBytes, mtimeMs, seen, includeNoisy, includeSuppressed, noColor, includePii) {
270
270
  const batch = await readWholeFile(source, file, sizeBytes, mtimeMs);
271
271
  if (!batch) return;
272
272
  let result;
273
273
  try {
274
274
  result = await scan({
275
275
  sources: [makeSyntheticSource(sourceId, new Map([[file, batch]]))],
276
- includeNoisy, includeSuppressed, verify: false, noColor,
276
+ includeNoisy, includeSuppressed, verify: false, noColor, includePii,
277
277
  });
278
278
  } catch {
279
279
  return; // best-effort: a failure here just leaves this file's dedup
@@ -299,7 +299,7 @@ async function baselineSeed(source, sourceId, file, sizeBytes, mtimeMs, seen, in
299
299
  * `dismiss` takes effect without a restart.
300
300
  */
301
301
  async function sweepOnce({ sources, tracked, seen, ledger, options, emit }) {
302
- const { includeNoisy, includeSuppressed, verify, noColor } = options || {};
302
+ const { includeNoisy, includeSuppressed, verify, noColor, includePii } = options || {};
303
303
  let loud = 0;
304
304
  let quiet = 0;
305
305
  let suppressedByLedger = 0;
@@ -363,7 +363,7 @@ async function sweepOnce({ sources, tracked, seen, ledger, options, emit }) {
363
363
  contentHash: tailable ? null : wholeFileHash(file),
364
364
  });
365
365
  if (!tailable) {
366
- await baselineSeed(source, sourceId, file, sizeBytes, mtimeMs, seen, includeNoisy, includeSuppressed, noColor);
366
+ await baselineSeed(source, sourceId, file, sizeBytes, mtimeMs, seen, includeNoisy, includeSuppressed, noColor, includePii);
367
367
  }
368
368
  continue;
369
369
  }
@@ -432,7 +432,7 @@ async function sweepOnce({ sources, tracked, seen, ledger, options, emit }) {
432
432
  try {
433
433
  result = await scan({
434
434
  sources: [makeSyntheticSource(sourceId, batchesByFile)],
435
- includeNoisy, includeSuppressed, verify, noColor,
435
+ includeNoisy, includeSuppressed, verify, noColor, includePii,
436
436
  });
437
437
  } catch (err) {
438
438
  emit({ type: "watch-error", at: new Date(), source: sourceId, detail: "scan failed: " + (err && err.message) });