residoo 0.4.11 → 0.4.12
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 +2 -2
- package/package.json +1 -1
- package/src/scan.js +67 -10
package/README.md
CHANGED
|
@@ -265,7 +265,7 @@ As a GitHub Action (this repo doubles as a composite action):
|
|
|
265
265
|
```yaml
|
|
266
266
|
steps:
|
|
267
267
|
- uses: actions/checkout@v4
|
|
268
|
-
- uses: dandovdub/residoo@v0.4.
|
|
268
|
+
- uses: dandovdub/residoo@v0.4.12
|
|
269
269
|
```
|
|
270
270
|
|
|
271
271
|
As a pre-commit hook:
|
|
@@ -273,7 +273,7 @@ As a pre-commit hook:
|
|
|
273
273
|
```yaml
|
|
274
274
|
repos:
|
|
275
275
|
- repo: https://github.com/dandovdub/residoo
|
|
276
|
-
rev: v0.4.
|
|
276
|
+
rev: v0.4.12
|
|
277
277
|
hooks:
|
|
278
278
|
- id: residoo
|
|
279
279
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "residoo",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
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
|
@@ -237,6 +237,16 @@ const VENDOR_EXAMPLE_VALUES = new Set([
|
|
|
237
237
|
/** Matches every finding's own `relFile` convention — never the full path. See SECURITY.md. */
|
|
238
238
|
function safeName(file) { return path.basename(file); }
|
|
239
239
|
|
|
240
|
+
// Same format as report.js's own localTimestamp, duplicated rather than
|
|
241
|
+
// imported: this is a 3-line pure function, and report.js is the
|
|
242
|
+
// presentation layer for stdout while this file's own --verify results
|
|
243
|
+
// table is stderr, the same reasoning pairing.js gives for its own small
|
|
244
|
+
// duplicated helper (looksZeroEntropy) rather than cross-importing.
|
|
245
|
+
function localTimestamp(d) {
|
|
246
|
+
const p2 = (n) => String(n).padStart(2, "0");
|
|
247
|
+
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())} ${p2(d.getHours())}:${p2(d.getMinutes())}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
240
250
|
/**
|
|
241
251
|
* Scan every transcript from every available source.
|
|
242
252
|
*
|
|
@@ -705,6 +715,11 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
|
|
|
705
715
|
// file while stderr still reaches a real terminal is a real case: `scan
|
|
706
716
|
// --verify --json > out.json` should still color this table).
|
|
707
717
|
const paint = makePaint(noColor, process.stderr);
|
|
718
|
+
// Populated inside the disclosure block below (when there's something to
|
|
719
|
+
// verify), then read again once every verify loop below has finished, to
|
|
720
|
+
// print the results table. Declared out here, not inside that block, so
|
|
721
|
+
// it survives to that second read.
|
|
722
|
+
let verifyRows = [];
|
|
708
723
|
if (verify && anyPending) {
|
|
709
724
|
// One disclosure, not one per vendor: this used to print a full
|
|
710
725
|
// "this is a real network request..." paragraph for EACH vendor in
|
|
@@ -724,26 +739,37 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
|
|
|
724
739
|
// everywhere else in the report — reusing redact() on the same raw
|
|
725
740
|
// value record() was called with, not a second display convention.
|
|
726
741
|
// One line per vendor+endpoint header, one indented line per credential.
|
|
727
|
-
|
|
742
|
+
//
|
|
743
|
+
// Each row also carries a `resultFinding` per credential: a direct
|
|
744
|
+
// reference to the finding object applyVerifyResult mutates below (the
|
|
745
|
+
// access-key/secret finding for AWS, the secret finding for
|
|
746
|
+
// PlanetScale/MongoDB Atlas, the finding itself for a simple vendor).
|
|
747
|
+
// Captured now, read after the verify loops run, so the results table
|
|
748
|
+
// further down needs no second vendor-shape dispatch of its own.
|
|
749
|
+
verifyRows = [];
|
|
728
750
|
if (pendingAwsVerifications.size > 0 && awsAvailable) {
|
|
729
|
-
|
|
751
|
+
verifyRows.push(["AWS", "sts:get-caller-identity",
|
|
752
|
+
[...pendingAwsVerifications.entries()].map(([value, { refs }]) => ({ value, resultFinding: refs[0].akiaFinding }))]);
|
|
730
753
|
}
|
|
731
754
|
if (pendingPlanetScaleVerifications.size > 0) {
|
|
732
|
-
|
|
755
|
+
verifyRows.push(["PlanetScale", "organizations endpoint",
|
|
756
|
+
[...pendingPlanetScaleVerifications.entries()].map(([value, { refs }]) => ({ value, resultFinding: refs[0].secretFinding }))]);
|
|
733
757
|
}
|
|
734
758
|
if (pendingMongoDbAtlasVerifications.size > 0) {
|
|
735
|
-
|
|
759
|
+
verifyRows.push(["MongoDB Atlas", "oauth/token endpoint",
|
|
760
|
+
[...pendingMongoDbAtlasVerifications.entries()].map(([value, { refs }]) => ({ value, resultFinding: refs[0].secretFinding }))]);
|
|
736
761
|
}
|
|
737
762
|
for (const [ruleId, byValue] of pendingSimpleVerifications) {
|
|
738
763
|
if (byValue.size === 0) continue;
|
|
739
764
|
const [vendor, endpoint] = SIMPLE_VERIFY_VENDOR_LABEL[ruleId].split("'s ");
|
|
740
|
-
|
|
765
|
+
verifyRows.push([vendor, endpoint,
|
|
766
|
+
[...byValue.entries()].map(([value, { refs }]) => ({ value, resultFinding: refs[0] }))]);
|
|
741
767
|
}
|
|
742
|
-
if (
|
|
743
|
-
const table =
|
|
744
|
-
.map(([vendor, endpoint,
|
|
745
|
-
` ${paint(c.bold, vendor)} ${paint(c.dim, "·")} ${paint(c.dim, endpoint)}\n` +
|
|
746
|
-
|
|
768
|
+
if (verifyRows.length > 0) {
|
|
769
|
+
const table = verifyRows
|
|
770
|
+
.map(([vendor, endpoint, credentials]) =>
|
|
771
|
+
` ${paint(c.bold + c.cyan, vendor)} ${paint(c.dim, "·")} ${paint(c.dim, endpoint)}\n` +
|
|
772
|
+
credentials.map(({ value }) => ` ${redact(value)}`).join("\n"))
|
|
747
773
|
.join("\n");
|
|
748
774
|
process.stderr.write(
|
|
749
775
|
paint(c.yellow + c.bold, "residoo --verify:") +
|
|
@@ -801,6 +827,37 @@ async function scan({ sources, includeNoisy = false, includeSuppressed = false,
|
|
|
801
827
|
}
|
|
802
828
|
}
|
|
803
829
|
|
|
830
|
+
if (verify && verifyRows.length > 0) {
|
|
831
|
+
// Same vendor/endpoint grouping as the disclosure table above, now
|
|
832
|
+
// showing what each call actually found. An ACTIVE credential is a
|
|
833
|
+
// real, present-tense risk, colored the same red/bold the Rotation
|
|
834
|
+
// section below uses for the identical fact ("rotate immediately");
|
|
835
|
+
// "could not verify" gets the same red/bold too, on purpose, matching
|
|
836
|
+
// this project's fail-safe-direction policy of treating "unknown" as
|
|
837
|
+
// "assume risk" rather than as reassuring silence. Stamped with when
|
|
838
|
+
// this check actually ran, so a report read later (pasted into a
|
|
839
|
+
// ticket, screenshotted) doesn't silently imply "still true right now."
|
|
840
|
+
const checkedAt = localTimestamp(new Date());
|
|
841
|
+
const describeResult = (finding) => {
|
|
842
|
+
if (finding.verified === "active") {
|
|
843
|
+
return paint(c.red + c.bold, `⚠ ACTIVE: real working credential`) + paint(c.dim, ` (checked ${checkedAt})`);
|
|
844
|
+
}
|
|
845
|
+
if (finding.verified === "invalid") {
|
|
846
|
+
return paint(c.green, `✓ inactive: vendor rejected it`) + paint(c.dim, ` (checked ${checkedAt})`);
|
|
847
|
+
}
|
|
848
|
+
return paint(c.red + c.bold, `⚠ could not verify`) + paint(c.dim, ` (checked ${checkedAt}${finding.verifiedDetail ? `: ${finding.verifiedDetail}` : ""})`);
|
|
849
|
+
};
|
|
850
|
+
const resultsTable = verifyRows
|
|
851
|
+
.map(([vendor, endpoint, credentials]) =>
|
|
852
|
+
` ${paint(c.bold + c.cyan, vendor)} ${paint(c.dim, "·")} ${paint(c.dim, endpoint)}\n` +
|
|
853
|
+
credentials.map(({ value, resultFinding }) => ` ${redact(value)} ${describeResult(resultFinding)}`).join("\n"))
|
|
854
|
+
.join("\n");
|
|
855
|
+
process.stderr.write(
|
|
856
|
+
paint(c.yellow + c.bold, "residoo --verify:") + " results\n\n" +
|
|
857
|
+
resultsTable + "\n\n"
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
804
861
|
const distinctCounts = {};
|
|
805
862
|
for (const [ruleId, set] of distinctByRule) distinctCounts[ruleId] = set.size;
|
|
806
863
|
return { findings, filesScanned, sourcesScanned, bytesScanned, suppressedCount, distinctCounts, unreadableFiles };
|