residoo 0.4.7 → 0.4.9
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/README.md +4 -4
- package/package.json +1 -1
- package/src/scan.js +53 -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.
|
|
198
|
+
scanning starts (`residoo v0.4.9 · 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.
|
|
201
|
+
it was run with (`residoo v0.4.9 · 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.
|
|
432
|
+
- uses: dandovdub/residoo@v0.4.9
|
|
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.
|
|
440
|
+
rev: v0.4.9
|
|
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.
|
|
3
|
+
"version": "0.4.9",
|
|
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,68 @@ 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
|
+
//
|
|
656
|
+
// Three columns, not two: an earlier version of this table showed only
|
|
657
|
+
// the vendor name ("AWS 2"), which answers "how many" but drops the
|
|
658
|
+
// one thing a security-conscious user actually wants to check before
|
|
659
|
+
// agreeing to real outbound calls, namely WHICH endpoint each vendor's
|
|
660
|
+
// credential gets sent to. Every vendor's own SIMPLE_VERIFY_VENDOR_LABEL
|
|
661
|
+
// is already written "<Vendor>'s <endpoint>", so split it into the two
|
|
662
|
+
// columns rather than maintaining a second, parallel description.
|
|
663
|
+
const rows = [];
|
|
664
|
+
if (pendingAwsVerifications.size > 0 && awsAvailable) {
|
|
665
|
+
rows.push(["AWS", "sts:get-caller-identity", pendingAwsVerifications.size]);
|
|
666
|
+
}
|
|
667
|
+
if (pendingPlanetScaleVerifications.size > 0) {
|
|
668
|
+
rows.push(["PlanetScale", "organizations endpoint", pendingPlanetScaleVerifications.size]);
|
|
669
|
+
}
|
|
670
|
+
for (const [ruleId, byValue] of pendingSimpleVerifications) {
|
|
671
|
+
if (byValue.size === 0) continue;
|
|
672
|
+
const [vendor, endpoint] = SIMPLE_VERIFY_VENDOR_LABEL[ruleId].split("'s ");
|
|
673
|
+
rows.push([vendor, endpoint, byValue.size]);
|
|
674
|
+
}
|
|
675
|
+
if (rows.length > 0) {
|
|
676
|
+
const vendorWidth = Math.max(...rows.map(([vendor]) => vendor.length));
|
|
677
|
+
const endpointWidth = Math.max(...rows.map(([, endpoint]) => endpoint.length));
|
|
678
|
+
const table = rows
|
|
679
|
+
.map(([vendor, endpoint, count]) => ` ${vendor.padEnd(vendorWidth)} ${endpoint.padEnd(endpointWidth)} ${count}`)
|
|
680
|
+
.join("\n");
|
|
681
|
+
process.stderr.write(
|
|
682
|
+
"residoo --verify: checking whether these credentials are still active. Real network " +
|
|
683
|
+
"calls, using the exact value found in your transcript, one at a time. Nothing is cached " +
|
|
684
|
+
"or sent anywhere but the endpoint listed below.\n\n" +
|
|
685
|
+
table + "\n\n"
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (pendingAwsVerifications.size > 0 && !awsAvailable) {
|
|
689
|
+
process.stderr.write(
|
|
690
|
+
"residoo --verify: the aws CLI was not found on PATH, so the " +
|
|
691
|
+
`${pendingAwsVerifications.size} AWS credential(s) found in this scan could not be checked. ` +
|
|
692
|
+
"Install it (https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to use --verify.\n"
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
644
696
|
if (verify && pendingAwsVerifications.size > 0) {
|
|
645
697
|
const applyPair = (refs, result) => {
|
|
646
698
|
for (const ref of refs) {
|
|
647
699
|
applyVerifyResult([ref.akiaFinding, ref.secretFinding], result);
|
|
648
700
|
}
|
|
649
701
|
};
|
|
650
|
-
if (!
|
|
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
|
-
);
|
|
702
|
+
if (!awsAvailable) {
|
|
656
703
|
const result = { status: "error", detail: "aws CLI not found on PATH" };
|
|
657
704
|
for (const { refs } of pendingAwsVerifications.values()) applyPair(refs, result);
|
|
658
705
|
} 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
706
|
for (const [accessKeyValue, { secretValue, refs }] of pendingAwsVerifications) {
|
|
665
707
|
const result = verifyAwsCredential(accessKeyValue, secretValue);
|
|
666
708
|
applyPair(refs, result);
|
|
@@ -668,11 +710,6 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
|
|
|
668
710
|
}
|
|
669
711
|
}
|
|
670
712
|
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
713
|
for (const [secretValue, { idValue, refs }] of pendingPlanetScaleVerifications) {
|
|
677
714
|
const result = await verifyPlanetScaleToken(idValue, secretValue);
|
|
678
715
|
for (const ref of refs) applyVerifyResult([ref.secretFinding, ref.idFinding], result);
|
|
@@ -682,11 +719,6 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
|
|
|
682
719
|
for (const [ruleId, byValue] of pendingSimpleVerifications) {
|
|
683
720
|
if (byValue.size === 0) continue;
|
|
684
721
|
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
722
|
for (const [value, { refs }] of byValue) {
|
|
691
723
|
const result = await verifyFn(value);
|
|
692
724
|
applyVerifyResult(refs, result);
|