@intentius/chant 0.54.1 → 0.54.2

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.
@@ -86,10 +86,7 @@ export declare const ENTROPY_RULE_ID = "SEC010";
86
86
  * trigger it.
87
87
  */
88
88
  export declare function looksLikeIdentifierSlug(value: string): boolean;
89
- /**
90
- * Scan a set of files for likely secrets/credentials. Pure with respect to
91
- * the filesystem and network. Findings never carry the matched value —
92
- * only its rule id, kind, file/line, and a redaction-safe fingerprint.
93
- */
89
+ /** Is this a dependency lockfile the secrets scan should skip? */
90
+ export declare function isLockfilePath(path: string): boolean;
94
91
  export declare function scanForSecrets(files: ScannableFile[], opts?: SecretsScanOptions): AuditFinding[];
95
92
  //# sourceMappingURL=secrets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/audit/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAG3C,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,0GAA0G;IAC1G,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B;AAOD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYvD;AAkDD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAuBtE;AA2GD,2FAA2F;AAC3F,eAAO,MAAM,eAAe,WAAW,CAAC;AAIxC;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK9D;AAsCD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,IAAI,GAAE,kBAAuB,GAAG,YAAY,EAAE,CAkEpG"}
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/audit/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAG3C,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,0GAA0G;IAC1G,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B;AAOD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYvD;AAkDD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAuBtE;AA2GD,2FAA2F;AAC3F,eAAO,MAAM,eAAe,WAAW,CAAC;AAIxC;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK9D;AAqED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,IAAI,GAAE,kBAAuB,GAAG,YAAY,EAAE,CAmEpG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.54.1",
3
+ "version": "0.54.2",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -300,4 +300,19 @@ describe("scanForSecrets — file handling", () => {
300
300
  expect(findings[0].severity).toBe("error");
301
301
  expect(findings[0].lexicon).toBe("secrets");
302
302
  });
303
+
304
+ test("dependency lockfiles are skipped entirely — integrity hashes are not secrets", () => {
305
+ // The exact false-positive class that failed the first dogfooding repo:
306
+ // one lockfile's sha512 integrity fields read as hundreds of SEC010s.
307
+ const integrity = `"integrity": "sha512-${"a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0u1V2w3X4y5Z6".repeat(2)}"`;
308
+ for (const path of ["package-lock.json", "web/package-lock.json", "yarn.lock", "pnpm-lock.yaml", "Cargo.lock", "go.sum"]) {
309
+ expect(scanForSecrets([{ path, content: `${integrity}\n` }]), path).toEqual([]);
310
+ }
311
+ // Even a real credential shape stays silent there — the file is skipped,
312
+ // not entropy-tuned; a credential does not live in a machine-written
313
+ // lockfile, and per-file skipping is what keeps the rule explainable.
314
+ expect(scanForSecrets([{ path: "package-lock.json", content: `${FAKE_AWS_KEY}\n` }])).toEqual([]);
315
+ // A non-lockfile with the same content still fires.
316
+ expect(scanForSecrets([{ path: "config.json", content: `${integrity}\n` }]).length).toBeGreaterThan(0);
317
+ });
303
318
  });
@@ -337,6 +337,38 @@ function lineNumberFor(starts: number[], index: number): number {
337
337
  * the filesystem and network. Findings never carry the matched value —
338
338
  * only its rule id, kind, file/line, and a redaction-safe fingerprint.
339
339
  */
340
+ /**
341
+ * Machine-generated dependency lockfiles. Skipped entirely: they are wall-to-
342
+ * wall content-integrity hashes (`sha512-…` and friends), which the entropy
343
+ * heuristic reads as hundreds of "possible secrets" per file — a single
344
+ * `package-lock.json` produced 438 merge-worthy SEC010 findings on the first
345
+ * repo that dogfooded this at `--fail-on merge-worthy`. Integrity hashes are
346
+ * public by construction, a credential does not live in a lockfile, and no
347
+ * other scanner reads them.
348
+ */
349
+ const LOCKFILE_NAMES = new Set([
350
+ "package-lock.json",
351
+ "npm-shrinkwrap.json",
352
+ "yarn.lock",
353
+ "pnpm-lock.yaml",
354
+ "bun.lock",
355
+ "deno.lock",
356
+ "Cargo.lock",
357
+ "poetry.lock",
358
+ "uv.lock",
359
+ "Pipfile.lock",
360
+ "composer.lock",
361
+ "Gemfile.lock",
362
+ "go.sum",
363
+ "flake.lock",
364
+ ]);
365
+
366
+ /** Is this a dependency lockfile the secrets scan should skip? */
367
+ export function isLockfilePath(path: string): boolean {
368
+ const name = path.split("/").pop() ?? path;
369
+ return LOCKFILE_NAMES.has(name);
370
+ }
371
+
340
372
  export function scanForSecrets(files: ScannableFile[], opts: SecretsScanOptions = {}): AuditFinding[] {
341
373
  const entropyThreshold = opts.entropyThreshold ?? DEFAULT_ENTROPY_THRESHOLD;
342
374
  const entropyMinLength = opts.entropyMinLength ?? DEFAULT_ENTROPY_MIN_LENGTH;
@@ -347,6 +379,7 @@ export function scanForSecrets(files: ScannableFile[], opts: SecretsScanOptions
347
379
 
348
380
  for (const file of files) {
349
381
  if (!file.content) continue;
382
+ if (isLockfilePath(file.path)) continue;
350
383
  const lines = file.content.split("\n");
351
384
  const starts = lineStarts(file.content);
352
385
  const claimed: Array<[number, number]> = [];