@coo-quack/sensitive-canary 0.5.3 → 0.7.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/CHANGELOG.md +61 -8
- package/README.md +163 -8
- package/package.json +6 -6
- package/src/__tests__/pre-tool-use-hook.test.ts +87 -21
- package/src/__tests__/user-prompt-submit-hook.test.ts +43 -1
- package/src/lib/__tests__/inspector.test.ts +8 -0
- package/src/lib/__tests__/rules.test.ts +1020 -2
- package/src/lib/default-config.json +461 -0
- package/src/lib/rules.ts +650 -243
- package/src/pre-tool-use-hook.ts +21 -6
- package/src/user-prompt-submit-hook.ts +4 -2
package/src/pre-tool-use-hook.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parseAllowTags,
|
|
11
11
|
randomBird,
|
|
12
12
|
} from "./lib/inspector.ts";
|
|
13
|
-
import { type Finding, scan } from "./lib/rules.ts";
|
|
13
|
+
import { enabledCategoriesFromEnv, type Finding, scan } from "./lib/rules.ts";
|
|
14
14
|
|
|
15
15
|
interface HookInput {
|
|
16
16
|
transcript_path?: string;
|
|
@@ -30,6 +30,8 @@ interface TranscriptLine {
|
|
|
30
30
|
// Maximum bytes to read from the tail of a transcript file.
|
|
31
31
|
const MAX_TRANSCRIPT_TAIL_BYTES = 65_536; // 64 KB
|
|
32
32
|
|
|
33
|
+
const ENABLED_CATEGORIES = enabledCategoriesFromEnv();
|
|
34
|
+
|
|
33
35
|
// ── Transcript ────────────────────────────────────────────────────────────────
|
|
34
36
|
|
|
35
37
|
// Returns true when the message contains at least one text content block
|
|
@@ -150,7 +152,8 @@ function extractFilePathsFromCommand(command: string): string[] {
|
|
|
150
152
|
|
|
151
153
|
// ── .env pattern ──────────────────────────────────────────────────────────────
|
|
152
154
|
|
|
153
|
-
// .env and .env.* (e.g. .env.local, .env.production)
|
|
155
|
+
// .env and .env.* (e.g. .env.local, .env.production) match the env filename pattern.
|
|
156
|
+
// The block only applies while the "secret" category is enabled (see shouldBlockEnvFile).
|
|
154
157
|
// Files that merely end in .env (e.g. production.env) are handled by content scanning.
|
|
155
158
|
function isBlockedEnvFile(filePath: string): boolean {
|
|
156
159
|
if (!filePath) return false;
|
|
@@ -158,6 +161,12 @@ function isBlockedEnvFile(filePath: string): boolean {
|
|
|
158
161
|
return base === ".env" || base.startsWith(".env.");
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
// The .env name-based block is a secret guard: it only applies while the
|
|
165
|
+
// "secret" category is enabled.
|
|
166
|
+
function shouldBlockEnvFile(filePath: string): boolean {
|
|
167
|
+
return ENABLED_CATEGORIES.has("secret") && isBlockedEnvFile(filePath);
|
|
168
|
+
}
|
|
169
|
+
|
|
161
170
|
// ── Output helpers ────────────────────────────────────────────────────────────
|
|
162
171
|
|
|
163
172
|
// Build the allow-tag hint lines shown to Claude.
|
|
@@ -239,7 +248,7 @@ function block(
|
|
|
239
248
|
// ── Core scan logic ───────────────────────────────────────────────────────────
|
|
240
249
|
|
|
241
250
|
function scanFile(filePath: string, allowTags: Set<string>): void {
|
|
242
|
-
if (
|
|
251
|
+
if (shouldBlockEnvFile(filePath)) {
|
|
243
252
|
if (allowTags.size > 0) return;
|
|
244
253
|
block(
|
|
245
254
|
filePath,
|
|
@@ -263,7 +272,10 @@ function scanFile(filePath: string, allowTags: Set<string>): void {
|
|
|
263
272
|
return;
|
|
264
273
|
}
|
|
265
274
|
|
|
266
|
-
const findings = applyAllowTags(
|
|
275
|
+
const findings = applyAllowTags(
|
|
276
|
+
dedupeFindings(scan(content, ENABLED_CATEGORIES)),
|
|
277
|
+
allowTags,
|
|
278
|
+
);
|
|
267
279
|
if (findings.length === 0) return;
|
|
268
280
|
|
|
269
281
|
block(
|
|
@@ -308,7 +320,10 @@ process.stdin.on("end", () => {
|
|
|
308
320
|
for (const varName of extractEnvVarNames(command)) {
|
|
309
321
|
const value = process.env[varName];
|
|
310
322
|
if (!value) continue;
|
|
311
|
-
const findings = applyAllowTags(
|
|
323
|
+
const findings = applyAllowTags(
|
|
324
|
+
dedupeFindings(scan(value, ENABLED_CATEGORIES)),
|
|
325
|
+
allowTags,
|
|
326
|
+
);
|
|
312
327
|
if (findings.length === 0) continue;
|
|
313
328
|
block(
|
|
314
329
|
`bash command: ${command.slice(0, 80)}`,
|
|
@@ -322,7 +337,7 @@ process.stdin.on("end", () => {
|
|
|
322
337
|
}
|
|
323
338
|
|
|
324
339
|
const cmdFindings = applyAllowTags(
|
|
325
|
-
dedupeFindings(scan(command)),
|
|
340
|
+
dedupeFindings(scan(command, ENABLED_CATEGORIES)),
|
|
326
341
|
allowTags,
|
|
327
342
|
);
|
|
328
343
|
if (cmdFindings.length > 0) {
|
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
randomBird,
|
|
9
9
|
resolveTagPriority,
|
|
10
10
|
} from "./lib/inspector.ts";
|
|
11
|
-
import { scan } from "./lib/rules.ts";
|
|
11
|
+
import { enabledCategoriesFromEnv, scan } from "./lib/rules.ts";
|
|
12
12
|
|
|
13
13
|
interface HookInput {
|
|
14
14
|
prompt?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const ENABLED_CATEGORIES = enabledCategoriesFromEnv();
|
|
18
|
+
|
|
17
19
|
let raw = "";
|
|
18
20
|
process.stdin.setEncoding("utf8");
|
|
19
21
|
process.stdin.on("data", (chunk: string) => (raw += chunk));
|
|
@@ -27,7 +29,7 @@ process.stdin.on("end", () => {
|
|
|
27
29
|
|
|
28
30
|
const prompt = data.prompt ?? "";
|
|
29
31
|
|
|
30
|
-
const allFindings = scan(prompt);
|
|
32
|
+
const allFindings = scan(prompt, ENABLED_CATEGORIES);
|
|
31
33
|
|
|
32
34
|
if (allFindings.length === 0) process.exit(0);
|
|
33
35
|
|