residoo 0.15.0 → 0.16.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 +1 -1
- package/src/integrity.js +73 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "residoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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/integrity.js
CHANGED
|
@@ -677,6 +677,79 @@ function checkIntegrity({ home = os.homedir(), cwd = process.cwd(), projectMode
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
+
// ---- 5. credential-vault file permissions (HOME only, POSIX only) ------
|
|
681
|
+
// GitGuardian's "State of Secrets Sprawl 2026" (blog.gitguardian.com,
|
|
682
|
+
// published 2026-03-17) found 24,008 unique secrets in MCP-related config
|
|
683
|
+
// files on public GitHub, 8.8% of them still live -- these files
|
|
684
|
+
// routinely hold real, working credentials. This does not scan their
|
|
685
|
+
// CONTENT: the files below are the exact "credential VAULTS ... deliberately
|
|
686
|
+
// NOT read" set from agent-configs.js's own header comment, files whose
|
|
687
|
+
// whole documented job is holding the user's own keys/tokens -- reporting
|
|
688
|
+
// their content re-reports what the user put there on purpose. What's
|
|
689
|
+
// checked instead is narrower and complementary: whether the file's own OS
|
|
690
|
+
// permission bits leak that live credential to every other account on the
|
|
691
|
+
// machine. Anthropic's own docs (code.claude.com/docs/en/authentication,
|
|
692
|
+
// "Credential management", fetched 2026-09-04) state Claude Code writes
|
|
693
|
+
// .credentials.json with file mode 0600 on Linux and as the macOS
|
|
694
|
+
// Keychain-write-failure fallback, and that Windows inherits its user
|
|
695
|
+
// profile directory's own access controls. Kiro's own security guidance
|
|
696
|
+
// separately recommends "chmod 600" on its global mcp.json -- the
|
|
697
|
+
// vendor's own admission it holds secrets (already cited in
|
|
698
|
+
// agent-configs.js). Nothing here disputes that these tools do the right
|
|
699
|
+
// thing by default; the point is drift AFTER that -- a WSL DrvFs mount, a
|
|
700
|
+
// naive backup restore, or a shared-volume container mount can each
|
|
701
|
+
// silently widen a file's mode without the tool that wrote it ever
|
|
702
|
+
// knowing, the same way SSH itself checks id_rsa's permissions rather
|
|
703
|
+
// than trusting whoever created it. Windows is skipped entirely, not
|
|
704
|
+
// approximated: Node's fs.Stats.mode on Windows does not reflect NTFS
|
|
705
|
+
// ACLs, so a POSIX-style bit check there would be meaningless rather than
|
|
706
|
+
// merely imprecise. Project mode is skipped too -- none of these are ever
|
|
707
|
+
// project-scoped files by any vendor's own design, so there is no
|
|
708
|
+
// project-relative equivalent to check.
|
|
709
|
+
if (!projectMode && process.platform !== "win32") {
|
|
710
|
+
const geminiDir = process.env.GEMINI_CLI_HOME
|
|
711
|
+
? path.join(process.env.GEMINI_CLI_HOME, ".gemini")
|
|
712
|
+
: path.join(home, ".gemini");
|
|
713
|
+
const credentialVaultFiles = [
|
|
714
|
+
{
|
|
715
|
+
file: path.join(process.env.CLAUDE_CONFIG_DIR || path.join(home, ".claude"), ".credentials.json"),
|
|
716
|
+
note: "Claude Code's OAuth login (code.claude.com/docs/en/authentication documents file mode 0600)",
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
file: path.join(process.env.CODEX_HOME || path.join(home, ".codex"), "auth.json"),
|
|
720
|
+
note: "Codex CLI's own auth store",
|
|
721
|
+
},
|
|
722
|
+
{ file: path.join(geminiDir, "oauth_creds.json"), note: "Gemini CLI's own OAuth store" },
|
|
723
|
+
{ file: path.join(geminiDir, ".env"), note: "Gemini CLI's own credential env file" },
|
|
724
|
+
{
|
|
725
|
+
file: path.join(home, ".kiro", "settings", "mcp.json"),
|
|
726
|
+
note: "Kiro's own security guidance recommends chmod 600 on this file",
|
|
727
|
+
},
|
|
728
|
+
];
|
|
729
|
+
for (const { file, note } of credentialVaultFiles) {
|
|
730
|
+
const r = path.resolve(file);
|
|
731
|
+
if (seen.has(r)) continue;
|
|
732
|
+
seen.add(r);
|
|
733
|
+
let stat;
|
|
734
|
+
try { stat = fs.statSync(file); }
|
|
735
|
+
catch (err) {
|
|
736
|
+
mark(file, err && err.code === "ENOENT" ? "absent" : "unreadable");
|
|
737
|
+
if (!(err && err.code === "ENOENT")) {
|
|
738
|
+
add("warn", "unreadable-config", file, "credential file exists but its permissions could not be checked (stat failed): unverified, not clean");
|
|
739
|
+
}
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
mark(file, "checked");
|
|
743
|
+
const openBits = stat.mode & 0o077;
|
|
744
|
+
if (openBits !== 0) {
|
|
745
|
+
const octal = (stat.mode & 0o777).toString(8).padStart(3, "0");
|
|
746
|
+
const shown = display(file);
|
|
747
|
+
add("warn", "insecure-credential-permissions", file,
|
|
748
|
+
`${note}; current mode ${octal} grants group and/or other accounts on this machine access to a live credential. Fix: chmod 600 "${shown}"`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
680
753
|
return {
|
|
681
754
|
findings,
|
|
682
755
|
filesChecked,
|