residoo 0.4.7 → 0.4.8

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.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/src/scan.js +46 -21
package/README.md CHANGED
@@ -195,10 +195,10 @@ won't be built into the tool that writes it.
195
195
  preview, never the real value, including in `--json` mode. A decoded or
196
196
  rejoined secret is redacted exactly like a plain one.
197
197
  - On an interactive terminal, prints who it is and where it lives before
198
- scanning starts (`residoo v0.4.7 · find secrets your AI coding agent left
198
+ scanning starts (`residoo v0.4.8 · find secrets your AI coding agent left
199
199
  on disk` plus the repo URL), then a live spinner naming the current file
200
200
  as it scans. Every report also opens with the exact version and timestamp
201
- it was run with (`residoo v0.4.7 · scanned 2026-01-01 12:00`; `--json`
201
+ it was run with (`residoo v0.4.8 · scanned 2026-01-01 12:00`; `--json`
202
202
  carries the same as `residooVersion`/`scannedAt`), so a report pasted or
203
203
  screenshotted later never leaves you guessing which build produced it.
204
204
  When there are findings, the report closes with a "Next steps" pointer to
@@ -429,7 +429,7 @@ As a GitHub Action (this repository doubles as a composite action):
429
429
  ```yaml
430
430
  steps:
431
431
  - uses: actions/checkout@v4
432
- - uses: dandovdub/residoo@v0.4.7
432
+ - uses: dandovdub/residoo@v0.4.8
433
433
  ```
434
434
 
435
435
  As a pre-commit hook:
@@ -437,7 +437,7 @@ As a pre-commit hook:
437
437
  ```yaml
438
438
  repos:
439
439
  - repo: https://github.com/dandovdub/residoo
440
- rev: v0.4.7
440
+ rev: v0.4.8
441
441
  hooks:
442
442
  - id: residoo
443
443
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "residoo",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
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/scan.js CHANGED
@@ -641,26 +641,61 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
641
641
  ref.verifiedDetail = result.detail;
642
642
  }
643
643
  };
644
+ const awsAvailable = pendingAwsVerifications.size === 0 || isAwsCliAvailable();
645
+ if (verify && anyPending) {
646
+ // One disclosure, not one per vendor: this used to print a full
647
+ // "this is a real network request..." paragraph for EACH vendor in
648
+ // turn, so a scan touching nine vendors put nine near-identical
649
+ // paragraphs on screen before any results — a wall of repeated
650
+ // boilerplate, caught live as bad UX. The safety-relevant fact (real
651
+ // outbound calls, using the exact matched value, one at a time) only
652
+ // needs saying once; per-vendor detail becomes a compact count table.
653
+ // Deliberately not gated on isTTY like the scan spinner: a script
654
+ // piping through a pager or into a log still needs this disclosure.
655
+ const rows = [];
656
+ if (pendingAwsVerifications.size > 0 && awsAvailable) {
657
+ rows.push(["AWS", pendingAwsVerifications.size]);
658
+ }
659
+ if (pendingPlanetScaleVerifications.size > 0) {
660
+ rows.push(["PlanetScale", pendingPlanetScaleVerifications.size]);
661
+ }
662
+ for (const [ruleId, byValue] of pendingSimpleVerifications) {
663
+ if (byValue.size === 0) continue;
664
+ // Vendor labels are consistently written "<Vendor>'s <endpoint
665
+ // description>" (see SIMPLE_VERIFY_VENDOR_LABEL above); take the
666
+ // part before "'s" rather than maintaining a second, parallel map
667
+ // of the same names.
668
+ const vendor = SIMPLE_VERIFY_VENDOR_LABEL[ruleId].split("'s ")[0];
669
+ rows.push([vendor, byValue.size]);
670
+ }
671
+ if (rows.length > 0) {
672
+ const width = Math.max(...rows.map(([label]) => label.length));
673
+ const table = rows.map(([label, count]) => ` ${label.padEnd(width)} ${count}`).join("\n");
674
+ process.stderr.write(
675
+ "residoo --verify: checking whether these credentials are still active. Real network " +
676
+ "calls, one per vendor's own API, using the exact value found in your transcript, one at " +
677
+ "a time. Nothing is cached or sent anywhere but the vendor that issued it.\n\n" +
678
+ table + "\n\n"
679
+ );
680
+ }
681
+ if (pendingAwsVerifications.size > 0 && !awsAvailable) {
682
+ process.stderr.write(
683
+ "residoo --verify: the aws CLI was not found on PATH, so the " +
684
+ `${pendingAwsVerifications.size} AWS credential(s) found in this scan could not be checked. ` +
685
+ "Install it (https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to use --verify.\n"
686
+ );
687
+ }
688
+ }
644
689
  if (verify && pendingAwsVerifications.size > 0) {
645
690
  const applyPair = (refs, result) => {
646
691
  for (const ref of refs) {
647
692
  applyVerifyResult([ref.akiaFinding, ref.secretFinding], result);
648
693
  }
649
694
  };
650
- if (!isAwsCliAvailable()) {
651
- process.stderr.write(
652
- "residoo --verify: the aws CLI was not found on PATH, so the " +
653
- `${pendingAwsVerifications.size} AWS credential(s) found in this scan could not be checked. ` +
654
- "Install it (https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to use --verify.\n"
655
- );
695
+ if (!awsAvailable) {
656
696
  const result = { status: "error", detail: "aws CLI not found on PATH" };
657
697
  for (const { refs } of pendingAwsVerifications.values()) applyPair(refs, result);
658
698
  } else {
659
- process.stderr.write(
660
- `residoo --verify: calling AWS sts:get-caller-identity for ${pendingAwsVerifications.size} ` +
661
- "credential(s) found in this scan. This is a real network request to AWS, using the exact " +
662
- "credential found in your transcript, one at a time.\n"
663
- );
664
699
  for (const [accessKeyValue, { secretValue, refs }] of pendingAwsVerifications) {
665
700
  const result = verifyAwsCredential(accessKeyValue, secretValue);
666
701
  applyPair(refs, result);
@@ -668,11 +703,6 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
668
703
  }
669
704
  }
670
705
  if (verify && pendingPlanetScaleVerifications.size > 0) {
671
- process.stderr.write(
672
- `residoo --verify: calling PlanetScale's organizations endpoint for ${pendingPlanetScaleVerifications.size} ` +
673
- "credential(s) found in this scan. This is a real network request to PlanetScale, using the exact " +
674
- "credential found in your transcript, one at a time.\n"
675
- );
676
706
  for (const [secretValue, { idValue, refs }] of pendingPlanetScaleVerifications) {
677
707
  const result = await verifyPlanetScaleToken(idValue, secretValue);
678
708
  for (const ref of refs) applyVerifyResult([ref.secretFinding, ref.idFinding], result);
@@ -682,11 +712,6 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
682
712
  for (const [ruleId, byValue] of pendingSimpleVerifications) {
683
713
  if (byValue.size === 0) continue;
684
714
  const verifyFn = SIMPLE_VERIFY_FNS[ruleId];
685
- process.stderr.write(
686
- `residoo --verify: calling ${SIMPLE_VERIFY_VENDOR_LABEL[ruleId]} for ${byValue.size} ` +
687
- "token(s) found in this scan. This is a real network request, using the exact " +
688
- "token found in your transcript, one at a time.\n"
689
- );
690
715
  for (const [value, { refs }] of byValue) {
691
716
  const result = await verifyFn(value);
692
717
  applyVerifyResult(refs, result);