codeproof 1.0.4 → 1.0.5
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/bin/codeproof.js +60 -60
- package/commands/apply.js +32 -32
- package/commands/ignore.js +32 -32
- package/commands/init.js +106 -106
- package/commands/moveSecret.js +202 -202
- package/commands/reportDashboard.js +65 -65
- package/commands/run.js +234 -234
- package/commands/whoami.js +19 -19
- package/core/boundaries.md +26 -26
- package/core/enforcement.js +51 -51
- package/core/featureFlags.js +25 -25
- package/core/identity.js +78 -78
- package/core/safetyGuards.js +58 -58
- package/engine/aiAnalyzer.js +143 -143
- package/engine/aiEscalation.js +6 -6
- package/engine/contextBuilder.js +65 -65
- package/engine/decisionMerger.js +30 -30
- package/engine/ruleEngine.js +52 -52
- package/hooks/preCommit.js +93 -93
- package/package.json +16 -16
- package/reporting/reportBuilder.js +112 -112
- package/reporting/reportReader.js +49 -49
- package/reporting/reportWriter.js +104 -91
- package/rules/dangerousUsageRule.js +11 -11
- package/rules/insecureConfigRule.js +11 -11
- package/rules/regexPatterns.js +53 -53
- package/rules/ruleUtils.js +58 -58
- package/rules/secretRule.js +21 -21
- package/ui/welcomeScreen.js +42 -42
- package/utils/apiClient.js +96 -96
- package/utils/envManager.js +48 -48
- package/utils/fileRewriter.js +145 -145
- package/utils/fileScanner.js +40 -40
- package/utils/files.js +50 -50
- package/utils/git.js +63 -63
- package/utils/gitIgnore.js +55 -55
- package/utils/logger.js +25 -25
- package/utils/projectType.js +20 -20
- package/.env +0 -1
package/commands/run.js
CHANGED
|
@@ -1,234 +1,234 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { ensureGitRepo, getGitRoot, getStagedFiles, getRepoIdentifier, getProjectName } from "../utils/git.js";
|
|
4
|
-
import { logError, logInfo, logSuccess, logWarn } from "../utils/logger.js";
|
|
5
|
-
import { buildScanTargets } from "../utils/fileScanner.js";
|
|
6
|
-
import { runRuleEngine } from "../engine/ruleEngine.js";
|
|
7
|
-
import { analyze } from "../engine/aiAnalyzer.js";
|
|
8
|
-
import { mergeDecisions } from "../engine/decisionMerger.js";
|
|
9
|
-
import { buildAiInputs, buildProjectContext } from "../engine/contextBuilder.js";
|
|
10
|
-
import { buildReport } from "../reporting/reportBuilder.js";
|
|
11
|
-
import { writeReport } from "../reporting/reportWriter.js";
|
|
12
|
-
import { sendReportToServer } from "../utils/apiClient.js";
|
|
13
|
-
import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
|
|
14
|
-
import { getClientId } from "../core/identity.js";
|
|
15
|
-
import { getEnforcementState } from "../core/enforcement.js";
|
|
16
|
-
import {
|
|
17
|
-
reportFeatureDisabled,
|
|
18
|
-
warnExperimentalOnce,
|
|
19
|
-
withFailOpenAiEscalation,
|
|
20
|
-
withFailOpenIntegration,
|
|
21
|
-
withFailOpenReporting
|
|
22
|
-
} from "../core/safetyGuards.js";
|
|
23
|
-
import { randomUUID } from "crypto";
|
|
24
|
-
|
|
25
|
-
function readConfig(configPath) {
|
|
26
|
-
if (!fs.existsSync(configPath)) {
|
|
27
|
-
logError("Missing codeproof.config.json. Run codeproof init first.");
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const raw = fs.readFileSync(configPath, "utf8");
|
|
33
|
-
return JSON.parse(raw);
|
|
34
|
-
} catch {
|
|
35
|
-
logError("Invalid codeproof.config.json. Please fix the file.");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function runCli({ args = [], cwd }) {
|
|
41
|
-
// Boundary: CLI orchestration only. Avoid importing this module in lower layers.
|
|
42
|
-
logInfo("CodeProof run started.");
|
|
43
|
-
|
|
44
|
-
ensureGitRepo(cwd);
|
|
45
|
-
const gitRoot = getGitRoot(cwd);
|
|
46
|
-
const configPath = path.join(gitRoot, "codeproof.config.json");
|
|
47
|
-
const config = readConfig(configPath);
|
|
48
|
-
const features = resolveFeatureFlags(config);
|
|
49
|
-
const verbose = isVerbose(config);
|
|
50
|
-
let enforcement = "enabled";
|
|
51
|
-
try {
|
|
52
|
-
enforcement = getEnforcementState(gitRoot);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
logError(error?.message || "Unable to read enforcement state.");
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
const isPreCommit = args.includes("--precommit") || Boolean(process.env.CODEPROOF_PRECOMMIT);
|
|
58
|
-
|
|
59
|
-
if (isPreCommit && enforcement === "disabled") {
|
|
60
|
-
logWarn("CodeProof enforcement is temporarily disabled.");
|
|
61
|
-
logInfo("Commit allowed.");
|
|
62
|
-
process.exit(0);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!config.scanMode) {
|
|
66
|
-
logError("Config missing scanMode. Expected 'staged' or 'full'.");
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const scanMode = String(config.scanMode).toLowerCase();
|
|
71
|
-
let targets = [];
|
|
72
|
-
|
|
73
|
-
if (scanMode === "staged") {
|
|
74
|
-
logInfo("Scan mode: staged");
|
|
75
|
-
targets = buildScanTargets({
|
|
76
|
-
gitRoot,
|
|
77
|
-
scanMode,
|
|
78
|
-
stagedFiles: getStagedFiles(gitRoot)
|
|
79
|
-
});
|
|
80
|
-
} else if (scanMode === "full") {
|
|
81
|
-
logInfo("Scan mode: full");
|
|
82
|
-
targets = buildScanTargets({
|
|
83
|
-
gitRoot,
|
|
84
|
-
scanMode,
|
|
85
|
-
stagedFiles: []
|
|
86
|
-
});
|
|
87
|
-
} else {
|
|
88
|
-
logError("Invalid scanMode. Expected 'staged' or 'full'.");
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (targets.length === 0) {
|
|
93
|
-
logWarn("No relevant files found. Exiting.");
|
|
94
|
-
// Exit code 0 allows the Git commit to continue.
|
|
95
|
-
process.exit(0);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const { findings, escalations } = runRuleEngine({ files: targets });
|
|
99
|
-
const projectContext = buildProjectContext({ gitRoot, config });
|
|
100
|
-
let aiInputs = [];
|
|
101
|
-
if (features.aiEscalation) {
|
|
102
|
-
warnExperimentalOnce("Experimental feature enabled: AI escalation.", logWarn);
|
|
103
|
-
aiInputs = buildAiInputs(escalations, projectContext);
|
|
104
|
-
if (aiInputs.length > 0) {
|
|
105
|
-
// Regex-first keeps scans fast; only ambiguous findings go to AI.
|
|
106
|
-
logWarn(`Escalating ${aiInputs.length} findings to AI for contextual analysis`);
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
reportFeatureDisabled("AI escalation", verbose, logInfo);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const aiDecisions = aiInputs.length > 0
|
|
113
|
-
? await withFailOpenAiEscalation(features.aiEscalation, () => analyze(aiInputs, projectContext))
|
|
114
|
-
: [];
|
|
115
|
-
const { blockFindings, warnFindings, aiReviewed, exitCode } = mergeDecisions({
|
|
116
|
-
baselineFindings: [...findings, ...escalations],
|
|
117
|
-
aiDecisions
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (features.reporting) {
|
|
121
|
-
await withFailOpenReporting(async () => {
|
|
122
|
-
const timestamp = new Date().toISOString();
|
|
123
|
-
const reportId = randomUUID();
|
|
124
|
-
const projectId = config.projectId || "";
|
|
125
|
-
const clientId = getClientId();
|
|
126
|
-
const projectName = getProjectName(gitRoot);
|
|
127
|
-
const repoIdentifier = getRepoIdentifier(gitRoot);
|
|
128
|
-
const report = buildReport({
|
|
129
|
-
projectRoot: gitRoot,
|
|
130
|
-
projectId,
|
|
131
|
-
projectName,
|
|
132
|
-
repoIdentifier,
|
|
133
|
-
clientId,
|
|
134
|
-
reportId,
|
|
135
|
-
scanMode,
|
|
136
|
-
filesScannedCount: targets.length,
|
|
137
|
-
baselineFindings: [...findings, ...escalations],
|
|
138
|
-
aiReviewed,
|
|
139
|
-
timestamp
|
|
140
|
-
});
|
|
141
|
-
// Report is saved to file and sent to server regardless of findings
|
|
142
|
-
logInfo("Saving report to file...");
|
|
143
|
-
writeReport({ projectRoot: gitRoot, report });
|
|
144
|
-
logSuccess("Report saved locally.");
|
|
145
|
-
|
|
146
|
-
const integration = config?.integration || {};
|
|
147
|
-
const integrationEnabled = features.integration && Boolean(integration.enabled);
|
|
148
|
-
|
|
149
|
-
// Always send to server in pre-commit or manual mode
|
|
150
|
-
if (integrationEnabled) {
|
|
151
|
-
logInfo("Syncing report to server...");
|
|
152
|
-
await withFailOpenIntegration(async () => {
|
|
153
|
-
// Network calls are fail-open; never affect exit codes.
|
|
154
|
-
return await sendReportToServer(report, {
|
|
155
|
-
enabled: true,
|
|
156
|
-
endpointUrl: integration.endpointUrl
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
logSuccess("Report synced to server.");
|
|
160
|
-
} else {
|
|
161
|
-
reportFeatureDisabled("Integration", verbose, logInfo);
|
|
162
|
-
}
|
|
163
|
-
}, () => {
|
|
164
|
-
logWarn("Failed to process report. Continuing without blocking.");
|
|
165
|
-
});
|
|
166
|
-
} else {
|
|
167
|
-
reportFeatureDisabled("Reporting", verbose, logInfo);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (blockFindings.length > 0) {
|
|
171
|
-
logError(`\n❌ CRITICAL ISSUES FOUND (${blockFindings.length}):\n`);
|
|
172
|
-
for (const finding of blockFindings) {
|
|
173
|
-
const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
174
|
-
logError(` • ${finding.ruleId.toUpperCase()}`);
|
|
175
|
-
logError(` File: ${relative}:${finding.line}`);
|
|
176
|
-
logError(` Issue: ${finding.message}`);
|
|
177
|
-
// console.log(` Code: ${finding.snippet}`);
|
|
178
|
-
logError("");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (warnFindings.length > 0) {
|
|
183
|
-
// Filter to show only HIGH risk warnings
|
|
184
|
-
const highRiskWarnings = warnFindings.filter(f => f.confidence === "high");
|
|
185
|
-
if (highRiskWarnings.length > 0) {
|
|
186
|
-
logWarn(`\n⚠️ HIGH RISK WARNINGS (${highRiskWarnings.length}):\n`);
|
|
187
|
-
for (const finding of highRiskWarnings) {
|
|
188
|
-
const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
189
|
-
logWarn(` • ${finding.ruleId.toUpperCase()}`);
|
|
190
|
-
logWarn(` File: ${relative}:${finding.line}`);
|
|
191
|
-
logWarn(` Issue: ${finding.message}`);
|
|
192
|
-
// console.log(` Code: ${finding.snippet}`);
|
|
193
|
-
logWarn("");
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
// comment out low risk warnings
|
|
197
|
-
// const lowRiskWarnings = warnFindings.filter(f => f.confidence !== "high");
|
|
198
|
-
// if (lowRiskWarnings.length > 0) {
|
|
199
|
-
// logWarn(`Baseline warnings (${lowRiskWarnings.length}):`);
|
|
200
|
-
// for (const finding of lowRiskWarnings) {
|
|
201
|
-
// const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
202
|
-
// logWarn(
|
|
203
|
-
// `${finding.ruleId} [${finding.severity}/${finding.confidence}] ${relative}:${finding.line} ${finding.message}`
|
|
204
|
-
// );
|
|
205
|
-
// logWarn(` ${finding.snippet}`);
|
|
206
|
-
// }
|
|
207
|
-
// }
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (aiReviewed.length > 0) {
|
|
211
|
-
logWarn(`\n🤖 AI-REVIEWED FINDINGS (${aiReviewed.length}):\n`);
|
|
212
|
-
for (const entry of aiReviewed) {
|
|
213
|
-
const { finding, decision } = entry;
|
|
214
|
-
const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
215
|
-
const verdict = decision.verdict === "block" ? "BLOCKED" : "WARNING";
|
|
216
|
-
logWarn(` • ${finding.ruleId.toUpperCase()} [${verdict}]`);
|
|
217
|
-
logWarn(` File: ${relative}:${finding.line}`);
|
|
218
|
-
logWarn(` Analysis: ${decision.explanation}`);
|
|
219
|
-
if (decision.suggestedFix) {
|
|
220
|
-
logWarn(` Fix: ${decision.suggestedFix}`);
|
|
221
|
-
}
|
|
222
|
-
logWarn("");
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (exitCode === 1) {
|
|
227
|
-
// Exit code 1 blocks the Git commit via the pre-commit hook.
|
|
228
|
-
process.exit(1);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
logSuccess("CodeProof run complete.");
|
|
232
|
-
// Exit code 0 allows the Git commit to continue.
|
|
233
|
-
process.exit(0);
|
|
234
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { ensureGitRepo, getGitRoot, getStagedFiles, getRepoIdentifier, getProjectName } from "../utils/git.js";
|
|
4
|
+
import { logError, logInfo, logSuccess, logWarn } from "../utils/logger.js";
|
|
5
|
+
import { buildScanTargets } from "../utils/fileScanner.js";
|
|
6
|
+
import { runRuleEngine } from "../engine/ruleEngine.js";
|
|
7
|
+
import { analyze } from "../engine/aiAnalyzer.js";
|
|
8
|
+
import { mergeDecisions } from "../engine/decisionMerger.js";
|
|
9
|
+
import { buildAiInputs, buildProjectContext } from "../engine/contextBuilder.js";
|
|
10
|
+
import { buildReport } from "../reporting/reportBuilder.js";
|
|
11
|
+
import { writeReport } from "../reporting/reportWriter.js";
|
|
12
|
+
import { sendReportToServer } from "../utils/apiClient.js";
|
|
13
|
+
import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
|
|
14
|
+
import { getClientId } from "../core/identity.js";
|
|
15
|
+
import { getEnforcementState } from "../core/enforcement.js";
|
|
16
|
+
import {
|
|
17
|
+
reportFeatureDisabled,
|
|
18
|
+
warnExperimentalOnce,
|
|
19
|
+
withFailOpenAiEscalation,
|
|
20
|
+
withFailOpenIntegration,
|
|
21
|
+
withFailOpenReporting
|
|
22
|
+
} from "../core/safetyGuards.js";
|
|
23
|
+
import { randomUUID } from "crypto";
|
|
24
|
+
|
|
25
|
+
function readConfig(configPath) {
|
|
26
|
+
if (!fs.existsSync(configPath)) {
|
|
27
|
+
logError("Missing codeproof.config.json. Run codeproof init first.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
} catch {
|
|
35
|
+
logError("Invalid codeproof.config.json. Please fix the file.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runCli({ args = [], cwd }) {
|
|
41
|
+
// Boundary: CLI orchestration only. Avoid importing this module in lower layers.
|
|
42
|
+
logInfo("CodeProof run started.");
|
|
43
|
+
|
|
44
|
+
ensureGitRepo(cwd);
|
|
45
|
+
const gitRoot = getGitRoot(cwd);
|
|
46
|
+
const configPath = path.join(gitRoot, "codeproof.config.json");
|
|
47
|
+
const config = readConfig(configPath);
|
|
48
|
+
const features = resolveFeatureFlags(config);
|
|
49
|
+
const verbose = isVerbose(config);
|
|
50
|
+
let enforcement = "enabled";
|
|
51
|
+
try {
|
|
52
|
+
enforcement = getEnforcementState(gitRoot);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logError(error?.message || "Unable to read enforcement state.");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const isPreCommit = args.includes("--precommit") || Boolean(process.env.CODEPROOF_PRECOMMIT);
|
|
58
|
+
|
|
59
|
+
if (isPreCommit && enforcement === "disabled") {
|
|
60
|
+
logWarn("CodeProof enforcement is temporarily disabled.");
|
|
61
|
+
logInfo("Commit allowed.");
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!config.scanMode) {
|
|
66
|
+
logError("Config missing scanMode. Expected 'staged' or 'full'.");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const scanMode = String(config.scanMode).toLowerCase();
|
|
71
|
+
let targets = [];
|
|
72
|
+
|
|
73
|
+
if (scanMode === "staged") {
|
|
74
|
+
logInfo("Scan mode: staged");
|
|
75
|
+
targets = buildScanTargets({
|
|
76
|
+
gitRoot,
|
|
77
|
+
scanMode,
|
|
78
|
+
stagedFiles: getStagedFiles(gitRoot)
|
|
79
|
+
});
|
|
80
|
+
} else if (scanMode === "full") {
|
|
81
|
+
logInfo("Scan mode: full");
|
|
82
|
+
targets = buildScanTargets({
|
|
83
|
+
gitRoot,
|
|
84
|
+
scanMode,
|
|
85
|
+
stagedFiles: []
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
logError("Invalid scanMode. Expected 'staged' or 'full'.");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (targets.length === 0) {
|
|
93
|
+
logWarn("No relevant files found. Exiting.");
|
|
94
|
+
// Exit code 0 allows the Git commit to continue.
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { findings, escalations } = runRuleEngine({ files: targets });
|
|
99
|
+
const projectContext = buildProjectContext({ gitRoot, config });
|
|
100
|
+
let aiInputs = [];
|
|
101
|
+
if (features.aiEscalation) {
|
|
102
|
+
warnExperimentalOnce("Experimental feature enabled: AI escalation.", logWarn);
|
|
103
|
+
aiInputs = buildAiInputs(escalations, projectContext);
|
|
104
|
+
if (aiInputs.length > 0) {
|
|
105
|
+
// Regex-first keeps scans fast; only ambiguous findings go to AI.
|
|
106
|
+
logWarn(`Escalating ${aiInputs.length} findings to AI for contextual analysis`);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
reportFeatureDisabled("AI escalation", verbose, logInfo);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const aiDecisions = aiInputs.length > 0
|
|
113
|
+
? await withFailOpenAiEscalation(features.aiEscalation, () => analyze(aiInputs, projectContext))
|
|
114
|
+
: [];
|
|
115
|
+
const { blockFindings, warnFindings, aiReviewed, exitCode } = mergeDecisions({
|
|
116
|
+
baselineFindings: [...findings, ...escalations],
|
|
117
|
+
aiDecisions
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (features.reporting) {
|
|
121
|
+
await withFailOpenReporting(async () => {
|
|
122
|
+
const timestamp = new Date().toISOString();
|
|
123
|
+
const reportId = randomUUID();
|
|
124
|
+
const projectId = config.projectId || "";
|
|
125
|
+
const clientId = getClientId();
|
|
126
|
+
const projectName = getProjectName(gitRoot);
|
|
127
|
+
const repoIdentifier = getRepoIdentifier(gitRoot);
|
|
128
|
+
const report = buildReport({
|
|
129
|
+
projectRoot: gitRoot,
|
|
130
|
+
projectId,
|
|
131
|
+
projectName,
|
|
132
|
+
repoIdentifier,
|
|
133
|
+
clientId,
|
|
134
|
+
reportId,
|
|
135
|
+
scanMode,
|
|
136
|
+
filesScannedCount: targets.length,
|
|
137
|
+
baselineFindings: [...findings, ...escalations],
|
|
138
|
+
aiReviewed,
|
|
139
|
+
timestamp
|
|
140
|
+
});
|
|
141
|
+
// Report is saved to file and sent to server regardless of findings
|
|
142
|
+
logInfo("Saving report to file...");
|
|
143
|
+
writeReport({ projectRoot: gitRoot, report });
|
|
144
|
+
logSuccess("Report saved locally.");
|
|
145
|
+
|
|
146
|
+
const integration = config?.integration || {};
|
|
147
|
+
const integrationEnabled = features.integration && Boolean(integration.enabled);
|
|
148
|
+
|
|
149
|
+
// Always send to server in pre-commit or manual mode
|
|
150
|
+
if (integrationEnabled) {
|
|
151
|
+
logInfo("Syncing report to server...");
|
|
152
|
+
await withFailOpenIntegration(async () => {
|
|
153
|
+
// Network calls are fail-open; never affect exit codes.
|
|
154
|
+
return await sendReportToServer(report, {
|
|
155
|
+
enabled: true,
|
|
156
|
+
endpointUrl: integration.endpointUrl
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
logSuccess("Report synced to server.");
|
|
160
|
+
} else {
|
|
161
|
+
reportFeatureDisabled("Integration", verbose, logInfo);
|
|
162
|
+
}
|
|
163
|
+
}, () => {
|
|
164
|
+
logWarn("Failed to process report. Continuing without blocking.");
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
reportFeatureDisabled("Reporting", verbose, logInfo);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (blockFindings.length > 0) {
|
|
171
|
+
logError(`\n❌ CRITICAL ISSUES FOUND (${blockFindings.length}):\n`);
|
|
172
|
+
for (const finding of blockFindings) {
|
|
173
|
+
const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
174
|
+
logError(` • ${finding.ruleId.toUpperCase()}`);
|
|
175
|
+
logError(` File: ${relative}:${finding.line}`);
|
|
176
|
+
logError(` Issue: ${finding.message}`);
|
|
177
|
+
// console.log(` Code: ${finding.snippet}`);
|
|
178
|
+
logError("");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (warnFindings.length > 0) {
|
|
183
|
+
// Filter to show only HIGH risk warnings
|
|
184
|
+
const highRiskWarnings = warnFindings.filter(f => f.confidence === "high");
|
|
185
|
+
if (highRiskWarnings.length > 0) {
|
|
186
|
+
logWarn(`\n⚠️ HIGH RISK WARNINGS (${highRiskWarnings.length}):\n`);
|
|
187
|
+
for (const finding of highRiskWarnings) {
|
|
188
|
+
const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
189
|
+
logWarn(` • ${finding.ruleId.toUpperCase()}`);
|
|
190
|
+
logWarn(` File: ${relative}:${finding.line}`);
|
|
191
|
+
logWarn(` Issue: ${finding.message}`);
|
|
192
|
+
// console.log(` Code: ${finding.snippet}`);
|
|
193
|
+
logWarn("");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// comment out low risk warnings
|
|
197
|
+
// const lowRiskWarnings = warnFindings.filter(f => f.confidence !== "high");
|
|
198
|
+
// if (lowRiskWarnings.length > 0) {
|
|
199
|
+
// logWarn(`Baseline warnings (${lowRiskWarnings.length}):`);
|
|
200
|
+
// for (const finding of lowRiskWarnings) {
|
|
201
|
+
// const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
202
|
+
// logWarn(
|
|
203
|
+
// `${finding.ruleId} [${finding.severity}/${finding.confidence}] ${relative}:${finding.line} ${finding.message}`
|
|
204
|
+
// );
|
|
205
|
+
// logWarn(` ${finding.snippet}`);
|
|
206
|
+
// }
|
|
207
|
+
// }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (aiReviewed.length > 0) {
|
|
211
|
+
logWarn(`\n🤖 AI-REVIEWED FINDINGS (${aiReviewed.length}):\n`);
|
|
212
|
+
for (const entry of aiReviewed) {
|
|
213
|
+
const { finding, decision } = entry;
|
|
214
|
+
const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
|
|
215
|
+
const verdict = decision.verdict === "block" ? "BLOCKED" : "WARNING";
|
|
216
|
+
logWarn(` • ${finding.ruleId.toUpperCase()} [${verdict}]`);
|
|
217
|
+
logWarn(` File: ${relative}:${finding.line}`);
|
|
218
|
+
logWarn(` Analysis: ${decision.explanation}`);
|
|
219
|
+
if (decision.suggestedFix) {
|
|
220
|
+
logWarn(` Fix: ${decision.suggestedFix}`);
|
|
221
|
+
}
|
|
222
|
+
logWarn("");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (exitCode === 1) {
|
|
227
|
+
// Exit code 1 blocks the Git commit via the pre-commit hook.
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
logSuccess("CodeProof run complete.");
|
|
232
|
+
// Exit code 0 allows the Git commit to continue.
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
package/commands/whoami.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { readClientId, getGlobalConfigPath } from "../core/identity.js";
|
|
2
|
-
import { logError, logInfo } from "../utils/logger.js";
|
|
3
|
-
|
|
4
|
-
export async function runWhoAmI() {
|
|
5
|
-
const clientId = readClientId();
|
|
6
|
-
|
|
7
|
-
if (!clientId) {
|
|
8
|
-
logError("CodeProof not initialized. Run codeproof init first.");
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
logInfo("CodeProof Client ID:");
|
|
13
|
-
logInfo(clientId);
|
|
14
|
-
logInfo("");
|
|
15
|
-
logInfo("Use this ID to log in at:");
|
|
16
|
-
logInfo("https://your-dashboard-url.com/login");
|
|
17
|
-
logInfo("");
|
|
18
|
-
logInfo(`Config: ${getGlobalConfigPath()}`);
|
|
19
|
-
}
|
|
1
|
+
import { readClientId, getGlobalConfigPath } from "../core/identity.js";
|
|
2
|
+
import { logError, logInfo } from "../utils/logger.js";
|
|
3
|
+
|
|
4
|
+
export async function runWhoAmI() {
|
|
5
|
+
const clientId = readClientId();
|
|
6
|
+
|
|
7
|
+
if (!clientId) {
|
|
8
|
+
logError("CodeProof not initialized. Run codeproof init first.");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
logInfo("CodeProof Client ID:");
|
|
13
|
+
logInfo(clientId);
|
|
14
|
+
logInfo("");
|
|
15
|
+
logInfo("Use this ID to log in at:");
|
|
16
|
+
logInfo("https://your-dashboard-url.com/login");
|
|
17
|
+
logInfo("");
|
|
18
|
+
logInfo(`Config: ${getGlobalConfigPath()}`);
|
|
19
|
+
}
|
package/core/boundaries.md
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
# CodeProof Internal Boundaries
|
|
2
|
-
|
|
3
|
-
This is a short internal guide to prevent accidental coupling between subsystems.
|
|
4
|
-
|
|
5
|
-
## Allowed Dependencies
|
|
6
|
-
- Rule engine → May depend on rules and utilities only.
|
|
7
|
-
- Reporting → May depend on report input data and file I/O only.
|
|
8
|
-
- Integration → May depend on HTTP/HTTPS only.
|
|
9
|
-
- Secret remediation → May depend on reports, env helpers, and file I/O only.
|
|
10
|
-
- CLI commands → Orchestrate flows and call feature guards.
|
|
11
|
-
|
|
12
|
-
## Forbidden Dependencies
|
|
13
|
-
- Rule engine must never import reporting, integration, or remediation.
|
|
14
|
-
- Reporting must never import rule logic or AI logic.
|
|
15
|
-
- Integration must never import CLI or rule logic.
|
|
16
|
-
- Secret remediation must never import analysis state (only reports).
|
|
17
|
-
|
|
18
|
-
## Fail-Open Guarantees
|
|
19
|
-
- Reporting failures never affect exit codes.
|
|
20
|
-
- Integration failures never affect commits.
|
|
21
|
-
- AI failures always downgrade to warn and never block.
|
|
22
|
-
|
|
23
|
-
## Safe Extension Points
|
|
24
|
-
- Add new features to core/featureFlags.js with default-off behavior.
|
|
25
|
-
- Use core/safetyGuards.js for all fail-open or experimental flows.
|
|
26
|
-
- Keep experimental features opt-in and guarded by feature flags.
|
|
1
|
+
# CodeProof Internal Boundaries
|
|
2
|
+
|
|
3
|
+
This is a short internal guide to prevent accidental coupling between subsystems.
|
|
4
|
+
|
|
5
|
+
## Allowed Dependencies
|
|
6
|
+
- Rule engine → May depend on rules and utilities only.
|
|
7
|
+
- Reporting → May depend on report input data and file I/O only.
|
|
8
|
+
- Integration → May depend on HTTP/HTTPS only.
|
|
9
|
+
- Secret remediation → May depend on reports, env helpers, and file I/O only.
|
|
10
|
+
- CLI commands → Orchestrate flows and call feature guards.
|
|
11
|
+
|
|
12
|
+
## Forbidden Dependencies
|
|
13
|
+
- Rule engine must never import reporting, integration, or remediation.
|
|
14
|
+
- Reporting must never import rule logic or AI logic.
|
|
15
|
+
- Integration must never import CLI or rule logic.
|
|
16
|
+
- Secret remediation must never import analysis state (only reports).
|
|
17
|
+
|
|
18
|
+
## Fail-Open Guarantees
|
|
19
|
+
- Reporting failures never affect exit codes.
|
|
20
|
+
- Integration failures never affect commits.
|
|
21
|
+
- AI failures always downgrade to warn and never block.
|
|
22
|
+
|
|
23
|
+
## Safe Extension Points
|
|
24
|
+
- Add new features to core/featureFlags.js with default-off behavior.
|
|
25
|
+
- Use core/safetyGuards.js for all fail-open or experimental flows.
|
|
26
|
+
- Keep experimental features opt-in and guarded by feature flags.
|