codeproof 1.0.3 → 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.
@@ -1,202 +1,202 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import readline from "readline";
4
- import { ensureGitRepo, getGitRoot } from "../utils/git.js";
5
- import { getDefaultExcludes } from "../utils/files.js";
6
- import { logInfo, logWarn } from "../utils/logger.js";
7
- import { ensureEnvFile, readEnvKeys, appendEnvEntries } from "../utils/envManager.js";
8
- import { backupFileOnce, extractSecretValueFromLine, replaceSecretInFile } from "../utils/fileRewriter.js";
9
- import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
10
- import { reportFeatureDisabled, warnExperimentalOnce } from "../core/safetyGuards.js";
11
- import { readLatestReport } from "../reporting/reportReader.js";
12
-
13
- const TEST_PATH_HINTS = [
14
- "test",
15
- "tests",
16
- "__tests__",
17
- "spec",
18
- "example",
19
- "examples",
20
- "sample",
21
- "samples",
22
- "mock",
23
- "mocks"
24
- ];
25
-
26
- function isTestLike(filePath) {
27
- const normalized = filePath.toLowerCase();
28
- return TEST_PATH_HINTS.some((hint) => normalized.includes(path.sep + hint));
29
- }
30
-
31
- function isIgnoredPath(filePath, excludes) {
32
- const segments = filePath.split(path.sep).map((segment) => segment.toLowerCase());
33
- for (const segment of segments) {
34
- if (excludes.has(segment)) {
35
- return true;
36
- }
37
- }
38
- return false;
39
- }
40
-
41
- function confirmProceed(message) {
42
- return new Promise((resolve) => {
43
- const rl = readline.createInterface({
44
- input: process.stdin,
45
- output: process.stdout
46
- });
47
-
48
- rl.question(message, (answer) => {
49
- rl.close();
50
- resolve(String(answer).trim().toLowerCase() === "y");
51
- });
52
- });
53
- }
54
-
55
- export async function runMoveSecret({ cwd }) {
56
- // Boundary: remediation reads reports only and must not depend on analysis state.
57
- ensureGitRepo(cwd);
58
- const gitRoot = getGitRoot(cwd);
59
- const configPath = path.join(gitRoot, "codeproof.config.json");
60
- let config = {};
61
- try {
62
- const raw = fs.readFileSync(configPath, "utf8");
63
- config = JSON.parse(raw);
64
- } catch {
65
- config = {};
66
- logWarn("Unable to read codeproof.config.json. Using safe defaults.");
67
- }
68
- const features = resolveFeatureFlags(config);
69
- const verbose = isVerbose(config);
70
-
71
- if (!features.secretRemediation) {
72
- reportFeatureDisabled("Secret remediation", verbose, logInfo);
73
- process.exit(0);
74
- }
75
-
76
- warnExperimentalOnce("Experimental feature enabled: move-secret.", logWarn);
77
- const latestReport = readLatestReport(gitRoot)?.report || null;
78
-
79
- if (!latestReport || !Array.isArray(latestReport.findings)) {
80
- logWarn("No reports found. Run 'codeproof run' first.");
81
- process.exit(0);
82
- }
83
-
84
- const excludes = getDefaultExcludes();
85
-
86
- const eligible = latestReport.findings.filter((finding) => {
87
- if (finding.ruleId?.startsWith("secret.") !== true) {
88
- return false;
89
- }
90
- if (finding.severity !== "block" || finding.confidence !== "high") {
91
- return false;
92
- }
93
- if (!finding.filePath || !finding.lineNumber) {
94
- return false;
95
- }
96
-
97
- if (!finding.codeSnippet) {
98
- return false;
99
- }
100
-
101
- const absolutePath = path.isAbsolute(finding.filePath)
102
- ? finding.filePath
103
- : path.join(gitRoot, finding.filePath);
104
-
105
- if (isTestLike(absolutePath)) {
106
- return false;
107
- }
108
-
109
- if (isIgnoredPath(absolutePath, excludes)) {
110
- return false;
111
- }
112
-
113
- return true;
114
- });
115
-
116
- if (eligible.length === 0) {
117
- logInfo("No eligible high-confidence secrets to move.");
118
- process.exit(0);
119
- }
120
-
121
- // Safety: secrets are never auto-fixed silently; users must confirm every change.
122
- logInfo("Eligible secrets preview:");
123
- for (const finding of eligible) {
124
- const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
125
- logInfo(`- ${relative}:${finding.lineNumber}`);
126
- }
127
- logInfo(`Secrets to move: ${eligible.length}`);
128
-
129
- const confirmed = await confirmProceed("Proceed with moving these secrets? (y/N): ");
130
- if (!confirmed) {
131
- logInfo("No changes made.");
132
- process.exit(0);
133
- }
134
-
135
- const envPath = ensureEnvFile(gitRoot);
136
- const existingKeys = readEnvKeys(envPath);
137
- const newEntries = [];
138
- const backedUp = new Set();
139
- let secretIndex = 1;
140
- let secretsMoved = 0;
141
- const modifiedFiles = new Set();
142
-
143
- for (const finding of eligible) {
144
- const absolutePath = path.isAbsolute(finding.filePath)
145
- ? finding.filePath
146
- : path.join(gitRoot, finding.filePath);
147
-
148
- let lineContent = "";
149
- try {
150
- const content = fs.readFileSync(absolutePath, "utf8");
151
- const lines = content.split(/\r?\n/);
152
- lineContent = lines[finding.lineNumber - 1] || "";
153
- } catch {
154
- logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (unable to read file).`);
155
- continue;
156
- }
157
-
158
- const expectedSecretValue = extractSecretValueFromLine(lineContent);
159
- if (!expectedSecretValue) {
160
- logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (unable to validate secret value).`);
161
- continue;
162
- }
163
-
164
- while (existingKeys.has(`CODEPROOF_SECRET_${secretIndex}`)) {
165
- secretIndex += 1;
166
- }
167
-
168
- const envKey = `CODEPROOF_SECRET_${secretIndex}`;
169
-
170
- // Safety: keep an original copy before any rewrite.
171
- backupFileOnce(gitRoot, absolutePath, backedUp);
172
-
173
- const result = replaceSecretInFile({
174
- filePath: absolutePath,
175
- lineNumber: finding.lineNumber,
176
- envKey,
177
- expectedSnippet: finding.codeSnippet,
178
- expectedSecretValue
179
- });
180
-
181
- if (!result.updated) {
182
- logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (${result.reason}).`);
183
- continue;
184
- }
185
-
186
- newEntries.push({ key: envKey, value: result.secretValue });
187
- existingKeys.add(envKey);
188
- secretsMoved += 1;
189
- secretIndex += 1;
190
- modifiedFiles.add(absolutePath);
191
-
192
- const relative = path.relative(gitRoot, absolutePath) || absolutePath;
193
- logInfo(`Updated ${relative}:${finding.lineNumber} → process.env.${envKey}`);
194
- }
195
-
196
- appendEnvEntries(envPath, newEntries);
197
-
198
- logInfo("Secret move summary:");
199
- logInfo(`Secrets moved: ${secretsMoved}`);
200
- logInfo(`Files modified: ${modifiedFiles.size}`);
201
- logInfo(`Backup location: ${path.join(gitRoot, ".codeproof-backup")}`);
202
- }
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import readline from "readline";
4
+ import { ensureGitRepo, getGitRoot } from "../utils/git.js";
5
+ import { getDefaultExcludes } from "../utils/files.js";
6
+ import { logInfo, logWarn } from "../utils/logger.js";
7
+ import { ensureEnvFile, readEnvKeys, appendEnvEntries } from "../utils/envManager.js";
8
+ import { backupFileOnce, extractSecretValueFromLine, replaceSecretInFile } from "../utils/fileRewriter.js";
9
+ import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
10
+ import { reportFeatureDisabled, warnExperimentalOnce } from "../core/safetyGuards.js";
11
+ import { readLatestReport } from "../reporting/reportReader.js";
12
+
13
+ const TEST_PATH_HINTS = [
14
+ "test",
15
+ "tests",
16
+ "__tests__",
17
+ "spec",
18
+ "example",
19
+ "examples",
20
+ "sample",
21
+ "samples",
22
+ "mock",
23
+ "mocks"
24
+ ];
25
+
26
+ function isTestLike(filePath) {
27
+ const normalized = filePath.toLowerCase();
28
+ return TEST_PATH_HINTS.some((hint) => normalized.includes(path.sep + hint));
29
+ }
30
+
31
+ function isIgnoredPath(filePath, excludes) {
32
+ const segments = filePath.split(path.sep).map((segment) => segment.toLowerCase());
33
+ for (const segment of segments) {
34
+ if (excludes.has(segment)) {
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+
41
+ function confirmProceed(message) {
42
+ return new Promise((resolve) => {
43
+ const rl = readline.createInterface({
44
+ input: process.stdin,
45
+ output: process.stdout
46
+ });
47
+
48
+ rl.question(message, (answer) => {
49
+ rl.close();
50
+ resolve(String(answer).trim().toLowerCase() === "y");
51
+ });
52
+ });
53
+ }
54
+
55
+ export async function runMoveSecret({ cwd }) {
56
+ // Boundary: remediation reads reports only and must not depend on analysis state.
57
+ ensureGitRepo(cwd);
58
+ const gitRoot = getGitRoot(cwd);
59
+ const configPath = path.join(gitRoot, "codeproof.config.json");
60
+ let config = {};
61
+ try {
62
+ const raw = fs.readFileSync(configPath, "utf8");
63
+ config = JSON.parse(raw);
64
+ } catch {
65
+ config = {};
66
+ logWarn("Unable to read codeproof.config.json. Using safe defaults.");
67
+ }
68
+ const features = resolveFeatureFlags(config);
69
+ const verbose = isVerbose(config);
70
+
71
+ if (!features.secretRemediation) {
72
+ reportFeatureDisabled("Secret remediation", verbose, logInfo);
73
+ process.exit(0);
74
+ }
75
+
76
+ warnExperimentalOnce("Experimental feature enabled: move-secret.", logWarn);
77
+ const latestReport = readLatestReport(gitRoot)?.report || null;
78
+
79
+ if (!latestReport || !Array.isArray(latestReport.findings)) {
80
+ logWarn("No reports found. Run 'codeproof run' first.");
81
+ process.exit(0);
82
+ }
83
+
84
+ const excludes = getDefaultExcludes();
85
+
86
+ const eligible = latestReport.findings.filter((finding) => {
87
+ if (finding.ruleId?.startsWith("secret.") !== true) {
88
+ return false;
89
+ }
90
+ if (finding.severity !== "block" || finding.confidence !== "high") {
91
+ return false;
92
+ }
93
+ if (!finding.filePath || !finding.lineNumber) {
94
+ return false;
95
+ }
96
+
97
+ if (!finding.codeSnippet) {
98
+ return false;
99
+ }
100
+
101
+ const absolutePath = path.isAbsolute(finding.filePath)
102
+ ? finding.filePath
103
+ : path.join(gitRoot, finding.filePath);
104
+
105
+ if (isTestLike(absolutePath)) {
106
+ return false;
107
+ }
108
+
109
+ if (isIgnoredPath(absolutePath, excludes)) {
110
+ return false;
111
+ }
112
+
113
+ return true;
114
+ });
115
+
116
+ if (eligible.length === 0) {
117
+ logInfo("No eligible high-confidence secrets to move.");
118
+ process.exit(0);
119
+ }
120
+
121
+ // Safety: secrets are never auto-fixed silently; users must confirm every change.
122
+ logInfo("Eligible secrets preview:");
123
+ for (const finding of eligible) {
124
+ const relative = path.relative(gitRoot, finding.filePath) || finding.filePath;
125
+ logInfo(`- ${relative}:${finding.lineNumber}`);
126
+ }
127
+ logInfo(`Secrets to move: ${eligible.length}`);
128
+
129
+ const confirmed = await confirmProceed("Proceed with moving these secrets? (y/N): ");
130
+ if (!confirmed) {
131
+ logInfo("No changes made.");
132
+ process.exit(0);
133
+ }
134
+
135
+ const envPath = ensureEnvFile(gitRoot);
136
+ const existingKeys = readEnvKeys(envPath);
137
+ const newEntries = [];
138
+ const backedUp = new Set();
139
+ let secretIndex = 1;
140
+ let secretsMoved = 0;
141
+ const modifiedFiles = new Set();
142
+
143
+ for (const finding of eligible) {
144
+ const absolutePath = path.isAbsolute(finding.filePath)
145
+ ? finding.filePath
146
+ : path.join(gitRoot, finding.filePath);
147
+
148
+ let lineContent = "";
149
+ try {
150
+ const content = fs.readFileSync(absolutePath, "utf8");
151
+ const lines = content.split(/\r?\n/);
152
+ lineContent = lines[finding.lineNumber - 1] || "";
153
+ } catch {
154
+ logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (unable to read file).`);
155
+ continue;
156
+ }
157
+
158
+ const expectedSecretValue = extractSecretValueFromLine(lineContent);
159
+ if (!expectedSecretValue) {
160
+ logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (unable to validate secret value).`);
161
+ continue;
162
+ }
163
+
164
+ while (existingKeys.has(`CODEPROOF_SECRET_${secretIndex}`)) {
165
+ secretIndex += 1;
166
+ }
167
+
168
+ const envKey = `CODEPROOF_SECRET_${secretIndex}`;
169
+
170
+ // Safety: keep an original copy before any rewrite.
171
+ backupFileOnce(gitRoot, absolutePath, backedUp);
172
+
173
+ const result = replaceSecretInFile({
174
+ filePath: absolutePath,
175
+ lineNumber: finding.lineNumber,
176
+ envKey,
177
+ expectedSnippet: finding.codeSnippet,
178
+ expectedSecretValue
179
+ });
180
+
181
+ if (!result.updated) {
182
+ logWarn(`Skipped ${finding.filePath}:${finding.lineNumber} (${result.reason}).`);
183
+ continue;
184
+ }
185
+
186
+ newEntries.push({ key: envKey, value: result.secretValue });
187
+ existingKeys.add(envKey);
188
+ secretsMoved += 1;
189
+ secretIndex += 1;
190
+ modifiedFiles.add(absolutePath);
191
+
192
+ const relative = path.relative(gitRoot, absolutePath) || absolutePath;
193
+ logInfo(`Updated ${relative}:${finding.lineNumber} → process.env.${envKey}`);
194
+ }
195
+
196
+ appendEnvEntries(envPath, newEntries);
197
+
198
+ logInfo("Secret move summary:");
199
+ logInfo(`Secrets moved: ${secretsMoved}`);
200
+ logInfo(`Files modified: ${modifiedFiles.size}`);
201
+ logInfo(`Backup location: ${path.join(gitRoot, ".codeproof-backup")}`);
202
+ }
@@ -1,65 +1,65 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { ensureGitRepo, getGitRoot } from "../utils/git.js";
4
- import { logError, logInfo, logWarn } from "../utils/logger.js";
5
- import { sendReportToServer } from "../utils/apiClient.js";
6
- import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
7
- import { reportFeatureDisabled, withFailOpenIntegration } from "../core/safetyGuards.js";
8
- import { readLatestReport } from "../reporting/reportReader.js";
9
-
10
- function readConfig(configPath) {
11
- if (!fs.existsSync(configPath)) {
12
- logError("Missing codeproof.config.json. Run codeproof init first.");
13
- process.exit(1);
14
- }
15
-
16
- try {
17
- const raw = fs.readFileSync(configPath, "utf8");
18
- return JSON.parse(raw);
19
- } catch {
20
- logError("Invalid codeproof.config.json. Please fix the file.");
21
- process.exit(1);
22
- }
23
- }
24
-
25
- export async function runReportDashboard({ cwd }) {
26
- // Boundary: CLI orchestration only. Avoid importing this module in lower layers.
27
- ensureGitRepo(cwd);
28
- const gitRoot = getGitRoot(cwd);
29
- const configPath = path.join(gitRoot, "codeproof.config.json");
30
- const config = readConfig(configPath);
31
- const features = resolveFeatureFlags(config);
32
- const verbose = isVerbose(config);
33
-
34
- if (features.reporting) {
35
- const latestEntry = readLatestReport(gitRoot);
36
- const latestReport = latestEntry?.report || null;
37
-
38
- if (!latestReport) {
39
- logWarn("No reports found. Run `codeproof run` first.");
40
- return;
41
- }
42
-
43
- const integration = config?.integration || {};
44
- const integrationEnabled = features.integration && Boolean(integration.enabled);
45
-
46
- if (integrationEnabled) {
47
- // Integrations are fail-open: never throw on network errors.
48
- withFailOpenIntegration(() => {
49
- sendReportToServer(latestReport, {
50
- enabled: true,
51
- endpointUrl: integration.endpointUrl
52
- });
53
- });
54
- logInfo("Report sent to server.");
55
- } else {
56
- reportFeatureDisabled("Integration", verbose, logInfo);
57
- }
58
-
59
- if (latestReport?.projectId) {
60
- logInfo(`View dashboard: https://dashboard.codeproof.dev/project/${latestReport.projectId}`);
61
- }
62
- } else {
63
- reportFeatureDisabled("Reporting", verbose, logInfo);
64
- }
65
- }
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ensureGitRepo, getGitRoot } from "../utils/git.js";
4
+ import { logError, logInfo, logWarn } from "../utils/logger.js";
5
+ import { sendReportToServer } from "../utils/apiClient.js";
6
+ import { resolveFeatureFlags, isVerbose } from "../core/featureFlags.js";
7
+ import { reportFeatureDisabled, withFailOpenIntegration } from "../core/safetyGuards.js";
8
+ import { readLatestReport } from "../reporting/reportReader.js";
9
+
10
+ function readConfig(configPath) {
11
+ if (!fs.existsSync(configPath)) {
12
+ logError("Missing codeproof.config.json. Run codeproof init first.");
13
+ process.exit(1);
14
+ }
15
+
16
+ try {
17
+ const raw = fs.readFileSync(configPath, "utf8");
18
+ return JSON.parse(raw);
19
+ } catch {
20
+ logError("Invalid codeproof.config.json. Please fix the file.");
21
+ process.exit(1);
22
+ }
23
+ }
24
+
25
+ export async function runReportDashboard({ cwd }) {
26
+ // Boundary: CLI orchestration only. Avoid importing this module in lower layers.
27
+ ensureGitRepo(cwd);
28
+ const gitRoot = getGitRoot(cwd);
29
+ const configPath = path.join(gitRoot, "codeproof.config.json");
30
+ const config = readConfig(configPath);
31
+ const features = resolveFeatureFlags(config);
32
+ const verbose = isVerbose(config);
33
+
34
+ if (features.reporting) {
35
+ const latestEntry = readLatestReport(gitRoot);
36
+ const latestReport = latestEntry?.report || null;
37
+
38
+ if (!latestReport) {
39
+ logWarn("No reports found. Run `codeproof run` first.");
40
+ return;
41
+ }
42
+
43
+ const integration = config?.integration || {};
44
+ const integrationEnabled = features.integration && Boolean(integration.enabled);
45
+
46
+ if (integrationEnabled) {
47
+ // Integrations are fail-open: never throw on network errors.
48
+ withFailOpenIntegration(() => {
49
+ sendReportToServer(latestReport, {
50
+ enabled: true,
51
+ endpointUrl: integration.endpointUrl
52
+ });
53
+ });
54
+ logInfo("Report sent to server.");
55
+ } else {
56
+ reportFeatureDisabled("Integration", verbose, logInfo);
57
+ }
58
+
59
+ if (latestReport?.projectId) {
60
+ logInfo(`View dashboard: https://dashboard.codeproof.dev/project/${latestReport.projectId}`);
61
+ }
62
+ } else {
63
+ reportFeatureDisabled("Reporting", verbose, logInfo);
64
+ }
65
+ }