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 +51 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +96 -0
- package/dist/git.js.map +1 -0
- package/dist/index.js +59 -40
- package/dist/index.js.map +1 -1
- package/dist/scanner.d.ts +88 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +244 -0
- package/dist/scanner.js.map +1 -0
- package/package.json +2 -2
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
|
package/dist/git.js.map
ADDED
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
return output.split('\n').filter(Boolean);
|
|
27
|
+
staged = getStagedFiles();
|
|
10
28
|
}
|
|
11
29
|
catch (error) {
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
console.
|
|
46
|
-
|
|
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
|
-
|
|
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,
|
|
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"}
|
package/dist/scanner.js
ADDED
|
@@ -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.
|
|
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
|
+
}
|