secureflow-cli 1.0.0 → 1.2.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,101 @@
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
+ import fs from 'fs';
3
+ import { GitError, getStagedFiles, readStagedContent } from './git.js';
4
+ import { scanFile, formatScanResults } from './scanner.js';
5
+ const VERBOSE = process.argv.includes('--verbose');
6
+ function parseFormatArg() {
7
+ const formatIndex = process.argv.findIndex((arg) => arg === '--format');
8
+ if (formatIndex !== -1) {
9
+ const valStr = process.argv[formatIndex + 1];
10
+ if (valStr) {
11
+ const val = valStr.toLowerCase();
12
+ if (val === 'sarif' || val === 'json' || val === 'text') {
13
+ return val;
14
+ }
15
+ }
16
+ }
17
+ return 'text';
18
+ }
19
+ function parseOutputArg() {
20
+ const outIndex = process.argv.findIndex((arg) => arg === '-o' || arg === '--output');
21
+ if (outIndex !== -1) {
22
+ const valStr = process.argv[outIndex + 1];
23
+ if (valStr) {
24
+ return valStr;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ function reportSkipped(result) {
30
+ if (VERBOSE && result.skipped) {
31
+ console.log(` ↷ skipped ${result.path} (${result.skipped})`);
32
+ }
33
+ }
34
+ function reportViolations(result) {
35
+ for (const violation of result.violations) {
36
+ console.error(`🚨 [SecureFlow] Secret logging detected in ${result.path}:${violation.line}`);
37
+ console.error(` -> ${violation.text}`);
38
+ console.error(` why: ${violation.reason} passed to a console call`);
39
+ }
40
+ }
41
+ function main() {
42
+ const format = parseFormatArg();
43
+ const outputPath = parseOutputArg();
44
+ let staged;
7
45
  try {
8
- const output = execSync('git diff --cached --name-only', { encoding: 'utf-8' });
9
- return output.split('\n').filter(Boolean);
46
+ staged = getStagedFiles();
10
47
  }
11
48
  catch (error) {
12
- console.error('Failed to get staged files. Are you in a git repository?');
13
- process.exit(1);
49
+ console.error(`❌ [SecureFlow] ${error instanceof GitError ? error.message : String(error)}`);
50
+ return 1;
14
51
  }
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
- });
52
+ const fileResults = [];
53
+ const unreadable = [];
54
+ let violationCount = 0;
55
+ if (staged.length > 0) {
56
+ for (const path of staged) {
57
+ const content = readStagedContent(path);
58
+ if (content === null) {
59
+ unreadable.push(path);
60
+ continue;
61
+ }
62
+ const result = scanFile(path, content);
63
+ fileResults.push(result);
64
+ if (format === 'text') {
65
+ reportSkipped(result);
66
+ reportViolations(result);
67
+ }
68
+ violationCount += result.violations.length;
69
+ }
70
+ }
71
+ if (format === 'sarif' || format === 'json') {
72
+ const outputString = formatScanResults(fileResults, format);
73
+ if (outputPath) {
74
+ fs.writeFileSync(outputPath, outputString, 'utf-8');
75
+ console.log(`📄 [SecureFlow] Scan report exported in ${format.toUpperCase()} format to ${outputPath}`);
76
+ }
77
+ else {
78
+ console.log(outputString);
79
+ }
80
+ }
81
+ else if (outputPath) {
82
+ const textOutput = formatScanResults(fileResults, 'text');
83
+ fs.writeFileSync(outputPath, textOutput, 'utf-8');
84
+ console.log(`📄 [SecureFlow] Scan report written to ${outputPath}`);
85
+ }
86
+ if (unreadable.length > 0 && format === 'text') {
87
+ console.warn(`⚠️ [SecureFlow] Could not read ${unreadable.length} staged entr${unreadable.length === 1 ? 'y' : 'ies'} (submodule, symlink or conflicted): ${unreadable.join(', ')}`);
88
+ }
89
+ if (violationCount > 0) {
90
+ if (format === 'text') {
91
+ console.error(`\n❌ SecureFlow blocked this commit: ${violationCount} secret-logging violation${violationCount === 1 ? '' : 's'}. Remove the exposed secrets/env variables, then re-stage.`);
38
92
  }
39
- });
40
- if (hasViolations) {
41
- console.error('\n❌ SecureFlow blocked this commit. Please remove the exposed secrets/env variables.');
42
- process.exit(1);
93
+ return 1;
43
94
  }
44
- else {
45
- console.log('✅ SecureFlow scan passed.');
46
- process.exit(0);
95
+ if (format === 'text') {
96
+ console.log(`✅ SecureFlow scan passed (${staged.length} staged file(s)).`);
47
97
  }
98
+ return 0;
48
99
  }
49
- scanFiles();
100
+ process.exit(main());
50
101
  //# 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,84 @@
1
+ /**
2
+ * SARIF (Static Analysis Results Interchange Format) Exporter for SecureFlow CLI (#728)
3
+ *
4
+ * Converts CLI scan results into standardized OASIS SARIF v2.1.0 schema format
5
+ * for direct ingestion by GitHub Advanced Security, GitLab Security Dashboards, and enterprise CI/CD systems.
6
+ */
7
+ import type { FileScanResult } from './scanner.js';
8
+ export interface SarifArtifactLocation {
9
+ uri: string;
10
+ uriBaseId?: string;
11
+ }
12
+ export interface SarifRegion {
13
+ startLine: number;
14
+ startColumn?: number;
15
+ endLine?: number;
16
+ endColumn?: number;
17
+ snippet?: {
18
+ text: string;
19
+ };
20
+ }
21
+ export interface SarifPhysicalLocation {
22
+ artifactLocation: SarifArtifactLocation;
23
+ region: SarifRegion;
24
+ }
25
+ export interface SarifLocation {
26
+ physicalLocation: SarifPhysicalLocation;
27
+ }
28
+ export interface SarifReportingDescriptor {
29
+ id: string;
30
+ name: string;
31
+ shortDescription: {
32
+ text: string;
33
+ };
34
+ fullDescription?: {
35
+ text: string;
36
+ };
37
+ defaultConfiguration?: {
38
+ level: 'error' | 'warning' | 'note' | 'none';
39
+ };
40
+ helpUri?: string;
41
+ properties?: Record<string, unknown>;
42
+ }
43
+ export interface SarifResult {
44
+ ruleId: string;
45
+ ruleIndex?: number;
46
+ level: 'error' | 'warning' | 'note' | 'none';
47
+ message: {
48
+ text: string;
49
+ };
50
+ locations: SarifLocation[];
51
+ properties?: Record<string, unknown>;
52
+ }
53
+ export interface SarifRun {
54
+ tool: {
55
+ driver: {
56
+ name: string;
57
+ organization?: string;
58
+ version?: string;
59
+ semanticVersion?: string;
60
+ informationUri?: string;
61
+ rules: SarifReportingDescriptor[];
62
+ };
63
+ };
64
+ results: SarifResult[];
65
+ }
66
+ export interface SarifDocument {
67
+ $schema: string;
68
+ version: '2.1.0';
69
+ runs: SarifRun[];
70
+ }
71
+ /**
72
+ * Maps CLI scan results into a valid OASIS SARIF v2.1.0 document object.
73
+ */
74
+ export declare function generateSarifReport(scanResults: FileScanResult[], options?: {
75
+ toolVersion?: string;
76
+ repoUri?: string;
77
+ }): SarifDocument;
78
+ /**
79
+ * Format SARIF report as pretty JSON string.
80
+ */
81
+ export declare function formatSarifJson(scanResults: FileScanResult[], options?: {
82
+ toolVersion?: string;
83
+ }): string;
84
+ //# sourceMappingURL=sarif.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif.d.ts","sourceRoot":"","sources":["../src/sarif.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,qBAAqB,CAAC;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,eAAe,CAAC,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,oBAAoB,CAAC,EAAE;QACrB,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;KAC9C,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7C,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,MAAM,CAAC;YACb,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,cAAc,CAAC,EAAE,MAAM,CAAC;YACxB,KAAK,EAAE,wBAAwB,EAAE,CAAC;SACnC,CAAC;KACH,CAAC;IACF,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AA4CD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,cAAc,EAAE,EAC7B,OAAO,CAAC,EAAE;IACR,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,aAAa,CAmFf;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGzG"}
package/dist/sarif.js ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * SARIF (Static Analysis Results Interchange Format) Exporter for SecureFlow CLI (#728)
3
+ *
4
+ * Converts CLI scan results into standardized OASIS SARIF v2.1.0 schema format
5
+ * for direct ingestion by GitHub Advanced Security, GitLab Security Dashboards, and enterprise CI/CD systems.
6
+ */
7
+ const DEFAULT_RULE_DEFINITIONS = {
8
+ 'environment variable': {
9
+ id: 'SECUREFLOW-001',
10
+ name: 'ConsoleSecretLoggingEnvironmentVariable',
11
+ shortDescription: {
12
+ text: 'Console logging of environment variable containing potential secret',
13
+ },
14
+ fullDescription: {
15
+ text: 'Passing process.env or other environment variable getters into console methods exposes credentials in build logs or standard output.',
16
+ },
17
+ defaultConfiguration: {
18
+ level: 'error',
19
+ },
20
+ helpUri: 'https://github.com/GauravKarakoti/SecureFlow#rules',
21
+ },
22
+ 'secret-named identifier': {
23
+ id: 'SECUREFLOW-002',
24
+ name: 'ConsoleSecretLoggingSecretIdentifier',
25
+ shortDescription: {
26
+ text: 'Console logging of secret-named identifier or credential parameter',
27
+ },
28
+ fullDescription: {
29
+ text: 'Identifiers containing secret, password, apikey, token, or auth parameters passed directly into console logging statements.',
30
+ },
31
+ defaultConfiguration: {
32
+ level: 'error',
33
+ },
34
+ helpUri: 'https://github.com/GauravKarakoti/SecureFlow#rules',
35
+ },
36
+ 'generic-secret-logging': {
37
+ id: 'SECUREFLOW-000',
38
+ name: 'ConsoleSecretLoggingGeneric',
39
+ shortDescription: {
40
+ text: 'Potential secret credential logging in console output',
41
+ },
42
+ defaultConfiguration: {
43
+ level: 'error',
44
+ },
45
+ helpUri: 'https://github.com/GauravKarakoti/SecureFlow#rules',
46
+ },
47
+ };
48
+ /**
49
+ * Maps CLI scan results into a valid OASIS SARIF v2.1.0 document object.
50
+ */
51
+ export function generateSarifReport(scanResults, options) {
52
+ const version = options?.toolVersion || '0.1.0';
53
+ const rulesMap = new Map();
54
+ const sarifResults = [];
55
+ for (const fileResult of scanResults) {
56
+ if (!fileResult.violations || fileResult.violations.length === 0) {
57
+ continue;
58
+ }
59
+ for (const violation of fileResult.violations) {
60
+ const reasonKey = violation.reason || 'generic-secret-logging';
61
+ let ruleInfo = rulesMap.get(reasonKey);
62
+ if (!ruleInfo) {
63
+ const descriptor = DEFAULT_RULE_DEFINITIONS[reasonKey] || {
64
+ id: `SECUREFLOW-${rulesMap.size + 100}`,
65
+ name: `ConsoleSecretLoggingCustomRule${rulesMap.size + 1}`,
66
+ shortDescription: {
67
+ text: `Console logging of ${violation.reason}`,
68
+ },
69
+ defaultConfiguration: {
70
+ level: 'error',
71
+ },
72
+ helpUri: 'https://github.com/GauravKarakoti/SecureFlow#rules',
73
+ };
74
+ ruleInfo = { descriptor, index: rulesMap.size };
75
+ rulesMap.set(reasonKey, ruleInfo);
76
+ }
77
+ sarifResults.push({
78
+ ruleId: ruleInfo.descriptor.id,
79
+ ruleIndex: ruleInfo.index,
80
+ level: ruleInfo.descriptor.defaultConfiguration?.level || 'error',
81
+ message: {
82
+ text: `[SecureFlow] ${violation.reason} passed to console call: "${violation.text}"`,
83
+ },
84
+ locations: [
85
+ {
86
+ physicalLocation: {
87
+ artifactLocation: {
88
+ uri: fileResult.path.replace(/\\/g, '/'),
89
+ },
90
+ region: {
91
+ startLine: Math.max(1, violation.line),
92
+ snippet: {
93
+ text: violation.text,
94
+ },
95
+ },
96
+ },
97
+ },
98
+ ],
99
+ properties: {
100
+ reason: violation.reason,
101
+ },
102
+ });
103
+ }
104
+ }
105
+ const rulesArray = Array.from(rulesMap.values()).map((r) => r.descriptor);
106
+ if (rulesArray.length === 0) {
107
+ // Appended `!` to override 'undefined' error resulting from Record<string, ...> signature
108
+ rulesArray.push(DEFAULT_RULE_DEFINITIONS['generic-secret-logging']);
109
+ }
110
+ return {
111
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
112
+ version: '2.1.0',
113
+ runs: [
114
+ {
115
+ tool: {
116
+ driver: {
117
+ name: 'SecureFlow CLI',
118
+ semanticVersion: version,
119
+ informationUri: 'https://github.com/GauravKarakoti/SecureFlow',
120
+ rules: rulesArray,
121
+ },
122
+ },
123
+ results: sarifResults,
124
+ },
125
+ ],
126
+ };
127
+ }
128
+ /**
129
+ * Format SARIF report as pretty JSON string.
130
+ */
131
+ export function formatSarifJson(scanResults, options) {
132
+ const sarifDoc = generateSarifReport(scanResults, options);
133
+ return JSON.stringify(sarifDoc, null, 2);
134
+ }
135
+ //# sourceMappingURL=sarif.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif.js","sourceRoot":"","sources":["../src/sarif.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
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
+ export type OutputFormat = 'text' | 'json' | 'sarif';
89
+ /**
90
+ * Format scan results based on the chosen output format ('text' | 'json' | 'sarif').
91
+ */
92
+ export declare function formatScanResults(results: FileScanResult[], format?: OutputFormat): string;
93
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,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;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,cAAc,EAAE,EACzB,MAAM,GAAE,YAAqB,GAC5B,MAAM,CAmBR"}
@@ -0,0 +1,266 @@
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
+ import { formatSarifJson } from './sarif.js';
10
+ /** Console methods that put their arguments somewhere durable. */
11
+ const CONSOLE_METHODS = ['log', 'info', 'warn', 'error', 'debug', 'trace', 'table', 'dir'];
12
+ /**
13
+ * Start of a console call. Whitespace is permitted around the dot and the
14
+ * parenthesis because a formatter will put it there.
15
+ */
16
+ const CONSOLE_CALL = new RegExp(`console\\s*\\.\\s*(?:${CONSOLE_METHODS.join('|')})\\s*\\(`, 'g');
17
+ /**
18
+ * What makes an argument list suspicious.
19
+ *
20
+ * Checked against the *masked* source, so a string literal that merely contains
21
+ * the word "password" does not match — only an identifier or a member
22
+ * expression does.
23
+ */
24
+ const INDICATORS = [
25
+ ['environment variable', /\b(?:process\s*\.\s*env|import\s*\.\s*meta\s*\.\s*env|Deno\s*\.\s*env|os\s*\.\s*environ)\b/],
26
+ ['secret-named identifier', /\b\w*(?:password|passwd|secret|token|credential|apikey|privatekey)\w*\b/i],
27
+ // `key` and `auth` on their own are common enough in ordinary code
28
+ // (`keyof`, `authorised`, `keys`) that they are only flagged when they read
29
+ // as a whole word or as an obvious compound.
30
+ ['secret-named identifier', /\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|auth[_-]?token|authorization)\b/i],
31
+ ];
32
+ /** Extensions never worth scanning as source text. */
33
+ const BINARY_EXTENSIONS = [
34
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.avif', '.bmp', '.tiff',
35
+ '.pdf', '.zip', '.gz', '.tar', '.bz2', '.7z', '.rar',
36
+ '.woff', '.woff2', '.ttf', '.otf', '.eot',
37
+ '.mp3', '.mp4', '.wav', '.mov', '.avi', '.webm',
38
+ '.so', '.dylib', '.dll', '.exe', '.wasm', '.class', '.jar',
39
+ '.sqlite', '.db',
40
+ ];
41
+ /** Generated files that are large, uninteresting, and full of hashes. */
42
+ const GENERATED_FILES = [
43
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'composer.lock',
44
+ 'Cargo.lock', 'Gemfile.lock', 'poetry.lock',
45
+ ];
46
+ /**
47
+ * Bytes above which a staged blob is skipped.
48
+ *
49
+ * Nothing bounded this before, so a staged fixture or a vendored bundle was
50
+ * read into memory and scanned line by line.
51
+ */
52
+ export const MAX_SCANNED_BYTES = 512 * 1024;
53
+ /** Whether `path` should be scanned at all. */
54
+ export function shouldScanFile(path, byteLength) {
55
+ const lower = path.toLowerCase();
56
+ const basename = lower.split('/').pop() ?? lower;
57
+ if (GENERATED_FILES.some((name) => basename === name.toLowerCase()))
58
+ return false;
59
+ if (BINARY_EXTENSIONS.some((ext) => lower.endsWith(ext)))
60
+ return false;
61
+ if (typeof byteLength === 'number' && byteLength > MAX_SCANNED_BYTES)
62
+ return false;
63
+ return true;
64
+ }
65
+ /**
66
+ * Heuristic for content that is binary despite its name.
67
+ *
68
+ * A NUL byte does not occur in text. Checking this beats trusting the
69
+ * extension, since a staged blob can be anything.
70
+ */
71
+ export function looksBinary(content) {
72
+ return content.includes('\u0000');
73
+ }
74
+ /** Filler used where a string literal's contents were. */
75
+ const MASK_CHAR = '·';
76
+ /**
77
+ * Blank out the *contents* of string literals, preserving length.
78
+ *
79
+ * The previous implementation deleted them outright:
80
+ *
81
+ * trimmedLine.replace(/(["'`])(?:(?=(\\?))\2.)*?\1/g, '')
82
+ *
83
+ * which turned `` console.log(`token: ${authToken}`) `` into `console.log()` —
84
+ * the interpolation went with the quotes, so the single most common way a
85
+ * secret reaches a log line became invisible. Deleting also shifts every column
86
+ * after it, which matters now that whole files are scanned rather than
87
+ * individual lines.
88
+ *
89
+ * Interpolations inside a template literal are left intact, because
90
+ * `${authToken}` is code, not text. Escapes are honoured so a `\"` does not end
91
+ * a literal, and an unterminated quote stops at the newline rather than eating
92
+ * the rest of the file.
93
+ */
94
+ export function maskStringLiterals(source) {
95
+ const out = source.split('');
96
+ let index = 0;
97
+ while (index < out.length) {
98
+ const char = out[index];
99
+ // Line comments are masked, not merely skipped over: skipping advances the
100
+ // cursor but leaves the text in the output, so the commented-out code was
101
+ // still scanned. The old detector only handled `//` at the very start of a
102
+ // trimmed line, so a trailing comment was scanned as code either way.
103
+ if (char === '/' && out[index + 1] === '/') {
104
+ while (index < out.length && out[index] !== '\n') {
105
+ out[index] = MASK_CHAR;
106
+ index += 1;
107
+ }
108
+ continue;
109
+ }
110
+ if (char === '/' && out[index + 1] === '*') {
111
+ index += 2;
112
+ while (index < out.length && !(out[index] === '*' && out[index + 1] === '/')) {
113
+ if (out[index] !== '\n')
114
+ out[index] = MASK_CHAR;
115
+ index += 1;
116
+ }
117
+ index += 2;
118
+ continue;
119
+ }
120
+ if (char !== '"' && char !== "'" && char !== '`') {
121
+ index += 1;
122
+ continue;
123
+ }
124
+ const quote = char;
125
+ index += 1;
126
+ while (index < out.length) {
127
+ const current = out[index];
128
+ if (current === '\\') {
129
+ // Escaped character: mask both, and never let it terminate the literal.
130
+ if (out[index] !== '\n')
131
+ out[index] = MASK_CHAR;
132
+ if (index + 1 < out.length && out[index + 1] !== '\n')
133
+ out[index + 1] = MASK_CHAR;
134
+ index += 2;
135
+ continue;
136
+ }
137
+ if (current === quote) {
138
+ index += 1;
139
+ break;
140
+ }
141
+ // An unterminated quote must not swallow the rest of the file. Only a
142
+ // template literal legally spans lines.
143
+ if (current === '\n' && quote !== '`')
144
+ break;
145
+ // `${ … }` inside a template literal is code. Leave it visible, tracking
146
+ // brace depth so a nested object literal does not end it early.
147
+ if (quote === '`' && current === '$' && out[index + 1] === '{') {
148
+ let depth = 1;
149
+ index += 2;
150
+ while (index < out.length && depth > 0) {
151
+ if (out[index] === '{')
152
+ depth += 1;
153
+ else if (out[index] === '}')
154
+ depth -= 1;
155
+ index += 1;
156
+ }
157
+ continue;
158
+ }
159
+ if (current !== '\n')
160
+ out[index] = MASK_CHAR;
161
+ index += 1;
162
+ }
163
+ }
164
+ return out.join('');
165
+ }
166
+ /**
167
+ * Text of the balanced argument list starting at `openParen`, or null when the
168
+ * parenthesis is never closed.
169
+ *
170
+ * Scanning for the matching parenthesis rather than matching a regex is what
171
+ * makes a wrapped call detectable. The old detector tested one line at a time,
172
+ * so the `[\s\S]*?` in its pattern could never span anything and
173
+ *
174
+ * console.log(
175
+ * 'db password:',
176
+ * process.env.DB_PASSWORD
177
+ * );
178
+ *
179
+ * — the shape Prettier produces at default print width — went unflagged.
180
+ */
181
+ export function readArgumentList(source, openParen) {
182
+ let depth = 0;
183
+ for (let i = openParen; i < source.length; i += 1) {
184
+ if (source[i] === '(')
185
+ depth += 1;
186
+ else if (source[i] === ')') {
187
+ depth -= 1;
188
+ if (depth === 0)
189
+ return source.slice(openParen + 1, i);
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ /** 1-based line number of `offset`. */
195
+ export function lineOf(source, offset) {
196
+ let line = 1;
197
+ for (let i = 0; i < offset && i < source.length; i += 1) {
198
+ if (source[i] === '\n')
199
+ line += 1;
200
+ }
201
+ return line;
202
+ }
203
+ /**
204
+ * Find every `console.*` call whose arguments reference a secret.
205
+ *
206
+ * Operates on the whole file, not line by line, so a call split across lines is
207
+ * matched. The reported line is the line the call *starts* on, which is where a
208
+ * reader will look.
209
+ */
210
+ export function findSecretLogging(source) {
211
+ if (!source)
212
+ return [];
213
+ const masked = maskStringLiterals(source);
214
+ const lines = source.split(/\r?\n/);
215
+ const violations = [];
216
+ CONSOLE_CALL.lastIndex = 0;
217
+ let match;
218
+ while ((match = CONSOLE_CALL.exec(masked)) !== null) {
219
+ const openParen = match.index + match[0].length - 1;
220
+ const args = readArgumentList(masked, openParen);
221
+ if (args === null)
222
+ continue;
223
+ const indicator = INDICATORS.find(([, pattern]) => pattern.test(args));
224
+ if (!indicator)
225
+ continue;
226
+ const line = lineOf(masked, match.index);
227
+ violations.push({
228
+ line,
229
+ text: (lines[line - 1] ?? '').trim(),
230
+ reason: indicator[0],
231
+ });
232
+ }
233
+ return violations;
234
+ }
235
+ /** Scan one staged blob. */
236
+ export function scanFile(path, content) {
237
+ if (!shouldScanFile(path, Buffer.byteLength(content, 'utf-8'))) {
238
+ return { path, violations: [], skipped: 'excluded by type or size' };
239
+ }
240
+ if (looksBinary(content)) {
241
+ return { path, violations: [], skipped: 'binary content' };
242
+ }
243
+ return { path, violations: findSecretLogging(content) };
244
+ }
245
+ /**
246
+ * Format scan results based on the chosen output format ('text' | 'json' | 'sarif').
247
+ */
248
+ export function formatScanResults(results, format = 'text') {
249
+ if (format === 'json') {
250
+ return JSON.stringify(results, null, 2);
251
+ }
252
+ if (format === 'sarif') {
253
+ return formatSarifJson(results);
254
+ }
255
+ // Default text summary
256
+ let text = '';
257
+ for (const r of results) {
258
+ for (const v of r.violations) {
259
+ text += `🚨 [SecureFlow] Secret logging detected in ${r.path}:${v.line}\n`;
260
+ text += ` -> ${v.text}\n`;
261
+ text += ` why: ${v.reason} passed to a console call\n`;
262
+ }
263
+ }
264
+ return text;
265
+ }
266
+ //# 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,32 +1,35 @@
1
- {
2
- "name": "secureflow-cli",
3
- "version": "1.0.0",
4
- "description": "Pre-commit secret logging detector for secure development workflows",
5
- "main": "./dist/index.js",
6
- "bin": {
7
- "secureflow": "./dist/index.js"
8
- },
9
- "scripts": {
10
- "build": "tsc",
11
- "prepare": "npm run build",
12
- "publish": "npm publish --access public"
13
- },
14
- "keywords": [
15
- "security",
16
- "secrets",
17
- "scanner",
18
- "pre-commit",
19
- "husky",
20
- "secureflow"
21
- ],
22
- "author": "Gaurav Karakoti",
23
- "license": "ISC",
24
- "type": "module",
25
- "files": [
26
- "dist"
27
- ],
28
- "devDependencies": {
29
- "@types/node": "^26.2.0",
30
- "typescript": "^7.0.2"
31
- }
32
- }
1
+ {
2
+ "name": "secureflow-cli",
3
+ "version": "1.2.0",
4
+ "description": "Pre-commit secret logging detector for secure development workflows",
5
+ "main": "./dist/index.js",
6
+ "bin": {
7
+ "secureflow": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepare": "npm run build",
12
+ "publish": "npm publish --access public",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
15
+ },
16
+ "keywords": [
17
+ "security",
18
+ "secrets",
19
+ "scanner",
20
+ "pre-commit",
21
+ "husky",
22
+ "secureflow"
23
+ ],
24
+ "author": "Gaurav Karakoti",
25
+ "license": "ISC",
26
+ "type": "module",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "devDependencies": {
31
+ "@types/node": "^26.4.0",
32
+ "typescript": "^6",
33
+ "vitest": "^4.1.11"
34
+ }
35
+ }