shipready 1.5.0 → 1.5.1

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 CHANGED
@@ -363,13 +363,26 @@ pnpm dev # run the CLI from source (tsx)
363
363
 
364
364
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for project structure and how to add new checks or secret patterns.
365
365
 
366
+ ## How does it compare to gitleaks?
367
+
368
+ Measured head-to-head on a corpus of 7 real-format leaks and 10 false-positive baits (gitleaks v8.24.3):
369
+
370
+ | | shipready | gitleaks |
371
+ | --- | --- | --- |
372
+ | Real leaks caught | **7 / 7** | 4 / 7 |
373
+ | Error-level false positives | **0** | 1 |
374
+ | Newer key formats (`sk-proj-`, `sk-ant-`) | ✓ | missed |
375
+ | Committed `.env.backup` | ✓ | partial |
376
+
377
+ gitleaks remains more battle-tested for deep git-history forensics with hundreds of rules. shipready covers the leaks that actually happen in modern AI-assisted projects — plus README, env hygiene, and code hygiene that gitleaks doesn't check at all. Full methodology, fairness notes, and reproduction steps: [BENCHMARK.md](./BENCHMARK.md).
378
+
366
379
  ## Roadmap
367
380
 
368
381
  - [x] `shipready staged` for pre-commit hooks
369
382
  - [x] SARIF report output for GitHub code scanning
370
383
  - [x] Git history scanning (`--history`)
371
384
  - [x] Live key verification (`--verify`)
372
- - [ ] Benchmark against gitleaks/trufflehog with published numbers
385
+ - [x] Benchmark against gitleaks with published numbers ([BENCHMARK.md](./BENCHMARK.md))
373
386
  - [ ] Framework-aware scoring (Next.js vs Vite vs Python have different needs)
374
387
  - [ ] Optional AI-powered fix suggestions (bring your own key)
375
388
 
@@ -232,6 +232,17 @@ const PATTERNS = [
232
232
  group: 1,
233
233
  minEntropy: 3.8,
234
234
  },
235
+ {
236
+ // Unquoted dotenv-style assignment (KEY=value at line start). Catches
237
+ // committed .env backups where values carry no quotes - a gap found by
238
+ // benchmarking against gitleaks. Anchored to line start so ordinary
239
+ // code assignments (always quoted) don't double-match.
240
+ kind: "Hardcoded credential",
241
+ re: /^[A-Z0-9_]*(?:API_?KEY|SECRET(?:_KEY)?|ACCESS_TOKEN|AUTH_TOKEN|PASSWORD|PASSWD|CREDENTIALS?)[A-Z0-9_]*=([A-Za-z0-9+/_.=-]{16,})\s*$/,
242
+ confidence: "medium",
243
+ group: 1,
244
+ minEntropy: 3.8,
245
+ },
235
246
  ];
236
247
  /** Words and shapes that indicate a placeholder, not a real secret. */
237
248
  const PLACEHOLDER_VALUE_RE = /your[-_ ]?|example|sample|placeholder|change[-_ ]?me|dummy|fake|insert|replace|redacted|deadbeef|lorem|goes[-_ ]?here|test[-_ ]?key|x{4,}|\*{3,}|\.\.\.|123456|abcdef/i;
package/dist/scanner.js CHANGED
@@ -42,11 +42,18 @@ export function scanFiles(root, files, secretAllowlist = []) {
42
42
  * an example is just as dangerous - but hygiene noise is skipped.
43
43
  */
44
44
  const HYGIENE_EXEMPT_RE = /(?:^|\/)(?:examples?|demos?|benchmarks?|scripts?|playground)\//;
45
+ /**
46
+ * Standard env file names that legitimately hold real values and are
47
+ * expected to be gitignored - the env check owns those. Anything else
48
+ * that merely starts with ".env" (.env.backup, .env.old, .env.prod)
49
+ * is a copy someone made and MUST be scanned: benchmark testing showed
50
+ * gitleaks catches committed .env backups while we used to skip them.
51
+ */
52
+ const STANDARD_ENV_RE = /^\.env(?:\.(?:local|development|production|test|staging))?(?:\.local)?$/;
45
53
  for (const file of files) {
46
54
  const base = path.basename(file);
47
- // Never flag .env files themselves for secrets/todos; they are expected
48
- // to contain real values and are handled by the env check.
49
- const isEnvFile = base.startsWith(".env");
55
+ const isStandardEnv = STANDARD_ENV_RE.test(base);
56
+ const isEnvTemplate = base === ".env.example" || base === ".env.sample" || base === ".env.template";
50
57
  const ext = path.extname(file).toLowerCase();
51
58
  const isCode = CODE_ONLY.has(ext);
52
59
  if (isProbablyBinary(root, file))
@@ -60,7 +67,7 @@ export function scanFiles(root, files, secretAllowlist = []) {
60
67
  todos.push(...scanContentForTodos(content, file));
61
68
  }
62
69
  }
63
- if (!isEnvFile && base !== ".env.example" && base !== ".env.sample") {
70
+ if (!isStandardEnv && !isEnvTemplate) {
64
71
  secrets.push(...scanContentForSecrets(content, file, secretAllowlist));
65
72
  }
66
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipready",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Pre-flight check for your repo before shipping. Scans AI-coded projects for secrets, env issues, debug leftovers, and generates AI-agent instruction files.",
5
5
  "type": "module",
6
6
  "license": "MIT",