@vibecodeqa/cli 0.39.0 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runners/secrets.d.ts +4 -2
- package/dist/runners/secrets.js +63 -28
- package/package.json +3 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
/** Secret detection — delegates to gitleaks when available
|
|
1
|
+
/** Secret detection — delegates to gitleaks when available; otherwise scans with
|
|
2
|
+
* secretlint's recommended ruleset (broad coverage) PLUS our own patterns (which
|
|
3
|
+
* add LLM keys — OpenAI/Anthropic — that secretlint's preset doesn't cover). */
|
|
2
4
|
import type { CheckResult } from "../types.js";
|
|
3
|
-
export declare function runSecrets(cwd: string): CheckResult
|
|
5
|
+
export declare function runSecrets(cwd: string): Promise<CheckResult>;
|
package/dist/runners/secrets.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
/** Secret detection — delegates to gitleaks when available
|
|
1
|
+
/** Secret detection — delegates to gitleaks when available; otherwise scans with
|
|
2
|
+
* secretlint's recommended ruleset (broad coverage) PLUS our own patterns (which
|
|
3
|
+
* add LLM keys — OpenAI/Anthropic — that secretlint's preset doesn't cover). */
|
|
2
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
5
|
import { join } from "node:path";
|
|
6
|
+
import { lintSource } from "@secretlint/core";
|
|
7
|
+
import { creator as secretlintPreset } from "@secretlint/secretlint-rule-preset-recommend";
|
|
4
8
|
import { collectAllFiles } from "../fs-utils.js";
|
|
5
9
|
import { gradeFromScore } from "../types.js";
|
|
6
10
|
import { run } from "./exec.js";
|
|
11
|
+
const SECRETLINT_CONFIG = { rules: [{ id: "@secretlint/secretlint-rule-preset-recommend", rule: secretlintPreset }] };
|
|
12
|
+
/** Human-readable kind from a secretlint message, without leaking the secret value. */
|
|
13
|
+
function secretlintKind(msg) {
|
|
14
|
+
if (msg.messageId)
|
|
15
|
+
return msg.messageId.replace(/_/g, " ").toLowerCase();
|
|
16
|
+
return (msg.ruleId ?? "secret").replace(/.*secretlint-rule-/, "");
|
|
17
|
+
}
|
|
7
18
|
/** Try running gitleaks for secret detection. Returns true if gitleaks ran. */
|
|
8
19
|
function tryGitleaks(cwd, issues) {
|
|
9
20
|
const { stdout, ok } = run("gitleaks detect --no-git --report-format json --report-path /dev/stdout 2>/dev/null", cwd, 30_000);
|
|
@@ -58,37 +69,61 @@ const SECRET_PATTERNS = [
|
|
|
58
69
|
pattern: /(?:password|secret|api_key|apikey|token|auth)\s*[:=]\s*['"][A-Za-z0-9+/=]{20,}['"]/,
|
|
59
70
|
},
|
|
60
71
|
];
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!gitleaksResult) {
|
|
68
|
-
// Fallback: built-in regex patterns
|
|
69
|
-
const sourceFiles = collectAllFiles(cwd, { extraExts: true });
|
|
70
|
-
for (const sf of sourceFiles) {
|
|
71
|
-
if (sf.isTest || sf.path.includes("__mock"))
|
|
72
|
+
function scanPatterns(files, add) {
|
|
73
|
+
for (const sf of files) {
|
|
74
|
+
const lines = sf.content.split("\n");
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
const line = lines[i];
|
|
77
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
72
78
|
continue;
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
77
|
-
continue;
|
|
78
|
-
for (const { name, pattern } of SECRET_PATTERNS) {
|
|
79
|
-
if (pattern.test(line)) {
|
|
80
|
-
issues.push({
|
|
81
|
-
severity: "error",
|
|
82
|
-
message: `Possible ${name}`,
|
|
83
|
-
file: sf.path,
|
|
84
|
-
line: i + 1,
|
|
85
|
-
rule: "secret-detected",
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
79
|
+
for (const { name, pattern } of SECRET_PATTERNS) {
|
|
80
|
+
if (pattern.test(line))
|
|
81
|
+
add({ severity: "error", message: `Possible ${name}`, file: sf.path, line: i + 1, rule: "secret-detected" });
|
|
89
82
|
}
|
|
90
83
|
}
|
|
91
84
|
}
|
|
85
|
+
}
|
|
86
|
+
async function scanSecretlint(files, add) {
|
|
87
|
+
for (const sf of files) {
|
|
88
|
+
try {
|
|
89
|
+
const result = await lintSource({
|
|
90
|
+
source: { filePath: sf.path, content: sf.content, contentType: "text" },
|
|
91
|
+
options: { config: SECRETLINT_CONFIG },
|
|
92
|
+
});
|
|
93
|
+
for (const m of result.messages) {
|
|
94
|
+
add({ severity: "error", message: `Possible ${secretlintKind(m)} — remove and rotate`, file: sf.path, line: m.loc?.start?.line ?? 1, rule: "secret-detected" });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* unparseable file — skip */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Built-in fallback when gitleaks isn't installed: curated patterns first (nice
|
|
103
|
+
* names + LLM keys, winning dedup), then secretlint's broad ruleset augments. */
|
|
104
|
+
async function scanFallback(cwd) {
|
|
105
|
+
const files = collectAllFiles(cwd, { extraExts: true }).filter((sf) => !sf.isTest && !sf.path.includes("__mock"));
|
|
106
|
+
const issues = [];
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const add = (iss) => {
|
|
109
|
+
const key = `${iss.file}:${iss.line}`;
|
|
110
|
+
if (!seen.has(key)) {
|
|
111
|
+
seen.add(key);
|
|
112
|
+
issues.push(iss);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
scanPatterns(files, add);
|
|
116
|
+
await scanSecretlint(files, add);
|
|
117
|
+
return issues;
|
|
118
|
+
}
|
|
119
|
+
export async function runSecrets(cwd) {
|
|
120
|
+
const start = Date.now();
|
|
121
|
+
const issues = [];
|
|
122
|
+
// Try gitleaks first (industry standard, 800+ patterns)
|
|
123
|
+
const gitleaksResult = tryGitleaks(cwd, issues);
|
|
124
|
+
const tool = gitleaksResult ? "gitleaks" : "secretlint";
|
|
125
|
+
if (!gitleaksResult)
|
|
126
|
+
issues.push(...(await scanFallback(cwd)));
|
|
92
127
|
// ── .env file audit ──
|
|
93
128
|
const envFiles = [".env", ".env.local", ".env.production", ".env.development"];
|
|
94
129
|
const gitignore = existsSync(join(cwd, ".gitignore")) ? readFileSync(join(cwd, ".gitignore"), "utf-8") : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecodeqa/cli",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.1",
|
|
4
4
|
"description": "Code health scanner for the AI coding era. 25 checks, zero config, full report.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@jscpd/core": "^4.2.4",
|
|
57
|
+
"@secretlint/core": "^13.0.2",
|
|
58
|
+
"@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
|
|
57
59
|
"dependency-cruiser": "^17.4.3",
|
|
58
60
|
"ink": "^5.2.1",
|
|
59
61
|
"react": "^18.3.1"
|