residoo 0.10.0 → 0.12.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/README.md +7 -4
- package/package.json +1 -1
- package/src/cli.js +40 -17
- package/src/guard.js +102 -13
- package/src/patterns.js +43 -0
- package/src/rotation.js +52 -0
- package/src/scan.js +6 -1
package/README.md
CHANGED
|
@@ -51,8 +51,10 @@ into the conversation at all — which also means a long session compacting
|
|
|
51
51
|
away the exact value you pasted days ago can't force you to paste it
|
|
52
52
|
again, since there's nothing to lose. `residoo guard` blocks an obviously
|
|
53
53
|
sensitive file read before it happens (100% recall, 0% false positives on
|
|
54
|
-
its own [scored 81-case corpus](bench/guard/RESULTS.md))
|
|
55
|
-
|
|
54
|
+
its own [scored 81-case corpus](bench/guard/RESULTS.md)) and, via a second
|
|
55
|
+
hook, a secret typed directly into the prompt itself — confirmed against
|
|
56
|
+
Claude Code's own docs to block before the model ever processes it. All
|
|
57
|
+
four are covered in [docs/features.md](docs/features.md).
|
|
56
58
|
|
|
57
59
|
> [!NOTE]
|
|
58
60
|
> gitleaks and trufflehog scan **commits**. residoo scans the **conversation
|
|
@@ -97,7 +99,7 @@ while losing rows, then fixed in public against the classes it was losing
|
|
|
97
99
|
|
|
98
100
|
## What it does
|
|
99
101
|
|
|
100
|
-
- Scans your local AI-agent session transcripts for
|
|
102
|
+
- Scans your local AI-agent session transcripts for 84 high-confidence
|
|
101
103
|
secret patterns: cloud provider keys, private key blocks, OAuth/API
|
|
102
104
|
tokens, database connection strings, and more. See
|
|
103
105
|
[`src/patterns.js`](src/patterns.js).
|
|
@@ -138,7 +140,8 @@ while losing rows, then fixed in public against the classes it was losing
|
|
|
138
140
|
for CI and pre-commit. See [docs/ci.md](docs/ci.md).
|
|
139
141
|
- `residoo watch` / `residoo mcp` / `residoo cred` / `residoo guard`:
|
|
140
142
|
continuous scanning, conversational queries, credential injection
|
|
141
|
-
without pasting, and
|
|
143
|
+
without pasting, and blocking a sensitive file read or a sensitive
|
|
144
|
+
prompt before either happens. See [docs/features.md](docs/features.md).
|
|
142
145
|
|
|
143
146
|
## What it does not do
|
|
144
147
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "residoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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
|
@@ -218,24 +218,47 @@ Cred:
|
|
|
218
218
|
tool, present only when RESIDOO_CRED_ALLOWED_COMMANDS is configured.
|
|
219
219
|
|
|
220
220
|
Guard:
|
|
221
|
-
residoo guard
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
221
|
+
residoo guard one binary, two Claude Code hooks, dispatched on
|
|
222
|
+
the payload's own hook_event_name:
|
|
223
|
+
|
|
224
|
+
PreToolUse blocks an obviously-sensitive file read
|
|
225
|
+
(.env, id_rsa, .aws/credentials, and similar)
|
|
226
|
+
before it can be written to the session
|
|
227
|
+
transcript at all. Narrower than it sounds:
|
|
228
|
+
Claude Code's hooks API can see a proposed Bash
|
|
229
|
+
command or Read path before it runs, but never
|
|
230
|
+
the command's OUTPUT, so this alone cannot catch
|
|
231
|
+
a secret typed into a prompt or one arriving
|
|
232
|
+
through an unrelated command's output.
|
|
233
|
+
|
|
234
|
+
UserPromptSubmit closes exactly that gap: it
|
|
235
|
+
checks the user's own typed prompt against
|
|
236
|
+
residoo's 79 high-confidence rules (never
|
|
237
|
+
--verify, never --ocr -- this hook has no matcher
|
|
238
|
+
and fires on every single prompt, so it must stay
|
|
239
|
+
fast) and can block it before Claude processes it
|
|
240
|
+
at all -- confirmed directly against Claude
|
|
241
|
+
Code's own docs, not assumed. A block ERASES the
|
|
242
|
+
user's whole typed message, so this path is
|
|
243
|
+
extra-conservative: a documented vendor-example
|
|
244
|
+
key or an obvious placeholder is suppressed, same
|
|
245
|
+
as residoo scan itself. Best-effort, not a
|
|
246
|
+
guarantee: per Claude Code's own docs, a slow
|
|
247
|
+
hook here fails OPEN (the prompt goes through
|
|
248
|
+
unscanned) rather than stalling the session,
|
|
249
|
+
consistent with this hook never blocking on
|
|
250
|
+
anything it doesn't recognize.
|
|
251
|
+
|
|
252
|
+
scan/watch/mcp remain the real safety net either
|
|
253
|
+
way -- this is prevention on top of detection,
|
|
254
|
+
not a replacement for it. Add both to
|
|
235
255
|
.claude/settings.json:
|
|
236
|
-
{"hooks":{
|
|
237
|
-
|
|
238
|
-
|
|
256
|
+
{"hooks":{
|
|
257
|
+
"PreToolUse":[{"matcher":"Bash|Read",
|
|
258
|
+
"hooks":[{"type":"command",
|
|
259
|
+
"command":"residoo guard"}]}],
|
|
260
|
+
"UserPromptSubmit":[{"hooks":[{"type":"command",
|
|
261
|
+
"command":"residoo guard"}]}]}}
|
|
239
262
|
|
|
240
263
|
Rotation:
|
|
241
264
|
residoo explain <rule-id> full rotation runbook for one detection rule
|
package/src/guard.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* `residoo guard`:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `residoo guard`: two Claude Code hooks in one binary, dispatched on the
|
|
5
|
+
* payload's own `hook_event_name` field.
|
|
6
|
+
*
|
|
7
|
+
* PreToolUse blocks an obviously-sensitive file read before it happens,
|
|
8
|
+
* instead of finding the leak in the transcript afterward.
|
|
7
9
|
*
|
|
8
10
|
* Scope, stated plainly because it is much narrower than "prevent secrets
|
|
9
11
|
* from leaking": Claude Code's hooks API gives a PreToolUse hook the
|
|
@@ -17,17 +19,53 @@
|
|
|
17
19
|
* and similar) -- it cannot catch a secret typed directly into a prompt, a
|
|
18
20
|
* secret arriving in the output of an otherwise-unremarkable command
|
|
19
21
|
* (curl, a build log), or any file path this pattern list does not name.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
*
|
|
23
|
+
* UserPromptSubmit closes exactly the "typed directly into a prompt" gap
|
|
24
|
+
* named above: Claude Code's own docs (code.claude.com/docs/en/hooks,
|
|
25
|
+
* fetched and read directly, not summarized secondhand) confirm this hook
|
|
26
|
+
* "runs before every prompt and blocks model processing until it
|
|
27
|
+
* completes" -- a real prevention point, not an after-the-fact alert.
|
|
28
|
+
* Verified precisely, not assumed: the payload's `prompt` field carries the
|
|
29
|
+
* exact submitted text (the field is `prompt`, NOT `user_prompt` -- a
|
|
30
|
+
* specific claim to the contrary did not survive this session's own
|
|
31
|
+
* adversarial verification pass), and a JSON response with
|
|
32
|
+
* `"decision": "block"` "prevents the prompt from being processed and
|
|
33
|
+
* erases it from context." Checked against residoo's existing 79
|
|
34
|
+
* high-confidence PATTERNS.js rules ONLY -- never NOISY_PATTERNS, never
|
|
35
|
+
* --verify (a network call), never --ocr (tesseract, and irrelevant to a
|
|
36
|
+
* text prompt anyway) -- because this hook has NO matcher support (it
|
|
37
|
+
* fires on every single prompt in every session, confirmed in the same
|
|
38
|
+
* docs) and must stay fast: Claude Code's own default timeout for this
|
|
39
|
+
* event's command hooks is 30s, down from 600s elsewhere, specifically
|
|
40
|
+
* because "a stuck hook stalls the session." A false positive here also
|
|
41
|
+
* costs more than a PreToolUse block: it erases the user's entire typed
|
|
42
|
+
* message, not one denied tool call, which is why this path additionally
|
|
43
|
+
* runs the same vendor-example/placeholder suppression `residoo scan`
|
|
44
|
+
* itself uses (see scan.js's VENDOR_EXAMPLE_VALUES/zeroEntropyTail) before
|
|
45
|
+
* ever blocking -- a documented AWS example key or an obvious placeholder
|
|
46
|
+
* must never eat a real prompt.
|
|
47
|
+
*
|
|
48
|
+
* Same fail-open timeout reality as scan.js's whole 30-vendor --verify
|
|
49
|
+
* surface, disclosed rather than hidden: per Claude Code's own docs, a
|
|
50
|
+
* command/http/mcp_tool hook (this one) that times out on UserPromptSubmit
|
|
51
|
+
* has its output discarded and "the prompt still reaches Claude" unblocked
|
|
52
|
+
* -- a genuinely slow scan cannot stall the session, but that also means
|
|
53
|
+
* this is best-effort, additive coverage, not an absolute guarantee, the
|
|
54
|
+
* same honesty already applied to --ocr.
|
|
22
55
|
*
|
|
23
56
|
* Fails safe in the direction of NOT blocking on any uncertainty: a
|
|
24
57
|
* malformed hook payload, an unrecognized tool name, or a parse error all
|
|
25
58
|
* fall through to "allow" (no stdout, exit 0) rather than denying a call
|
|
26
59
|
* this module does not understand. The one thing this module must never do
|
|
27
|
-
* is silently hang or crash the agent's turn over a tool call
|
|
28
|
-
* always going to be fine.
|
|
60
|
+
* is silently hang or crash the agent's turn over a tool call -- or a
|
|
61
|
+
* prompt -- that was always going to be fine. `residoo scan`/`watch`/`mcp`
|
|
62
|
+
* remain the actual safety net; this is a narrower, best-effort tripwire on
|
|
63
|
+
* top, not a replacement for them.
|
|
29
64
|
*/
|
|
30
65
|
|
|
66
|
+
const { PATTERNS, redact } = require("./patterns");
|
|
67
|
+
const { VENDOR_EXAMPLE_VALUES, zeroEntropyTail } = require("./scan");
|
|
68
|
+
|
|
31
69
|
// A matched path fragment must be preceded by a path separator or the start
|
|
32
70
|
// of the string, and followed by either the end of the string (the common
|
|
33
71
|
// case for Read's file_path) or a shell metacharacter/whitespace (the case
|
|
@@ -127,12 +165,49 @@ function evaluateToolInput(toolName, toolInput) {
|
|
|
127
165
|
};
|
|
128
166
|
}
|
|
129
167
|
|
|
168
|
+
// High-confidence only, computed once at module load (PATTERNS is a static
|
|
169
|
+
// array): never NOISY_PATTERNS, matching the same elevated bar decodeLine/
|
|
170
|
+
// ocrLine already use in scan.js for content one step removed from a plain
|
|
171
|
+
// file line -- a user's own live-typed prompt deserves at least that same
|
|
172
|
+
// bar, arguably a higher one given what a wrong block costs here (see this
|
|
173
|
+
// file's own docstring).
|
|
174
|
+
const PROMPT_GUARD_RULES = PATTERNS.filter((r) => r.confidence === "high");
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Pure decision function: given a UserPromptSubmit hook payload's raw
|
|
178
|
+
* `prompt` string, decide whether to block. No I/O, fully unit-testable.
|
|
179
|
+
* Suppresses the same way `residoo scan` does (a documented vendor-example
|
|
180
|
+
* key, or an obviously-placeholder zero-entropy tail) before ever blocking
|
|
181
|
+
* -- a false positive here erases the user's entire typed message, not one
|
|
182
|
+
* denied tool call, so this path is deliberately more conservative than
|
|
183
|
+
* evaluateToolInput above, not just a copy of the same bar.
|
|
184
|
+
*/
|
|
185
|
+
function evaluatePromptText(promptText) {
|
|
186
|
+
if (typeof promptText !== "string" || !promptText) return { block: false, reason: null };
|
|
187
|
+
for (const rule of PROMPT_GUARD_RULES) {
|
|
188
|
+
rule.re.lastIndex = 0;
|
|
189
|
+
const m = rule.re.exec(promptText);
|
|
190
|
+
if (!m) continue;
|
|
191
|
+
const value = m[0];
|
|
192
|
+
if (VENDOR_EXAMPLE_VALUES.has(value) || zeroEntropyTail(value)) continue;
|
|
193
|
+
return {
|
|
194
|
+
block: true,
|
|
195
|
+
reason: `residoo guard: this prompt looks like it contains ${rule.label} (${redact(value)}). ` +
|
|
196
|
+
`Blocked before it could be sent. If this is a false positive, rephrase or remove it, or disable this hook in .claude/settings.json.`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return { block: false, reason: null };
|
|
200
|
+
}
|
|
201
|
+
|
|
130
202
|
/**
|
|
131
|
-
* Reads one PreToolUse hook payload from `input`
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
203
|
+
* Reads one PreToolUse OR UserPromptSubmit hook payload from `input`
|
|
204
|
+
* (default stdin) -- distinguished by the payload's own `hook_event_name`
|
|
205
|
+
* common field, a single binary handling both the way Claude Code's own
|
|
206
|
+
* hook registration allows -- decides, and writes the hook's own JSON
|
|
207
|
+
* response protocol to `output` (default stdout) -- exit code is the
|
|
208
|
+
* caller's job (bin/residoo.js), this returns the intended process exit
|
|
209
|
+
* code instead of calling process.exit itself, matching every other run*
|
|
210
|
+
* function in cli.js.
|
|
136
211
|
*/
|
|
137
212
|
async function runGuard({ input = process.stdin, output = process.stdout } = {}) {
|
|
138
213
|
const chunks = [];
|
|
@@ -146,6 +221,20 @@ async function runGuard({ input = process.stdin, output = process.stdout } = {})
|
|
|
146
221
|
return 0; // malformed payload: fail open, never block on something we can't parse
|
|
147
222
|
}
|
|
148
223
|
|
|
224
|
+
// hook_event_name is a documented COMMON field present on every hook
|
|
225
|
+
// event's payload (code.claude.com/docs/en/hooks), not specific to one
|
|
226
|
+
// event -- checked first so a UserPromptSubmit payload (which has no
|
|
227
|
+
// tool_name/tool_input at all) never falls through to evaluateToolInput
|
|
228
|
+
// and is instead routed to its own decision function and its own
|
|
229
|
+
// response shape (decision:"block", not hookSpecificOutput.
|
|
230
|
+
// permissionDecision -- the two events do not share a response schema).
|
|
231
|
+
if (payload.hook_event_name === "UserPromptSubmit") {
|
|
232
|
+
const decision = evaluatePromptText(payload.prompt);
|
|
233
|
+
if (!decision.block) return 0;
|
|
234
|
+
output.write(JSON.stringify({ decision: "block", reason: decision.reason }) + "\n");
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
149
238
|
const decision = evaluateToolInput(payload.tool_name, payload.tool_input);
|
|
150
239
|
if (!decision.block) return 0;
|
|
151
240
|
|
|
@@ -159,4 +248,4 @@ async function runGuard({ input = process.stdin, output = process.stdout } = {})
|
|
|
159
248
|
return 0;
|
|
160
249
|
}
|
|
161
250
|
|
|
162
|
-
module.exports = { evaluateToolInput, matchSensitivePath, runGuard, SENSITIVE_PATH_PATTERNS };
|
|
251
|
+
module.exports = { evaluateToolInput, matchSensitivePath, evaluatePromptText, runGuard, SENSITIVE_PATH_PATTERNS };
|
package/src/patterns.js
CHANGED
|
@@ -539,6 +539,49 @@ const PATTERNS = [
|
|
|
539
539
|
// same bare/opaque shape already excluded elsewhere in this file.
|
|
540
540
|
{ id: "akamai_edgegrid_token", label: "Akamai EdgeGrid token", confidence: "high",
|
|
541
541
|
re: /\bakab-[a-z0-9]{16,32}-[a-z0-9]{6,32}\b/g },
|
|
542
|
+
// Doppler: seven distinct token kinds, all sharing the same dp.<tag>.
|
|
543
|
+
// structure. Confirmed via Doppler's own docs
|
|
544
|
+
// (docs.doppler.com/reference/auth-token-formats), which state plainly
|
|
545
|
+
// that "each token type uses a distinct prefix to enable identification
|
|
546
|
+
// during secret scanning operations" -- a format designed to be
|
|
547
|
+
// regex-detected, not inferred. Service tokens (dp.st.) alone carry an
|
|
548
|
+
// optional environment segment between the tag and the body; every other
|
|
549
|
+
// kind goes straight from the tag to the 40-44-char body.
|
|
550
|
+
{ id: "doppler_token", label: "Doppler token", confidence: "high",
|
|
551
|
+
re: /\bdp\.(?:ct|pt|sa|said|scim|audit)\.[A-Za-z0-9]{40,44}\b|\bdp\.st\.(?:[a-z0-9_-]{2,35}\.)?[A-Za-z0-9]{40,44}\b/g },
|
|
552
|
+
// Postman API key. Postman's own docs describe how to generate one but
|
|
553
|
+
// not its literal format; sourced instead from gitleaks' own
|
|
554
|
+
// production-tested rule (config/gitleaks.toml, id "postman-api-token"),
|
|
555
|
+
// same tier as azure_ad_client_secret's sourcing earlier in this file.
|
|
556
|
+
{ id: "postman_token", label: "Postman API key", confidence: "high",
|
|
557
|
+
re: /\bPMAK-[a-f0-9]{24}-[a-f0-9]{34}\b/gi },
|
|
558
|
+
// Figma personal access token. Figma's own docs don't publish the
|
|
559
|
+
// format either; sourced from trufflehog's own shipped detectors, which
|
|
560
|
+
// track two real, distinct generations: figd_ (the long-established
|
|
561
|
+
// form, trufflehog's v2 detector) and figp_ (a newer form, trufflehog's
|
|
562
|
+
// v3 detector, no keyword-proximity needed unlike v1's bare UUID shape,
|
|
563
|
+
// which is NOT included here for the same reason Bitbucket's keyword-
|
|
564
|
+
// dependent Client ID/Secret rules were declined -- no distinctive
|
|
565
|
+
// standalone prefix).
|
|
566
|
+
{ id: "figma_token", label: "Figma personal access token", confidence: "high",
|
|
567
|
+
re: /\bfig[dp]_[A-Za-z0-9_=-]{40,54}\b/g },
|
|
568
|
+
// Bitbucket App Password (distinct from Bitbucket's Client ID/Secret,
|
|
569
|
+
// declined elsewhere in this file's history for being keyword-dependent
|
|
570
|
+
// with no standalone prefix). Confirmed via an Atlassian staff reply on
|
|
571
|
+
// Atlassian's own community forum naming ATBB as the current App
|
|
572
|
+
// Password prefix -- the same source already used for atlassian_api_token.
|
|
573
|
+
{ id: "bitbucket_app_password", label: "Bitbucket App Password", confidence: "high",
|
|
574
|
+
re: /\bATBB[a-zA-Z0-9]{32}\b/g },
|
|
575
|
+
// SonarQube/SonarCloud token. gitleaks' own rule needs "sonar" keyword
|
|
576
|
+
// proximity because its body class also has to catch a bare unprefixed
|
|
577
|
+
// 40-char fallback -- unsafe as a standalone rule, so only the three
|
|
578
|
+
// confirmed literal prefixes are used here, which need no such context.
|
|
579
|
+
// squ_/sqp_/sqa_ confirmed real and current via SonarSource's own docs
|
|
580
|
+
// (docs.sonarsource.com), whose own worked example (sqp_ followed by
|
|
581
|
+
// 1aa323...8a1d13) is added to VENDOR_EXAMPLE_VALUES in scan.js as a
|
|
582
|
+
// documented example, not a findable secret.
|
|
583
|
+
{ id: "sonarqube_token", label: "SonarQube/SonarCloud token", confidence: "high",
|
|
584
|
+
re: /\b(?:squ|sqp|sqa)_[a-z0-9=_-]{40}\b/g },
|
|
542
585
|
];
|
|
543
586
|
|
|
544
587
|
/**
|
package/src/rotation.js
CHANGED
|
@@ -1093,6 +1093,58 @@ const ROTATION_GUIDANCE = {
|
|
|
1093
1093
|
],
|
|
1094
1094
|
revokeNote: "Deactivation is immediate; anything still using the old credential starts failing authentication at once.",
|
|
1095
1095
|
},
|
|
1096
|
+
// docs.doppler.com/reference/auth-token-formats confirms the prefix
|
|
1097
|
+
// table; the tokens page itself is login-walled.
|
|
1098
|
+
doppler_token: {
|
|
1099
|
+
label: "Doppler token",
|
|
1100
|
+
consolePath: "dashboard.doppler.com > the relevant project/workplace > Access > Tokens (or Service Tokens / Service Accounts, matching which prefix leaked)",
|
|
1101
|
+
steps: [
|
|
1102
|
+
"Identify which token kind leaked from its prefix (dp.pt. personal, dp.st. service, dp.sa. service account, dp.scim. SCIM, dp.audit. audit log)",
|
|
1103
|
+
"Revoke it from that kind's own settings page",
|
|
1104
|
+
"Create a replacement and update whatever used the old one",
|
|
1105
|
+
],
|
|
1106
|
+
revokeNote: "A dp.sa. (Service Account) or dp.st. (Service Token) leak can reach every secret in its assigned project/config -- treat it as broader than a personal token leak.",
|
|
1107
|
+
},
|
|
1108
|
+
postman_token: {
|
|
1109
|
+
label: "Postman API key",
|
|
1110
|
+
consolePath: "Postman > Settings (gear icon) > API keys",
|
|
1111
|
+
steps: [
|
|
1112
|
+
"Open API keys under your account settings",
|
|
1113
|
+
"Delete the leaked key",
|
|
1114
|
+
"Generate a replacement and update whatever used the old one",
|
|
1115
|
+
],
|
|
1116
|
+
revokeNote: "Deletion is immediate; anything still using the old key starts failing authentication at once.",
|
|
1117
|
+
},
|
|
1118
|
+
figma_token: {
|
|
1119
|
+
label: "Figma personal access token",
|
|
1120
|
+
consolePath: "Figma > account Settings > Personal access tokens",
|
|
1121
|
+
steps: [
|
|
1122
|
+
"Open Personal access tokens under account Settings",
|
|
1123
|
+
"Revoke the leaked token",
|
|
1124
|
+
"Create a replacement and update whatever used the old one",
|
|
1125
|
+
],
|
|
1126
|
+
revokeNote: "Revocation is immediate; anything still using the old token starts failing authentication at once.",
|
|
1127
|
+
},
|
|
1128
|
+
bitbucket_app_password: {
|
|
1129
|
+
label: "Bitbucket App Password",
|
|
1130
|
+
consolePath: "id.atlassian.com > Security > App passwords",
|
|
1131
|
+
steps: [
|
|
1132
|
+
"Open App passwords under account Security settings",
|
|
1133
|
+
"Delete the leaked app password",
|
|
1134
|
+
"Create a replacement with the narrowest scopes it needs",
|
|
1135
|
+
],
|
|
1136
|
+
revokeNote: "Atlassian is steering users toward API tokens/Access tokens instead of App Passwords -- consider migrating rather than just replacing like-for-like.",
|
|
1137
|
+
},
|
|
1138
|
+
sonarqube_token: {
|
|
1139
|
+
label: "SonarQube/SonarCloud token",
|
|
1140
|
+
consolePath: "SonarQube/SonarCloud > My Account > Security",
|
|
1141
|
+
steps: [
|
|
1142
|
+
"Open the Security tab under My Account",
|
|
1143
|
+
"Revoke the leaked token",
|
|
1144
|
+
"Generate a replacement and update whatever used the old one",
|
|
1145
|
+
],
|
|
1146
|
+
revokeNote: "Revocation is immediate; anything still using the old token starts failing authentication at once.",
|
|
1147
|
+
},
|
|
1096
1148
|
|
|
1097
1149
|
// ── NOISY_PATTERNS (only reachable via --include-noisy) ───────────────
|
|
1098
1150
|
generic_password_assignment: {
|
package/src/scan.js
CHANGED
|
@@ -217,6 +217,11 @@ function zeroEntropyTail(value) {
|
|
|
217
217
|
const VENDOR_EXAMPLE_VALUES = new Set([
|
|
218
218
|
"AKIAIOSFODNN7EXAMPLE",
|
|
219
219
|
"AKIAI44QH8DHBEXAMPLE",
|
|
220
|
+
// SonarSource's own worked example, repeated across its docs.sonarsource.com
|
|
221
|
+
// documentation (an API-key usage snippet, not a "this is our example
|
|
222
|
+
// secret" callout page, but used identically and repeatedly the same way
|
|
223
|
+
// AWS's own documented example key is).
|
|
224
|
+
"sqp_" + "1aa323ae0689cd4a1abd062a2ad0a224ae8a1d13",
|
|
220
225
|
"ghp_16C7e42F292c6912E7710c838347Ae178B4a",
|
|
221
226
|
"gho_16C7e42F292c6912E7710c838347Ae178B4a",
|
|
222
227
|
"ghr_1B4a2e77838347a7E420ce178F2E7c6912E169246c34E1ccbF66C46812d16D5B1A9Dc86A1498",
|
|
@@ -943,4 +948,4 @@ function emptyResult() {
|
|
|
943
948
|
// so residoo_verify_finding's v1 only supports the single-token vendors below.
|
|
944
949
|
const VERIFIABLE_RULE_IDS = new Set(Object.keys(SIMPLE_VERIFY_FNS));
|
|
945
950
|
|
|
946
|
-
module.exports = { scan, emptyResult, VENDOR_EXAMPLE_VALUES, VERIFIABLE_RULE_IDS };
|
|
951
|
+
module.exports = { scan, emptyResult, VENDOR_EXAMPLE_VALUES, VERIFIABLE_RULE_IDS, zeroEntropyTail };
|