@vibecheckai/cli 3.2.0 → 3.2.2
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/runners/lib/agent-firewall/change-packet/builder.js +214 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/analysis-core.js +198 -180
- package/bin/runners/lib/analyzers.js +1119 -536
- package/bin/runners/lib/cli-output.js +236 -210
- package/bin/runners/lib/detectors-v2.js +547 -785
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +144 -738
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +188 -770
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runScan.js +113 -10
- package/bin/runners/runShip.js +7 -8
- package/bin/runners/runTruth.js +89 -0
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/index.js +347 -313
- package/mcp-server/truth-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1412 -1045
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor IDE Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Cursor-specific integration for agent firewall.
|
|
5
|
+
* Hooks into Cursor's file write events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const { interceptFileWrite } = require("./base");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Intercept file write in Cursor
|
|
14
|
+
* This would be called by Cursor's extension/plugin system
|
|
15
|
+
*/
|
|
16
|
+
async function interceptCursorWrite({
|
|
17
|
+
projectRoot,
|
|
18
|
+
filePath,
|
|
19
|
+
content,
|
|
20
|
+
oldContent,
|
|
21
|
+
agentId = "cursor"
|
|
22
|
+
}) {
|
|
23
|
+
return await interceptFileWrite({
|
|
24
|
+
projectRoot,
|
|
25
|
+
agentId,
|
|
26
|
+
intent: "Cursor AI edit",
|
|
27
|
+
filePath,
|
|
28
|
+
content,
|
|
29
|
+
oldContent
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
interceptCursorWrite
|
|
35
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VS Code LSP Interceptor
|
|
3
|
+
*
|
|
4
|
+
* VS Code LSP integration for agent firewall.
|
|
5
|
+
* Generic LSP hook for file writes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const { interceptFileWrite } = require("./base");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Intercept file write via LSP
|
|
14
|
+
* This would be called by VS Code's LSP server
|
|
15
|
+
*/
|
|
16
|
+
async function interceptVSCodeWrite({
|
|
17
|
+
projectRoot,
|
|
18
|
+
filePath,
|
|
19
|
+
content,
|
|
20
|
+
oldContent,
|
|
21
|
+
agentId = "vscode"
|
|
22
|
+
}) {
|
|
23
|
+
return await interceptFileWrite({
|
|
24
|
+
projectRoot,
|
|
25
|
+
agentId,
|
|
26
|
+
intent: "VS Code AI edit",
|
|
27
|
+
filePath,
|
|
28
|
+
content,
|
|
29
|
+
oldContent
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
interceptVSCodeWrite
|
|
35
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf IDE Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Windsurf-specific integration for agent firewall.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { interceptFileWrite } = require("./base");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Intercept file write in Windsurf
|
|
13
|
+
* This would be called by Windsurf's extension/plugin system
|
|
14
|
+
*/
|
|
15
|
+
async function interceptWindsurfWrite({
|
|
16
|
+
projectRoot,
|
|
17
|
+
filePath,
|
|
18
|
+
content,
|
|
19
|
+
oldContent,
|
|
20
|
+
agentId = "windsurf"
|
|
21
|
+
}) {
|
|
22
|
+
return await interceptFileWrite({
|
|
23
|
+
projectRoot,
|
|
24
|
+
agentId,
|
|
25
|
+
intent: "Windsurf AI edit",
|
|
26
|
+
filePath,
|
|
27
|
+
content,
|
|
28
|
+
oldContent
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
interceptWindsurfWrite
|
|
34
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"mode": "enforce",
|
|
4
|
+
"profile": "repo-lock",
|
|
5
|
+
"scope": {
|
|
6
|
+
"max_files_touched": 10,
|
|
7
|
+
"max_lines_changed": 600,
|
|
8
|
+
"blocked_paths": [
|
|
9
|
+
"**/node_modules/**",
|
|
10
|
+
"**/dist/**",
|
|
11
|
+
"**/.next/**",
|
|
12
|
+
"**/.vibecheck/packets/**"
|
|
13
|
+
],
|
|
14
|
+
"allowed_paths": [
|
|
15
|
+
"apps/**",
|
|
16
|
+
"packages/**",
|
|
17
|
+
"src/**"
|
|
18
|
+
],
|
|
19
|
+
"require_intent_for_expand_scope": true
|
|
20
|
+
},
|
|
21
|
+
"hard_domains": {
|
|
22
|
+
"routes": true,
|
|
23
|
+
"env": true,
|
|
24
|
+
"auth": true,
|
|
25
|
+
"contracts": true,
|
|
26
|
+
"payments": true,
|
|
27
|
+
"side_effects": true
|
|
28
|
+
},
|
|
29
|
+
"rules": {
|
|
30
|
+
"ghost_route": {
|
|
31
|
+
"severity": "block",
|
|
32
|
+
"enabled": true
|
|
33
|
+
},
|
|
34
|
+
"ghost_env": {
|
|
35
|
+
"severity": "block",
|
|
36
|
+
"enabled": true
|
|
37
|
+
},
|
|
38
|
+
"auth_drift": {
|
|
39
|
+
"severity": "block",
|
|
40
|
+
"enabled": true
|
|
41
|
+
},
|
|
42
|
+
"contract_drift": {
|
|
43
|
+
"severity": "block",
|
|
44
|
+
"enabled": true
|
|
45
|
+
},
|
|
46
|
+
"fake_success_ui": {
|
|
47
|
+
"severity": "warn",
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"block_if_domain": ["payments", "auth", "side_effects"]
|
|
50
|
+
},
|
|
51
|
+
"scope_explosion": {
|
|
52
|
+
"severity": "block",
|
|
53
|
+
"enabled": true
|
|
54
|
+
},
|
|
55
|
+
"unsafe_side_effect": {
|
|
56
|
+
"severity": "block",
|
|
57
|
+
"enabled": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"evidence": {
|
|
61
|
+
"require_pointers": true,
|
|
62
|
+
"acceptable_sources": [
|
|
63
|
+
"truthpack.routes",
|
|
64
|
+
"truthpack.env",
|
|
65
|
+
"truthpack.auth",
|
|
66
|
+
"truthpack.contracts",
|
|
67
|
+
"repo.search"
|
|
68
|
+
],
|
|
69
|
+
"pointer_format": "file:lineStart-lineEnd"
|
|
70
|
+
},
|
|
71
|
+
"verification": {
|
|
72
|
+
"require_for_domains": ["auth", "payments", "side_effects"],
|
|
73
|
+
"accepted": ["tests", "reality"],
|
|
74
|
+
"reality": {
|
|
75
|
+
"enabled": true,
|
|
76
|
+
"block_on": ["fake_success", "no_mutation", "network_error"]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"output": {
|
|
80
|
+
"write_change_packets": true,
|
|
81
|
+
"packet_dir": ".vibecheck/packets",
|
|
82
|
+
"report_formats": ["md", "html"]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Engine
|
|
3
|
+
*
|
|
4
|
+
* Main policy engine that evaluates all rules deterministically.
|
|
5
|
+
* No LLM opinions - pure rule-based evaluation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const ghostRoute = require("./rules/ghost-route");
|
|
11
|
+
const ghostEnv = require("./rules/ghost-env");
|
|
12
|
+
const authDrift = require("./rules/auth-drift");
|
|
13
|
+
const contractDrift = require("./rules/contract-drift");
|
|
14
|
+
const fakeSuccess = require("./rules/fake-success");
|
|
15
|
+
const scope = require("./rules/scope");
|
|
16
|
+
const unsafeSideEffect = require("./rules/unsafe-side-effect");
|
|
17
|
+
const { generateVerdict } = require("./verdict");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Evaluate policy against change packet
|
|
21
|
+
* @param {object} params
|
|
22
|
+
* @param {object} params.policy - Policy configuration
|
|
23
|
+
* @param {array} params.claims - Extracted claims
|
|
24
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
25
|
+
* @param {array} params.files - Changed files
|
|
26
|
+
* @param {string} params.intent - Agent intent message
|
|
27
|
+
* @returns {object} Verdict object
|
|
28
|
+
*/
|
|
29
|
+
function evaluatePolicy({ policy, claims, evidence, files, intent }) {
|
|
30
|
+
const violations = [];
|
|
31
|
+
|
|
32
|
+
// Evaluate all rules
|
|
33
|
+
const ruleEvaluators = [
|
|
34
|
+
ghostRoute,
|
|
35
|
+
ghostEnv,
|
|
36
|
+
authDrift,
|
|
37
|
+
contractDrift,
|
|
38
|
+
fakeSuccess,
|
|
39
|
+
unsafeSideEffect
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Evaluate claim-based rules
|
|
43
|
+
for (const evaluator of ruleEvaluators) {
|
|
44
|
+
try {
|
|
45
|
+
const violation = evaluator.evaluate({ claims, evidence, policy });
|
|
46
|
+
if (violation) {
|
|
47
|
+
violations.push(violation);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(`Rule evaluation error: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Evaluate scope rule (file-based)
|
|
55
|
+
try {
|
|
56
|
+
const violation = scope.evaluate({ files, intent, policy });
|
|
57
|
+
if (violation) {
|
|
58
|
+
violations.push(violation);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn(`Scope rule evaluation error: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Generate final verdict
|
|
65
|
+
const verdict = generateVerdict(violations);
|
|
66
|
+
|
|
67
|
+
return verdict;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
evaluatePolicy
|
|
72
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads and validates agent firewall policy from .vibecheck/policy.json
|
|
5
|
+
* Falls back to default policy if not found.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const Ajv = require("ajv").default || require("ajv");
|
|
13
|
+
|
|
14
|
+
// Load schema
|
|
15
|
+
const schemaPath = path.join(__dirname, "schema.json");
|
|
16
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
17
|
+
|
|
18
|
+
// Load default policy
|
|
19
|
+
const defaultPolicyPath = path.join(__dirname, "default-policy.json");
|
|
20
|
+
const defaultPolicy = JSON.parse(fs.readFileSync(defaultPolicyPath, "utf8"));
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load policy from project root
|
|
24
|
+
* @param {string} projectRoot - Project root directory
|
|
25
|
+
* @param {object} overrides - Optional policy overrides
|
|
26
|
+
* @returns {object} Loaded and validated policy
|
|
27
|
+
*/
|
|
28
|
+
function loadPolicy(projectRoot, overrides = {}) {
|
|
29
|
+
const policyPath = path.join(projectRoot, ".vibecheck", "policy.json");
|
|
30
|
+
|
|
31
|
+
let policy;
|
|
32
|
+
|
|
33
|
+
// Try to load project policy
|
|
34
|
+
if (fs.existsSync(policyPath)) {
|
|
35
|
+
try {
|
|
36
|
+
policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Failed to parse policy file: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Use default policy
|
|
42
|
+
policy = JSON.parse(JSON.stringify(defaultPolicy));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Apply overrides (deep merge)
|
|
46
|
+
if (Object.keys(overrides).length > 0) {
|
|
47
|
+
policy = deepMerge(policy, overrides);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate against schema
|
|
51
|
+
const ajv = new Ajv({ allErrors: true });
|
|
52
|
+
const validate = ajv.compile(schema);
|
|
53
|
+
const valid = validate(policy);
|
|
54
|
+
|
|
55
|
+
if (!valid) {
|
|
56
|
+
const errors = validate.errors.map(e =>
|
|
57
|
+
`${e.instancePath || 'root'} ${e.message}`
|
|
58
|
+
).join(', ');
|
|
59
|
+
throw new Error(`Policy validation failed: ${errors}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return policy;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Deep merge two objects
|
|
67
|
+
* @param {object} target - Target object
|
|
68
|
+
* @param {object} source - Source object to merge
|
|
69
|
+
* @returns {object} Merged object
|
|
70
|
+
*/
|
|
71
|
+
function deepMerge(target, source) {
|
|
72
|
+
const output = Object.assign({}, target);
|
|
73
|
+
|
|
74
|
+
if (isObject(target) && isObject(source)) {
|
|
75
|
+
Object.keys(source).forEach(key => {
|
|
76
|
+
if (isObject(source[key])) {
|
|
77
|
+
if (!(key in target)) {
|
|
78
|
+
Object.assign(output, { [key]: source[key] });
|
|
79
|
+
} else {
|
|
80
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
Object.assign(output, { [key]: source[key] });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if value is a plain object
|
|
93
|
+
* @param {*} item - Value to check
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
function isObject(item) {
|
|
97
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get default policy
|
|
102
|
+
* @returns {object} Default policy
|
|
103
|
+
*/
|
|
104
|
+
function getDefaultPolicy() {
|
|
105
|
+
return JSON.parse(JSON.stringify(defaultPolicy));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Save policy to project root
|
|
110
|
+
* @param {string} projectRoot - Project root directory
|
|
111
|
+
* @param {object} policy - Policy to save
|
|
112
|
+
*/
|
|
113
|
+
function savePolicy(projectRoot, policy) {
|
|
114
|
+
const vibecheckDir = path.join(projectRoot, ".vibecheck");
|
|
115
|
+
const policyPath = path.join(vibecheckDir, "policy.json");
|
|
116
|
+
|
|
117
|
+
// Ensure .vibecheck directory exists
|
|
118
|
+
if (!fs.existsSync(vibecheckDir)) {
|
|
119
|
+
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate before saving
|
|
123
|
+
const ajv = new Ajv({ allErrors: true });
|
|
124
|
+
const validate = ajv.compile(schema);
|
|
125
|
+
const valid = validate(policy);
|
|
126
|
+
|
|
127
|
+
if (!valid) {
|
|
128
|
+
const errors = validate.errors.map(e =>
|
|
129
|
+
`${e.instancePath || 'root'} ${e.message}`
|
|
130
|
+
).join(', ');
|
|
131
|
+
throw new Error(`Policy validation failed: ${errors}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(policyPath, JSON.stringify(policy, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
loadPolicy,
|
|
139
|
+
getDefaultPolicy,
|
|
140
|
+
savePolicy,
|
|
141
|
+
schema,
|
|
142
|
+
defaultPolicy
|
|
143
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Drift Rule
|
|
3
|
+
*
|
|
4
|
+
* Blocks if auth restriction claimed but not enforced.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { CLAIM_TYPES } = require("../../claims/claim-types");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate auth drift rule
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {array} params.claims - Extracted claims
|
|
15
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
16
|
+
* @param {object} params.policy - Policy configuration
|
|
17
|
+
* @returns {object|null} Violation or null
|
|
18
|
+
*/
|
|
19
|
+
function evaluate({ claims, evidence, policy }) {
|
|
20
|
+
const ruleConfig = policy.rules?.auth_drift;
|
|
21
|
+
|
|
22
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find auth claims with CONTRADICTS evidence
|
|
27
|
+
for (let i = 0; i < claims.length; i++) {
|
|
28
|
+
const claim = claims[i];
|
|
29
|
+
|
|
30
|
+
if (claim.type === CLAIM_TYPES.AUTH) {
|
|
31
|
+
const ev = evidence.find(e => e.claimId === `claim_${i}`);
|
|
32
|
+
|
|
33
|
+
if (ev && ev.result === "CONTRADICTS") {
|
|
34
|
+
return {
|
|
35
|
+
rule: "auth_drift",
|
|
36
|
+
severity: ruleConfig.severity || "block",
|
|
37
|
+
message: `Auth drift: ${claim.value} claims auth restriction but route is not protected`,
|
|
38
|
+
claimId: `claim_${i}`,
|
|
39
|
+
claim
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
evaluate
|
|
50
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract Drift Rule
|
|
3
|
+
*
|
|
4
|
+
* Blocks if API contract mismatch.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { CLAIM_TYPES } = require("../../claims/claim-types");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate contract drift rule
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {array} params.claims - Extracted claims
|
|
15
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
16
|
+
* @param {object} params.policy - Policy configuration
|
|
17
|
+
* @returns {object|null} Violation or null
|
|
18
|
+
*/
|
|
19
|
+
function evaluate({ claims, evidence, policy }) {
|
|
20
|
+
const ruleConfig = policy.rules?.contract_drift;
|
|
21
|
+
|
|
22
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find contract claims with CONTRADICTS evidence
|
|
27
|
+
for (let i = 0; i < claims.length; i++) {
|
|
28
|
+
const claim = claims[i];
|
|
29
|
+
|
|
30
|
+
if (claim.type === CLAIM_TYPES.CONTRACT) {
|
|
31
|
+
const ev = evidence.find(e => e.claimId === `claim_${i}`);
|
|
32
|
+
|
|
33
|
+
if (ev && ev.result === "CONTRADICTS") {
|
|
34
|
+
return {
|
|
35
|
+
rule: "contract_drift",
|
|
36
|
+
severity: ruleConfig.severity || "block",
|
|
37
|
+
message: `Contract drift: ${claim.value} API contract mismatch`,
|
|
38
|
+
claimId: `claim_${i}`,
|
|
39
|
+
claim
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
evaluate
|
|
50
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake Success UI Rule
|
|
3
|
+
*
|
|
4
|
+
* Warns/blocks if UI shows success without mutation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { CLAIM_TYPES } = require("../../claims/claim-types");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate fake success UI rule
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {array} params.claims - Extracted claims
|
|
15
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
16
|
+
* @param {object} params.policy - Policy configuration
|
|
17
|
+
* @returns {object|null} Violation or null
|
|
18
|
+
*/
|
|
19
|
+
function evaluate({ claims, evidence, policy }) {
|
|
20
|
+
const ruleConfig = policy.rules?.fake_success_ui;
|
|
21
|
+
|
|
22
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find UI success claims
|
|
27
|
+
const successClaims = claims.filter(c => c.type === CLAIM_TYPES.UI_SUCCESS);
|
|
28
|
+
|
|
29
|
+
if (successClaims.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if there's a corresponding HTTP call or side effect
|
|
34
|
+
const hasHttpCall = claims.some(c =>
|
|
35
|
+
c.type === CLAIM_TYPES.HTTP_CALL || c.type === CLAIM_TYPES.ROUTE
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const hasSideEffect = claims.some(c => c.type === CLAIM_TYPES.SIDE_EFFECT);
|
|
39
|
+
|
|
40
|
+
if (!hasHttpCall && !hasSideEffect) {
|
|
41
|
+
// Check if domain requires blocking
|
|
42
|
+
const blockDomains = ruleConfig.block_if_domain || [];
|
|
43
|
+
const fileDomain = successClaims[0].domain || "general";
|
|
44
|
+
|
|
45
|
+
const shouldBlock = blockDomains.includes(fileDomain);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
rule: "fake_success_ui",
|
|
49
|
+
severity: shouldBlock ? "block" : (ruleConfig.severity || "warn"),
|
|
50
|
+
message: `Fake success UI: Success message shown but no HTTP call or side effect detected`,
|
|
51
|
+
claimId: `claim_${claims.indexOf(successClaims[0])}`,
|
|
52
|
+
claim: successClaims[0]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
evaluate
|
|
61
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ghost Env Rule
|
|
3
|
+
*
|
|
4
|
+
* Blocks if process.env.X used but not declared.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { CLAIM_TYPES } = require("../../claims/claim-types");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate ghost env rule
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {array} params.claims - Extracted claims
|
|
15
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
16
|
+
* @param {object} params.policy - Policy configuration
|
|
17
|
+
* @returns {object|null} Violation or null
|
|
18
|
+
*/
|
|
19
|
+
function evaluate({ claims, evidence, policy }) {
|
|
20
|
+
const ruleConfig = policy.rules?.ghost_env;
|
|
21
|
+
|
|
22
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find env claims with UNPROVEN evidence
|
|
27
|
+
for (let i = 0; i < claims.length; i++) {
|
|
28
|
+
const claim = claims[i];
|
|
29
|
+
|
|
30
|
+
if (claim.type === CLAIM_TYPES.ENV) {
|
|
31
|
+
const ev = evidence.find(e => e.claimId === `claim_${i}`);
|
|
32
|
+
|
|
33
|
+
if (ev && ev.result === "UNPROVEN") {
|
|
34
|
+
return {
|
|
35
|
+
rule: "ghost_env",
|
|
36
|
+
severity: ruleConfig.severity || "block",
|
|
37
|
+
message: `Ghost env var: ${claim.value} is used but not declared`,
|
|
38
|
+
claimId: `claim_${i}`,
|
|
39
|
+
claim
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
evaluate
|
|
50
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ghost Route Rule
|
|
3
|
+
*
|
|
4
|
+
* Blocks if UI references route not registered in truthpack.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { CLAIM_TYPES } = require("../../claims/claim-types");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate ghost route rule
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {array} params.claims - Extracted claims
|
|
15
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
16
|
+
* @param {object} params.policy - Policy configuration
|
|
17
|
+
* @returns {object|null} Violation or null
|
|
18
|
+
*/
|
|
19
|
+
function evaluate({ claims, evidence, policy }) {
|
|
20
|
+
const ruleConfig = policy.rules?.ghost_route;
|
|
21
|
+
|
|
22
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find route claims with UNPROVEN evidence
|
|
27
|
+
for (let i = 0; i < claims.length; i++) {
|
|
28
|
+
const claim = claims[i];
|
|
29
|
+
|
|
30
|
+
if (claim.type === CLAIM_TYPES.ROUTE || claim.type === CLAIM_TYPES.HTTP_CALL) {
|
|
31
|
+
const ev = evidence.find(e => e.claimId === `claim_${i}`);
|
|
32
|
+
|
|
33
|
+
if (ev && ev.result === "UNPROVEN") {
|
|
34
|
+
return {
|
|
35
|
+
rule: "ghost_route",
|
|
36
|
+
severity: ruleConfig.severity || "block",
|
|
37
|
+
message: `Ghost route: ${claim.value} is referenced but not registered in truthpack`,
|
|
38
|
+
claimId: `claim_${i}`,
|
|
39
|
+
claim
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
evaluate
|
|
50
|
+
};
|