@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.
Files changed (60) hide show
  1. package/bin/runners/lib/agent-firewall/change-packet/builder.js +214 -0
  2. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  3. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  4. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  5. package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
  6. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  7. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  8. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  9. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
  10. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  11. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
  12. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  13. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  14. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  15. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  16. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  17. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  18. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  19. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  20. package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
  21. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  22. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  23. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  24. package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
  25. package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
  26. package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
  27. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  28. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  29. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
  30. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
  31. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
  32. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  33. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  34. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  35. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  36. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  37. package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
  38. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  39. package/bin/runners/lib/analysis-core.js +198 -180
  40. package/bin/runners/lib/analyzers.js +1119 -536
  41. package/bin/runners/lib/cli-output.js +236 -210
  42. package/bin/runners/lib/detectors-v2.js +547 -785
  43. package/bin/runners/lib/fingerprint.js +377 -0
  44. package/bin/runners/lib/route-truth.js +1167 -322
  45. package/bin/runners/lib/scan-output.js +144 -738
  46. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  47. package/bin/runners/lib/terminal-ui.js +188 -770
  48. package/bin/runners/lib/truth.js +1004 -321
  49. package/bin/runners/lib/unified-output.js +162 -158
  50. package/bin/runners/runAgent.js +161 -0
  51. package/bin/runners/runFirewall.js +134 -0
  52. package/bin/runners/runFirewallHook.js +56 -0
  53. package/bin/runners/runScan.js +113 -10
  54. package/bin/runners/runShip.js +7 -8
  55. package/bin/runners/runTruth.js +89 -0
  56. package/mcp-server/agent-firewall-interceptor.js +164 -0
  57. package/mcp-server/index.js +347 -313
  58. package/mcp-server/truth-context.js +131 -90
  59. package/mcp-server/truth-firewall-tools.js +1412 -1045
  60. 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
+ };