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/core/enforcement.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
const ENFORCEMENT_ENABLED = "enabled";
|
|
5
|
-
const ENFORCEMENT_DISABLED = "disabled";
|
|
6
|
-
|
|
7
|
-
function getConfigPath(gitRoot) {
|
|
8
|
-
return path.join(gitRoot, "codeproof.config.json");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function readConfig(gitRoot) {
|
|
12
|
-
const configPath = getConfigPath(gitRoot);
|
|
13
|
-
if (!fs.existsSync(configPath)) {
|
|
14
|
-
const error = new Error("Missing codeproof.config.json. Run codeproof init first.");
|
|
15
|
-
error.code = "CODEPROOF_CONFIG_MISSING";
|
|
16
|
-
throw error;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const raw = fs.readFileSync(configPath, "utf8");
|
|
21
|
-
return JSON.parse(raw);
|
|
22
|
-
} catch {
|
|
23
|
-
const error = new Error("Invalid codeproof.config.json. Please fix the file.");
|
|
24
|
-
error.code = "CODEPROOF_CONFIG_INVALID";
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function writeConfig(gitRoot, config) {
|
|
30
|
-
const configPath = getConfigPath(gitRoot);
|
|
31
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getEnforcementState(gitRoot) {
|
|
35
|
-
const config = readConfig(gitRoot);
|
|
36
|
-
const enforcement = String(config.enforcement || ENFORCEMENT_ENABLED).toLowerCase();
|
|
37
|
-
return enforcement === ENFORCEMENT_DISABLED ? ENFORCEMENT_DISABLED : ENFORCEMENT_ENABLED;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function setEnforcementState(gitRoot, nextState) {
|
|
41
|
-
const config = readConfig(gitRoot);
|
|
42
|
-
const normalized = String(nextState || "").toLowerCase();
|
|
43
|
-
const enforcement = normalized === ENFORCEMENT_DISABLED
|
|
44
|
-
? ENFORCEMENT_DISABLED
|
|
45
|
-
: ENFORCEMENT_ENABLED;
|
|
46
|
-
|
|
47
|
-
// Security: explicit state keeps this a reversible bypass, not a silent disable.
|
|
48
|
-
const updated = { ...config, enforcement };
|
|
49
|
-
writeConfig(gitRoot, updated);
|
|
50
|
-
return enforcement;
|
|
51
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const ENFORCEMENT_ENABLED = "enabled";
|
|
5
|
+
const ENFORCEMENT_DISABLED = "disabled";
|
|
6
|
+
|
|
7
|
+
function getConfigPath(gitRoot) {
|
|
8
|
+
return path.join(gitRoot, "codeproof.config.json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readConfig(gitRoot) {
|
|
12
|
+
const configPath = getConfigPath(gitRoot);
|
|
13
|
+
if (!fs.existsSync(configPath)) {
|
|
14
|
+
const error = new Error("Missing codeproof.config.json. Run codeproof init first.");
|
|
15
|
+
error.code = "CODEPROOF_CONFIG_MISSING";
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
} catch {
|
|
23
|
+
const error = new Error("Invalid codeproof.config.json. Please fix the file.");
|
|
24
|
+
error.code = "CODEPROOF_CONFIG_INVALID";
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeConfig(gitRoot, config) {
|
|
30
|
+
const configPath = getConfigPath(gitRoot);
|
|
31
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getEnforcementState(gitRoot) {
|
|
35
|
+
const config = readConfig(gitRoot);
|
|
36
|
+
const enforcement = String(config.enforcement || ENFORCEMENT_ENABLED).toLowerCase();
|
|
37
|
+
return enforcement === ENFORCEMENT_DISABLED ? ENFORCEMENT_DISABLED : ENFORCEMENT_ENABLED;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function setEnforcementState(gitRoot, nextState) {
|
|
41
|
+
const config = readConfig(gitRoot);
|
|
42
|
+
const normalized = String(nextState || "").toLowerCase();
|
|
43
|
+
const enforcement = normalized === ENFORCEMENT_DISABLED
|
|
44
|
+
? ENFORCEMENT_DISABLED
|
|
45
|
+
: ENFORCEMENT_ENABLED;
|
|
46
|
+
|
|
47
|
+
// Security: explicit state keeps this a reversible bypass, not a silent disable.
|
|
48
|
+
const updated = { ...config, enforcement };
|
|
49
|
+
writeConfig(gitRoot, updated);
|
|
50
|
+
return enforcement;
|
|
51
|
+
}
|
package/core/featureFlags.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
const DEFAULT_FEATURES = {
|
|
2
|
-
reporting: true,
|
|
3
|
-
integration: false,
|
|
4
|
-
aiEscalation: false,
|
|
5
|
-
secretRemediation: false
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
// Boundary: feature flags are configuration only and must not import runtime systems.
|
|
9
|
-
// Future features should be added here with safe defaults and explicit gating.
|
|
10
|
-
export function resolveFeatureFlags(config) {
|
|
11
|
-
const features = config?.features || {};
|
|
12
|
-
return {
|
|
13
|
-
reporting: typeof features.reporting === "boolean" ? features.reporting : DEFAULT_FEATURES.reporting,
|
|
14
|
-
integration: typeof features.integration === "boolean" ? features.integration : DEFAULT_FEATURES.integration,
|
|
15
|
-
aiEscalation: typeof features.aiEscalation === "boolean" ? features.aiEscalation : DEFAULT_FEATURES.aiEscalation,
|
|
16
|
-
secretRemediation:
|
|
17
|
-
typeof features.secretRemediation === "boolean"
|
|
18
|
-
? features.secretRemediation
|
|
19
|
-
: DEFAULT_FEATURES.secretRemediation
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function isVerbose(config) {
|
|
24
|
-
return Boolean(config?.verbose) || process.env.CODEPROOF_VERBOSE === "1";
|
|
25
|
-
}
|
|
1
|
+
const DEFAULT_FEATURES = {
|
|
2
|
+
reporting: true,
|
|
3
|
+
integration: false,
|
|
4
|
+
aiEscalation: false,
|
|
5
|
+
secretRemediation: false
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Boundary: feature flags are configuration only and must not import runtime systems.
|
|
9
|
+
// Future features should be added here with safe defaults and explicit gating.
|
|
10
|
+
export function resolveFeatureFlags(config) {
|
|
11
|
+
const features = config?.features || {};
|
|
12
|
+
return {
|
|
13
|
+
reporting: typeof features.reporting === "boolean" ? features.reporting : DEFAULT_FEATURES.reporting,
|
|
14
|
+
integration: typeof features.integration === "boolean" ? features.integration : DEFAULT_FEATURES.integration,
|
|
15
|
+
aiEscalation: typeof features.aiEscalation === "boolean" ? features.aiEscalation : DEFAULT_FEATURES.aiEscalation,
|
|
16
|
+
secretRemediation:
|
|
17
|
+
typeof features.secretRemediation === "boolean"
|
|
18
|
+
? features.secretRemediation
|
|
19
|
+
: DEFAULT_FEATURES.secretRemediation
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isVerbose(config) {
|
|
24
|
+
return Boolean(config?.verbose) || process.env.CODEPROOF_VERBOSE === "1";
|
|
25
|
+
}
|
package/core/identity.js
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { randomUUID } from "crypto";
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR_NAME = ".codeproof";
|
|
7
|
-
const CONFIG_FILE_NAME = "config.json";
|
|
8
|
-
|
|
9
|
-
function getConfigDir() {
|
|
10
|
-
return path.join(os.homedir(), CONFIG_DIR_NAME);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function getConfigPath() {
|
|
14
|
-
return path.join(getConfigDir(), CONFIG_FILE_NAME);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function ensureConfigDir() {
|
|
18
|
-
const dir = getConfigDir();
|
|
19
|
-
if (!fs.existsSync(dir)) {
|
|
20
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
fs.chmodSync(dir, 0o700);
|
|
24
|
-
} catch {
|
|
25
|
-
// Best-effort on platforms that ignore chmod.
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function writeConfig(config) {
|
|
30
|
-
const filePath = getConfigPath();
|
|
31
|
-
const payload = JSON.stringify(config, null, 2) + "\n";
|
|
32
|
-
fs.writeFileSync(filePath, payload, { encoding: "utf8", mode: 0o600 });
|
|
33
|
-
try {
|
|
34
|
-
fs.chmodSync(filePath, 0o600);
|
|
35
|
-
} catch {
|
|
36
|
-
// Best-effort on platforms that ignore chmod.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function readConfig() {
|
|
41
|
-
const filePath = getConfigPath();
|
|
42
|
-
if (!fs.existsSync(filePath)) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
48
|
-
return JSON.parse(raw);
|
|
49
|
-
} catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function ensureClientId() {
|
|
55
|
-
ensureConfigDir();
|
|
56
|
-
|
|
57
|
-
const existing = readConfig();
|
|
58
|
-
if (existing?.clientId) {
|
|
59
|
-
return existing.clientId;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const clientId = randomUUID();
|
|
63
|
-
writeConfig({ clientId });
|
|
64
|
-
return clientId;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getClientId() {
|
|
68
|
-
return ensureClientId();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function readClientId() {
|
|
72
|
-
const existing = readConfig();
|
|
73
|
-
return existing?.clientId ?? null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function getGlobalConfigPath() {
|
|
77
|
-
return getConfigPath();
|
|
78
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR_NAME = ".codeproof";
|
|
7
|
+
const CONFIG_FILE_NAME = "config.json";
|
|
8
|
+
|
|
9
|
+
function getConfigDir() {
|
|
10
|
+
return path.join(os.homedir(), CONFIG_DIR_NAME);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getConfigPath() {
|
|
14
|
+
return path.join(getConfigDir(), CONFIG_FILE_NAME);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ensureConfigDir() {
|
|
18
|
+
const dir = getConfigDir();
|
|
19
|
+
if (!fs.existsSync(dir)) {
|
|
20
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
fs.chmodSync(dir, 0o700);
|
|
24
|
+
} catch {
|
|
25
|
+
// Best-effort on platforms that ignore chmod.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeConfig(config) {
|
|
30
|
+
const filePath = getConfigPath();
|
|
31
|
+
const payload = JSON.stringify(config, null, 2) + "\n";
|
|
32
|
+
fs.writeFileSync(filePath, payload, { encoding: "utf8", mode: 0o600 });
|
|
33
|
+
try {
|
|
34
|
+
fs.chmodSync(filePath, 0o600);
|
|
35
|
+
} catch {
|
|
36
|
+
// Best-effort on platforms that ignore chmod.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readConfig() {
|
|
41
|
+
const filePath = getConfigPath();
|
|
42
|
+
if (!fs.existsSync(filePath)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ensureClientId() {
|
|
55
|
+
ensureConfigDir();
|
|
56
|
+
|
|
57
|
+
const existing = readConfig();
|
|
58
|
+
if (existing?.clientId) {
|
|
59
|
+
return existing.clientId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const clientId = randomUUID();
|
|
63
|
+
writeConfig({ clientId });
|
|
64
|
+
return clientId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getClientId() {
|
|
68
|
+
return ensureClientId();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function readClientId() {
|
|
72
|
+
const existing = readConfig();
|
|
73
|
+
return existing?.clientId ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getGlobalConfigPath() {
|
|
77
|
+
return getConfigPath();
|
|
78
|
+
}
|
package/core/safetyGuards.js
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
// Centralized safety controls. These helpers must stay dependency-light.
|
|
2
|
-
// They enforce fail-open behavior without coupling to specific subsystems.
|
|
3
|
-
|
|
4
|
-
let warnedExperimental = false;
|
|
5
|
-
|
|
6
|
-
export function warnExperimentalOnce(message, logWarn) {
|
|
7
|
-
if (warnedExperimental) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
warnedExperimental = true;
|
|
11
|
-
logWarn(message);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function reportFeatureDisabled(name, verbose, logInfo) {
|
|
15
|
-
if (!verbose) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
logInfo(`${name} disabled by feature flag.`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function withFailOpenReporting(action, onError) {
|
|
22
|
-
try {
|
|
23
|
-
const result = action();
|
|
24
|
-
if (result && typeof result.then === 'function') {
|
|
25
|
-
return await result;
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
} catch {
|
|
29
|
-
if (onError) {
|
|
30
|
-
onError();
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function withFailOpenIntegration(action) {
|
|
37
|
-
try {
|
|
38
|
-
const result = action();
|
|
39
|
-
if (result && typeof result.then === 'function') {
|
|
40
|
-
await result;
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
// Integration failures are ignored to avoid affecting commits.
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function withFailOpenAiEscalation(enabled, action) {
|
|
48
|
-
if (!enabled) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
return await action();
|
|
54
|
-
} catch {
|
|
55
|
-
// AI failures downgrade to warnings by returning no decisions.
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
1
|
+
// Centralized safety controls. These helpers must stay dependency-light.
|
|
2
|
+
// They enforce fail-open behavior without coupling to specific subsystems.
|
|
3
|
+
|
|
4
|
+
let warnedExperimental = false;
|
|
5
|
+
|
|
6
|
+
export function warnExperimentalOnce(message, logWarn) {
|
|
7
|
+
if (warnedExperimental) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
warnedExperimental = true;
|
|
11
|
+
logWarn(message);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function reportFeatureDisabled(name, verbose, logInfo) {
|
|
15
|
+
if (!verbose) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
logInfo(`${name} disabled by feature flag.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function withFailOpenReporting(action, onError) {
|
|
22
|
+
try {
|
|
23
|
+
const result = action();
|
|
24
|
+
if (result && typeof result.then === 'function') {
|
|
25
|
+
return await result;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
} catch {
|
|
29
|
+
if (onError) {
|
|
30
|
+
onError();
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function withFailOpenIntegration(action) {
|
|
37
|
+
try {
|
|
38
|
+
const result = action();
|
|
39
|
+
if (result && typeof result.then === 'function') {
|
|
40
|
+
await result;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Integration failures are ignored to avoid affecting commits.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function withFailOpenAiEscalation(enabled, action) {
|
|
48
|
+
if (!enabled) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return await action();
|
|
54
|
+
} catch {
|
|
55
|
+
// AI failures downgrade to warnings by returning no decisions.
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|