secureflow-cli 1.0.0 → 1.1.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/dist/git.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Git access for the pre-commit hook.
3
+ *
4
+ * The one thing this module exists to get right: a pre-commit hook must check
5
+ * **staged content**, not what happens to be on disk. The previous
6
+ * implementation asked git for the staged *paths* and then read those paths
7
+ * from the working tree, which is a different thing and lets a secret through:
8
+ *
9
+ * echo 'console.log(process.env.AWS_SECRET_ACCESS_KEY);' >> src/debug.ts
10
+ * git add src/debug.ts # the secret is staged
11
+ * sed -i '' '$d' src/debug.ts # tidy the working tree, forget to re-stage
12
+ * git commit -m "wip" # ✅ SecureFlow scan passed.
13
+ *
14
+ * The commit contains the logged secret; the scanner read the clean working
15
+ * tree. "Stage a chunk, keep editing" is the ordinary `git add -p` workflow, so
16
+ * this is not a corner case, and it fails in the direction that lets secrets
17
+ * through (#593).
18
+ *
19
+ * The inverse is just as bad: unstaged debug code blocks a commit that does not
20
+ * contain it, which teaches people to reach for `--no-verify`.
21
+ */
22
+ /** A problem with the repository or the git invocation, not with the content. */
23
+ export declare class GitError extends Error {
24
+ readonly cause?: unknown;
25
+ constructor(message: string, cause?: unknown);
26
+ }
27
+ /** Whether the current directory is inside a work tree. */
28
+ export declare function isGitRepository(): boolean;
29
+ /**
30
+ * Paths staged for commit.
31
+ *
32
+ * `-z` is what makes this correct for real repositories. Without it git honours
33
+ * `core.quotePath`, which defaults to **true**, so a staged `src/café.ts` comes
34
+ * back as the literal 15 characters `"src/caf\303\251.ts"` — quotes and octal
35
+ * escapes included. `path.resolve` then built a path that does not exist,
36
+ * `existsSync` returned false, and the file was skipped with no warning and no
37
+ * non-zero exit. The same applied to any path containing a space or a `"`.
38
+ *
39
+ * `--diff-filter=ACMR` drops deletions, which have no staged content to read,
40
+ * and keeps renames, which do.
41
+ */
42
+ export declare function getStagedFiles(): string[];
43
+ /**
44
+ * The staged content of `path` — the bytes that will actually be committed.
45
+ *
46
+ * `git show :<path>` reads the index entry, which is the whole point. Returns
47
+ * null when the blob cannot be read (a conflicted entry, a submodule pointer,
48
+ * a symlink), so the caller can report it rather than treat it as clean.
49
+ */
50
+ export declare function readStagedContent(path: string): string | null;
51
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAaH,iFAAiF;AACjF,qBAAa,QAAS,SAAQ,KAAK;IACJ,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IAArD,YAAY,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,EAGpD;CACF;AASD,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,OAAO,CAMzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAgBzC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM7D"}
package/dist/git.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Git access for the pre-commit hook.
3
+ *
4
+ * The one thing this module exists to get right: a pre-commit hook must check
5
+ * **staged content**, not what happens to be on disk. The previous
6
+ * implementation asked git for the staged *paths* and then read those paths
7
+ * from the working tree, which is a different thing and lets a secret through:
8
+ *
9
+ * echo 'console.log(process.env.AWS_SECRET_ACCESS_KEY);' >> src/debug.ts
10
+ * git add src/debug.ts # the secret is staged
11
+ * sed -i '' '$d' src/debug.ts # tidy the working tree, forget to re-stage
12
+ * git commit -m "wip" # ✅ SecureFlow scan passed.
13
+ *
14
+ * The commit contains the logged secret; the scanner read the clean working
15
+ * tree. "Stage a chunk, keep editing" is the ordinary `git add -p` workflow, so
16
+ * this is not a corner case, and it fails in the direction that lets secrets
17
+ * through (#593).
18
+ *
19
+ * The inverse is just as bad: unstaged debug code blocks a commit that does not
20
+ * contain it, which teaches people to reach for `--no-verify`.
21
+ */
22
+ import { execFileSync } from 'child_process';
23
+ /**
24
+ * Buffer ceiling for git output.
25
+ *
26
+ * `execSync` defaults to 1 MB. A commit touching enough files overflowed it,
27
+ * and the catch block then printed "Are you in a git repository?" — which was
28
+ * not the problem, and blocked the commit for a reason nobody could act on.
29
+ */
30
+ const MAX_GIT_BUFFER = 64 * 1024 * 1024;
31
+ /** A problem with the repository or the git invocation, not with the content. */
32
+ export class GitError extends Error {
33
+ cause;
34
+ constructor(message, cause) {
35
+ super(message);
36
+ this.cause = cause;
37
+ this.name = 'GitError';
38
+ }
39
+ }
40
+ function git(args) {
41
+ return execFileSync('git', args, {
42
+ encoding: 'utf-8',
43
+ maxBuffer: MAX_GIT_BUFFER,
44
+ });
45
+ }
46
+ /** Whether the current directory is inside a work tree. */
47
+ export function isGitRepository() {
48
+ try {
49
+ return git(['rev-parse', '--is-inside-work-tree']).trim() === 'true';
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * Paths staged for commit.
57
+ *
58
+ * `-z` is what makes this correct for real repositories. Without it git honours
59
+ * `core.quotePath`, which defaults to **true**, so a staged `src/café.ts` comes
60
+ * back as the literal 15 characters `"src/caf\303\251.ts"` — quotes and octal
61
+ * escapes included. `path.resolve` then built a path that does not exist,
62
+ * `existsSync` returned false, and the file was skipped with no warning and no
63
+ * non-zero exit. The same applied to any path containing a space or a `"`.
64
+ *
65
+ * `--diff-filter=ACMR` drops deletions, which have no staged content to read,
66
+ * and keeps renames, which do.
67
+ */
68
+ export function getStagedFiles() {
69
+ let output;
70
+ try {
71
+ output = git(['diff', '--cached', '--name-only', '-z', '--diff-filter=ACMR']);
72
+ }
73
+ catch (error) {
74
+ throw new GitError(isGitRepository()
75
+ ? 'Could not list staged files. `git diff --cached` failed.'
76
+ : 'Not a git repository (or git is not on PATH).', error);
77
+ }
78
+ // NUL-separated, with a trailing separator on a non-empty list.
79
+ return output.split('\u0000').filter((entry) => entry.length > 0);
80
+ }
81
+ /**
82
+ * The staged content of `path` — the bytes that will actually be committed.
83
+ *
84
+ * `git show :<path>` reads the index entry, which is the whole point. Returns
85
+ * null when the blob cannot be read (a conflicted entry, a submodule pointer,
86
+ * a symlink), so the caller can report it rather than treat it as clean.
87
+ */
88
+ export function readStagedContent(path) {
89
+ try {
90
+ return git(['show', `:${path}`]);
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAExC,iFAAiF;AACjF,MAAM,OAAO,QAAS,SAAQ,KAAK;IACK,KAAK;IAA3C,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;qBADqB,KAAK;QAEzC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED,SAAS,GAAG,CAAC,IAAc;IACzB,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;QAC/B,QAAQ,EAAE,OAAO;QACjB,SAAS,EAAE,cAAc;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,MAAc,CAAC;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAChF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAChB,eAAe,EAAE;YACf,CAAC,CAAC,0DAA0D;YAC5D,CAAC,CAAC,+CAA+C,EACnD,KAAK,CACN,CAAC;IACJ,CAAC;IAED,gEAAgE;IAChE,OAAO,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -1,50 +1,69 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'child_process';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- const LOGGING_SECRET_REGEX = /console\.(log|info|warn|error|debug)\s*\([\s\S]*?(process\.env|password|secret|token|key)[\s\S]*?\)/i;
6
- function getStagedFiles() {
2
+ /**
3
+ * SecureFlow pre-commit hook.
4
+ *
5
+ * The shell only: read the staged set, scan each blob, report, exit. The
6
+ * detection logic lives in `./scanner` and the git access in `./git`, so both
7
+ * can be tested without a process exit or a repository (#593).
8
+ */
9
+ import { GitError, getStagedFiles, readStagedContent } from './git.js';
10
+ import { scanFile } from './scanner.js';
11
+ const VERBOSE = process.argv.includes('--verbose');
12
+ function reportSkipped(result) {
13
+ if (VERBOSE && result.skipped) {
14
+ console.log(` ↷ skipped ${result.path} (${result.skipped})`);
15
+ }
16
+ }
17
+ function reportViolations(result) {
18
+ for (const violation of result.violations) {
19
+ console.error(`🚨 [SecureFlow] Secret logging detected in ${result.path}:${violation.line}`);
20
+ console.error(` -> ${violation.text}`);
21
+ console.error(` why: ${violation.reason} passed to a console call`);
22
+ }
23
+ }
24
+ function main() {
25
+ let staged;
7
26
  try {
8
- const output = execSync('git diff --cached --name-only', { encoding: 'utf-8' });
9
- return output.split('\n').filter(Boolean);
27
+ staged = getStagedFiles();
10
28
  }
11
29
  catch (error) {
12
- console.error('Failed to get staged files. Are you in a git repository?');
13
- process.exit(1);
30
+ // Distinguished rather than collapsed into "Are you in a git repository?",
31
+ // which was previously printed for a buffer overflow, a missing git binary
32
+ // and a genuine non-repository alike.
33
+ console.error(`❌ [SecureFlow] ${error instanceof GitError ? error.message : String(error)}`);
34
+ return 1;
14
35
  }
15
- }
16
- function scanFiles() {
17
- const files = getStagedFiles();
18
- let hasViolations = false;
19
- files.forEach(file => {
20
- const filePath = path.resolve(process.cwd(), file);
21
- if (fs.existsSync(filePath)) {
22
- const content = fs.readFileSync(filePath, 'utf-8');
23
- const lines = content.split(/\r?\n/);
24
- lines.forEach((line, index) => {
25
- const trimmedLine = line.trim();
26
- // 1. Ignore single-line comments
27
- if (trimmedLine.startsWith('//')) {
28
- return;
29
- }
30
- // 2. Strip string literals ('...', "...", `...`) so we only scan actual code/variables
31
- const lineWithoutStrings = trimmedLine.replace(/(["'`])(?:(?=(\\?))\2.)*?\1/g, '');
32
- if (LOGGING_SECRET_REGEX.test(lineWithoutStrings)) {
33
- console.error(`🚨 [SecureFlow] Secret logging detected in ${file}:${index + 1}`);
34
- console.error(` -> ${line.trim()}`);
35
- hasViolations = true;
36
- }
37
- });
36
+ if (staged.length === 0) {
37
+ console.log('✅ SecureFlow scan passed (nothing staged).');
38
+ return 0;
39
+ }
40
+ const unreadable = [];
41
+ let violationCount = 0;
42
+ for (const path of staged) {
43
+ // The staged blob, not the working-tree file. This is the whole fix: the
44
+ // hook now checks the content that is about to be committed rather than
45
+ // whatever happens to be on disk when it runs.
46
+ const content = readStagedContent(path);
47
+ if (content === null) {
48
+ unreadable.push(path);
49
+ continue;
38
50
  }
39
- });
40
- if (hasViolations) {
41
- console.error('\n❌ SecureFlow blocked this commit. Please remove the exposed secrets/env variables.');
42
- process.exit(1);
51
+ const result = scanFile(path, content);
52
+ reportSkipped(result);
53
+ reportViolations(result);
54
+ violationCount += result.violations.length;
55
+ }
56
+ if (unreadable.length > 0) {
57
+ // Named rather than silently passed. A file we could not read is a file we
58
+ // did not check, and saying so is the difference between a gap and a lie.
59
+ console.warn(`⚠️ [SecureFlow] Could not read ${unreadable.length} staged entr${unreadable.length === 1 ? 'y' : 'ies'} (submodule, symlink or conflicted): ${unreadable.join(', ')}`);
43
60
  }
44
- else {
45
- console.log('✅ SecureFlow scan passed.');
46
- process.exit(0);
61
+ if (violationCount > 0) {
62
+ console.error(`\n❌ SecureFlow blocked this commit: ${violationCount} secret-logging violation${violationCount === 1 ? '' : 's'}. Remove the exposed secrets/env variables, then re-stage.`);
63
+ return 1;
47
64
  }
65
+ console.log(`✅ SecureFlow scan passed (${staged.length} staged file(s)).`);
66
+ return 0;
48
67
  }
49
- scanFiles();
68
+ process.exit(main());
50
69
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,oBAAoB,GAAG,sGAAsG,CAAC;AAEpI,SAAS,cAAc;IACnB,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAChF,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,SAAS,SAAS;IACd,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;QAEnD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAErC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAEhC,iCAAiC;gBACjC,IAAI,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/B,OAAO;gBACX,CAAC;gBAED,uFAAuF;gBACvF,MAAM,kBAAkB,GAAG,WAAW,CAAC,OAAO,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC;gBAEnF,IAAI,oBAAoB,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;oBAChD,OAAO,CAAC,KAAK,CAAC,8CAA8C,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;oBACjF,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBACtC,aAAa,GAAG,IAAI,CAAC;gBACzB,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,aAAa,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,sFAAsF,CAAC,CAAC;QACtG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The secret-logging detector, separated from the CLI shell.
3
+ *
4
+ * Everything here is pure: it takes text and returns violations. The process
5
+ * exit, the git calls and the console output live in `./index` and `./git`, so
6
+ * the part that decides whether a commit is blocked can actually be tested
7
+ * (#593).
8
+ */
9
+ /** One flagged call site. */
10
+ export interface Violation {
11
+ /** 1-based line of the `console.*` call. */
12
+ line: number;
13
+ /** The source line as written, for the report. */
14
+ text: string;
15
+ /** Which indicator matched, so the message can say why. */
16
+ reason: string;
17
+ }
18
+ /**
19
+ * Bytes above which a staged blob is skipped.
20
+ *
21
+ * Nothing bounded this before, so a staged fixture or a vendored bundle was
22
+ * read into memory and scanned line by line.
23
+ */
24
+ export declare const MAX_SCANNED_BYTES: number;
25
+ /** Whether `path` should be scanned at all. */
26
+ export declare function shouldScanFile(path: string, byteLength?: number): boolean;
27
+ /**
28
+ * Heuristic for content that is binary despite its name.
29
+ *
30
+ * A NUL byte does not occur in text. Checking this beats trusting the
31
+ * extension, since a staged blob can be anything.
32
+ */
33
+ export declare function looksBinary(content: string): boolean;
34
+ /**
35
+ * Blank out the *contents* of string literals, preserving length.
36
+ *
37
+ * The previous implementation deleted them outright:
38
+ *
39
+ * trimmedLine.replace(/(["'`])(?:(?=(\\?))\2.)*?\1/g, '')
40
+ *
41
+ * which turned `` console.log(`token: ${authToken}`) `` into `console.log()` —
42
+ * the interpolation went with the quotes, so the single most common way a
43
+ * secret reaches a log line became invisible. Deleting also shifts every column
44
+ * after it, which matters now that whole files are scanned rather than
45
+ * individual lines.
46
+ *
47
+ * Interpolations inside a template literal are left intact, because
48
+ * `${authToken}` is code, not text. Escapes are honoured so a `\"` does not end
49
+ * a literal, and an unterminated quote stops at the newline rather than eating
50
+ * the rest of the file.
51
+ */
52
+ export declare function maskStringLiterals(source: string): string;
53
+ /**
54
+ * Text of the balanced argument list starting at `openParen`, or null when the
55
+ * parenthesis is never closed.
56
+ *
57
+ * Scanning for the matching parenthesis rather than matching a regex is what
58
+ * makes a wrapped call detectable. The old detector tested one line at a time,
59
+ * so the `[\s\S]*?` in its pattern could never span anything and
60
+ *
61
+ * console.log(
62
+ * 'db password:',
63
+ * process.env.DB_PASSWORD
64
+ * );
65
+ *
66
+ * — the shape Prettier produces at default print width — went unflagged.
67
+ */
68
+ export declare function readArgumentList(source: string, openParen: number): string | null;
69
+ /** 1-based line number of `offset`. */
70
+ export declare function lineOf(source: string, offset: number): number;
71
+ /**
72
+ * Find every `console.*` call whose arguments reference a secret.
73
+ *
74
+ * Operates on the whole file, not line by line, so a call split across lines is
75
+ * matched. The reported line is the line the call *starts* on, which is where a
76
+ * reader will look.
77
+ */
78
+ export declare function findSecretLogging(source: string): Violation[];
79
+ /** Result of scanning one staged file. */
80
+ export interface FileScanResult {
81
+ path: string;
82
+ violations: Violation[];
83
+ /** Set when the file was not scanned, with the reason. */
84
+ skipped?: string;
85
+ }
86
+ /** Scan one staged blob. */
87
+ export declare function scanFile(path: string, content: string): FileScanResult;
88
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6BAA6B;AAC7B,MAAM,WAAW,SAAS;IACxB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB;AA8CD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAE5C,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CASzE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEpD;AAKD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA4EzD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYjF;AAED,uCAAuC;AACvC,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CA2B7D;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,4BAA4B;AAC5B,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAUtE"}
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The secret-logging detector, separated from the CLI shell.
3
+ *
4
+ * Everything here is pure: it takes text and returns violations. The process
5
+ * exit, the git calls and the console output live in `./index` and `./git`, so
6
+ * the part that decides whether a commit is blocked can actually be tested
7
+ * (#593).
8
+ */
9
+ /** Console methods that put their arguments somewhere durable. */
10
+ const CONSOLE_METHODS = ['log', 'info', 'warn', 'error', 'debug', 'trace', 'table', 'dir'];
11
+ /**
12
+ * Start of a console call. Whitespace is permitted around the dot and the
13
+ * parenthesis because a formatter will put it there.
14
+ */
15
+ const CONSOLE_CALL = new RegExp(`console\\s*\\.\\s*(?:${CONSOLE_METHODS.join('|')})\\s*\\(`, 'g');
16
+ /**
17
+ * What makes an argument list suspicious.
18
+ *
19
+ * Checked against the *masked* source, so a string literal that merely contains
20
+ * the word "password" does not match — only an identifier or a member
21
+ * expression does.
22
+ */
23
+ const INDICATORS = [
24
+ ['environment variable', /\b(?:process\s*\.\s*env|import\s*\.\s*meta\s*\.\s*env|Deno\s*\.\s*env|os\s*\.\s*environ)\b/],
25
+ ['secret-named identifier', /\b\w*(?:password|passwd|secret|token|credential|apikey|privatekey)\w*\b/i],
26
+ // `key` and `auth` on their own are common enough in ordinary code
27
+ // (`keyof`, `authorised`, `keys`) that they are only flagged when they read
28
+ // as a whole word or as an obvious compound.
29
+ ['secret-named identifier', /\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|auth[_-]?token|authorization)\b/i],
30
+ ];
31
+ /** Extensions never worth scanning as source text. */
32
+ const BINARY_EXTENSIONS = [
33
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.avif', '.bmp', '.tiff',
34
+ '.pdf', '.zip', '.gz', '.tar', '.bz2', '.7z', '.rar',
35
+ '.woff', '.woff2', '.ttf', '.otf', '.eot',
36
+ '.mp3', '.mp4', '.wav', '.mov', '.avi', '.webm',
37
+ '.so', '.dylib', '.dll', '.exe', '.wasm', '.class', '.jar',
38
+ '.sqlite', '.db',
39
+ ];
40
+ /** Generated files that are large, uninteresting, and full of hashes. */
41
+ const GENERATED_FILES = [
42
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'composer.lock',
43
+ 'Cargo.lock', 'Gemfile.lock', 'poetry.lock',
44
+ ];
45
+ /**
46
+ * Bytes above which a staged blob is skipped.
47
+ *
48
+ * Nothing bounded this before, so a staged fixture or a vendored bundle was
49
+ * read into memory and scanned line by line.
50
+ */
51
+ export const MAX_SCANNED_BYTES = 512 * 1024;
52
+ /** Whether `path` should be scanned at all. */
53
+ export function shouldScanFile(path, byteLength) {
54
+ const lower = path.toLowerCase();
55
+ const basename = lower.split('/').pop() ?? lower;
56
+ if (GENERATED_FILES.some((name) => basename === name.toLowerCase()))
57
+ return false;
58
+ if (BINARY_EXTENSIONS.some((ext) => lower.endsWith(ext)))
59
+ return false;
60
+ if (typeof byteLength === 'number' && byteLength > MAX_SCANNED_BYTES)
61
+ return false;
62
+ return true;
63
+ }
64
+ /**
65
+ * Heuristic for content that is binary despite its name.
66
+ *
67
+ * A NUL byte does not occur in text. Checking this beats trusting the
68
+ * extension, since a staged blob can be anything.
69
+ */
70
+ export function looksBinary(content) {
71
+ return content.includes('\u0000');
72
+ }
73
+ /** Filler used where a string literal's contents were. */
74
+ const MASK_CHAR = '·';
75
+ /**
76
+ * Blank out the *contents* of string literals, preserving length.
77
+ *
78
+ * The previous implementation deleted them outright:
79
+ *
80
+ * trimmedLine.replace(/(["'`])(?:(?=(\\?))\2.)*?\1/g, '')
81
+ *
82
+ * which turned `` console.log(`token: ${authToken}`) `` into `console.log()` —
83
+ * the interpolation went with the quotes, so the single most common way a
84
+ * secret reaches a log line became invisible. Deleting also shifts every column
85
+ * after it, which matters now that whole files are scanned rather than
86
+ * individual lines.
87
+ *
88
+ * Interpolations inside a template literal are left intact, because
89
+ * `${authToken}` is code, not text. Escapes are honoured so a `\"` does not end
90
+ * a literal, and an unterminated quote stops at the newline rather than eating
91
+ * the rest of the file.
92
+ */
93
+ export function maskStringLiterals(source) {
94
+ const out = source.split('');
95
+ let index = 0;
96
+ while (index < out.length) {
97
+ const char = out[index];
98
+ // Line comments are masked, not merely skipped over: skipping advances the
99
+ // cursor but leaves the text in the output, so the commented-out code was
100
+ // still scanned. The old detector only handled `//` at the very start of a
101
+ // trimmed line, so a trailing comment was scanned as code either way.
102
+ if (char === '/' && out[index + 1] === '/') {
103
+ while (index < out.length && out[index] !== '\n') {
104
+ out[index] = MASK_CHAR;
105
+ index += 1;
106
+ }
107
+ continue;
108
+ }
109
+ if (char === '/' && out[index + 1] === '*') {
110
+ index += 2;
111
+ while (index < out.length && !(out[index] === '*' && out[index + 1] === '/')) {
112
+ if (out[index] !== '\n')
113
+ out[index] = MASK_CHAR;
114
+ index += 1;
115
+ }
116
+ index += 2;
117
+ continue;
118
+ }
119
+ if (char !== '"' && char !== "'" && char !== '`') {
120
+ index += 1;
121
+ continue;
122
+ }
123
+ const quote = char;
124
+ index += 1;
125
+ while (index < out.length) {
126
+ const current = out[index];
127
+ if (current === '\\') {
128
+ // Escaped character: mask both, and never let it terminate the literal.
129
+ if (out[index] !== '\n')
130
+ out[index] = MASK_CHAR;
131
+ if (index + 1 < out.length && out[index + 1] !== '\n')
132
+ out[index + 1] = MASK_CHAR;
133
+ index += 2;
134
+ continue;
135
+ }
136
+ if (current === quote) {
137
+ index += 1;
138
+ break;
139
+ }
140
+ // An unterminated quote must not swallow the rest of the file. Only a
141
+ // template literal legally spans lines.
142
+ if (current === '\n' && quote !== '`')
143
+ break;
144
+ // `${ … }` inside a template literal is code. Leave it visible, tracking
145
+ // brace depth so a nested object literal does not end it early.
146
+ if (quote === '`' && current === '$' && out[index + 1] === '{') {
147
+ let depth = 1;
148
+ index += 2;
149
+ while (index < out.length && depth > 0) {
150
+ if (out[index] === '{')
151
+ depth += 1;
152
+ else if (out[index] === '}')
153
+ depth -= 1;
154
+ index += 1;
155
+ }
156
+ continue;
157
+ }
158
+ if (current !== '\n')
159
+ out[index] = MASK_CHAR;
160
+ index += 1;
161
+ }
162
+ }
163
+ return out.join('');
164
+ }
165
+ /**
166
+ * Text of the balanced argument list starting at `openParen`, or null when the
167
+ * parenthesis is never closed.
168
+ *
169
+ * Scanning for the matching parenthesis rather than matching a regex is what
170
+ * makes a wrapped call detectable. The old detector tested one line at a time,
171
+ * so the `[\s\S]*?` in its pattern could never span anything and
172
+ *
173
+ * console.log(
174
+ * 'db password:',
175
+ * process.env.DB_PASSWORD
176
+ * );
177
+ *
178
+ * — the shape Prettier produces at default print width — went unflagged.
179
+ */
180
+ export function readArgumentList(source, openParen) {
181
+ let depth = 0;
182
+ for (let i = openParen; i < source.length; i += 1) {
183
+ if (source[i] === '(')
184
+ depth += 1;
185
+ else if (source[i] === ')') {
186
+ depth -= 1;
187
+ if (depth === 0)
188
+ return source.slice(openParen + 1, i);
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+ /** 1-based line number of `offset`. */
194
+ export function lineOf(source, offset) {
195
+ let line = 1;
196
+ for (let i = 0; i < offset && i < source.length; i += 1) {
197
+ if (source[i] === '\n')
198
+ line += 1;
199
+ }
200
+ return line;
201
+ }
202
+ /**
203
+ * Find every `console.*` call whose arguments reference a secret.
204
+ *
205
+ * Operates on the whole file, not line by line, so a call split across lines is
206
+ * matched. The reported line is the line the call *starts* on, which is where a
207
+ * reader will look.
208
+ */
209
+ export function findSecretLogging(source) {
210
+ if (!source)
211
+ return [];
212
+ const masked = maskStringLiterals(source);
213
+ const lines = source.split(/\r?\n/);
214
+ const violations = [];
215
+ CONSOLE_CALL.lastIndex = 0;
216
+ let match;
217
+ while ((match = CONSOLE_CALL.exec(masked)) !== null) {
218
+ const openParen = match.index + match[0].length - 1;
219
+ const args = readArgumentList(masked, openParen);
220
+ if (args === null)
221
+ continue;
222
+ const indicator = INDICATORS.find(([, pattern]) => pattern.test(args));
223
+ if (!indicator)
224
+ continue;
225
+ const line = lineOf(masked, match.index);
226
+ violations.push({
227
+ line,
228
+ text: (lines[line - 1] ?? '').trim(),
229
+ reason: indicator[0],
230
+ });
231
+ }
232
+ return violations;
233
+ }
234
+ /** Scan one staged blob. */
235
+ export function scanFile(path, content) {
236
+ if (!shouldScanFile(path, Buffer.byteLength(content, 'utf-8'))) {
237
+ return { path, violations: [], skipped: 'excluded by type or size' };
238
+ }
239
+ if (looksBinary(content)) {
240
+ return { path, violations: [], skipped: 'binary content' };
241
+ }
242
+ return { path, violations: findSecretLogging(content) };
243
+ }
244
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secureflow-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Pre-commit secret logging detector for secure development workflows",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
@@ -29,4 +29,4 @@
29
29
  "@types/node": "^26.2.0",
30
30
  "typescript": "^7.0.2"
31
31
  }
32
- }
32
+ }