@rigour-labs/core 4.1.1 → 4.2.0
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/dist/hooks/checker.js +89 -0
- package/dist/hooks/dlp-templates.d.ts +26 -0
- package/dist/hooks/dlp-templates.js +281 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/input-validator.d.ts +70 -0
- package/dist/hooks/input-validator.js +461 -0
- package/dist/hooks/input-validator.test.d.ts +1 -0
- package/dist/hooks/input-validator.test.js +272 -0
- package/dist/hooks/standalone-dlp-checker.d.ts +18 -0
- package/dist/hooks/standalone-dlp-checker.js +91 -0
- package/dist/templates/universal-config.js +33 -0
- package/dist/types/index.d.ts +230 -12
- package/dist/types/index.js +59 -0
- package/package.json +6 -6
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Input Validation Gate — AI Agent DLP (Data Loss Prevention)
|
|
3
|
+
*
|
|
4
|
+
* @since v4.2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, } from './input-validator.js';
|
|
8
|
+
// ── Cloud Provider Keys ──────────────────────────────────────────
|
|
9
|
+
describe('scanInputForCredentials — AWS', () => {
|
|
10
|
+
it('detects AWS Access Key IDs', () => {
|
|
11
|
+
const result = scanInputForCredentials('Here is my key: AKIAIOSFODNN7EXAMPLE');
|
|
12
|
+
expect(result.status).toBe('blocked');
|
|
13
|
+
expect(result.detections).toHaveLength(1);
|
|
14
|
+
expect(result.detections[0].type).toBe('aws_access_key');
|
|
15
|
+
expect(result.detections[0].severity).toBe('critical');
|
|
16
|
+
});
|
|
17
|
+
it('detects AWS Secret Key assignments', () => {
|
|
18
|
+
const result = scanInputForCredentials('aws_secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"');
|
|
19
|
+
expect(result.status).toBe('blocked');
|
|
20
|
+
expect(result.detections.some(d => d.type === 'aws_secret_key')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('scanInputForCredentials — GCP', () => {
|
|
24
|
+
it('detects GCP service account JSON', () => {
|
|
25
|
+
const input = '{"type": "service_account", "project_id": "my-proj", "private_key": "-----BEGIN RSA PRIVATE KEY-----"}';
|
|
26
|
+
const result = scanInputForCredentials(input);
|
|
27
|
+
expect(result.status).toBe('blocked');
|
|
28
|
+
// May detect as gcp_service_account and/or private_key
|
|
29
|
+
expect(result.detections.length).toBeGreaterThanOrEqual(1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('scanInputForCredentials — Azure', () => {
|
|
33
|
+
it('detects Azure storage key', () => {
|
|
34
|
+
const result = scanInputForCredentials('AccountKey=dGhpcyBpcyBhIGJhc2U2NCBzdHJpbmcgdGhhdCBpcyBsb25nIGVub3VnaCB0byBtYXRjaA==');
|
|
35
|
+
expect(result.status).toBe('blocked');
|
|
36
|
+
expect(result.detections.some(d => d.type === 'azure_key')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
// ── API Keys (Provider-Specific) ─────────────────────────────────
|
|
40
|
+
describe('scanInputForCredentials — API keys', () => {
|
|
41
|
+
it('detects OpenAI key', () => {
|
|
42
|
+
const result = scanInputForCredentials('sk-proj-abc1234567890ABCDEFGH');
|
|
43
|
+
expect(result.status).toBe('blocked');
|
|
44
|
+
expect(result.detections[0].type).toBe('openai_key');
|
|
45
|
+
});
|
|
46
|
+
it('detects Anthropic key', () => {
|
|
47
|
+
const result = scanInputForCredentials('sk-ant-api03-abcdefghijklmnop123456');
|
|
48
|
+
expect(result.status).toBe('blocked');
|
|
49
|
+
expect(result.detections[0].type).toBe('anthropic_key');
|
|
50
|
+
});
|
|
51
|
+
it('detects GitHub PAT', () => {
|
|
52
|
+
const result = scanInputForCredentials('ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh12');
|
|
53
|
+
expect(result.status).toBe('blocked');
|
|
54
|
+
expect(result.detections[0].type).toBe('github_token');
|
|
55
|
+
});
|
|
56
|
+
it('detects Stripe live key', () => {
|
|
57
|
+
const result = scanInputForCredentials('sk_live_51HxAbCdEfGhIjKlMnOpQrStU');
|
|
58
|
+
expect(result.status).toBe('blocked');
|
|
59
|
+
expect(result.detections[0].type).toBe('stripe_key');
|
|
60
|
+
});
|
|
61
|
+
it('detects SendGrid key', () => {
|
|
62
|
+
const result = scanInputForCredentials('SG.abcdefghijklmnopqrstuv.1234567890abcdefghijklmnopqrstuvwxyz1234567');
|
|
63
|
+
expect(result.status).toBe('blocked');
|
|
64
|
+
expect(result.detections[0].type).toBe('sendgrid_key');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// ── Private Keys ─────────────────────────────────────────────────
|
|
68
|
+
describe('scanInputForCredentials — Private keys', () => {
|
|
69
|
+
it('detects RSA private key header', () => {
|
|
70
|
+
const result = scanInputForCredentials('-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQ...');
|
|
71
|
+
expect(result.status).toBe('blocked');
|
|
72
|
+
expect(result.detections.some(d => d.type === 'private_key')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('detects EC private key header', () => {
|
|
75
|
+
const result = scanInputForCredentials('-----BEGIN EC PRIVATE KEY-----');
|
|
76
|
+
expect(result.status).toBe('blocked');
|
|
77
|
+
});
|
|
78
|
+
it('detects OPENSSH private key header', () => {
|
|
79
|
+
const result = scanInputForCredentials('-----BEGIN OPENSSH PRIVATE KEY-----');
|
|
80
|
+
expect(result.status).toBe('blocked');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// ── Database Connection Strings ──────────────────────────────────
|
|
84
|
+
describe('scanInputForCredentials — Database URLs', () => {
|
|
85
|
+
it('detects PostgreSQL connection string', () => {
|
|
86
|
+
const result = scanInputForCredentials('postgresql://user:password@prod-server:5432/mydb');
|
|
87
|
+
expect(result.status).toBe('blocked');
|
|
88
|
+
const dbDetection = result.detections.find(d => d.type === 'database_url');
|
|
89
|
+
expect(dbDetection).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
it('detects MongoDB connection string', () => {
|
|
92
|
+
const result = scanInputForCredentials('mongodb+srv://admin:s3cret@cluster0.abc123.mongodb.net/production');
|
|
93
|
+
expect(result.status).toBe('blocked');
|
|
94
|
+
});
|
|
95
|
+
it('detects Redis connection string', () => {
|
|
96
|
+
const result = scanInputForCredentials('redis://default:mypassword@redis-host:6379');
|
|
97
|
+
expect(result.status).toBe('blocked');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
// ── Bearer Tokens & JWTs ─────────────────────────────────────────
|
|
101
|
+
describe('scanInputForCredentials — Tokens', () => {
|
|
102
|
+
it('detects JWT token', () => {
|
|
103
|
+
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
104
|
+
const result = scanInputForCredentials(jwt);
|
|
105
|
+
expect(result.status).toBe('blocked');
|
|
106
|
+
expect(result.detections.some(d => d.type === 'jwt_token')).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
// ── Generic Patterns ─────────────────────────────────────────────
|
|
110
|
+
describe('scanInputForCredentials — Generic patterns', () => {
|
|
111
|
+
it('detects password assignment', () => {
|
|
112
|
+
const result = scanInputForCredentials("password = 'SuperSecret123!'");
|
|
113
|
+
expect(result.status).toBe('blocked');
|
|
114
|
+
expect(result.detections[0].type).toBe('password_assignment');
|
|
115
|
+
});
|
|
116
|
+
it('detects api_key assignment', () => {
|
|
117
|
+
const result = scanInputForCredentials('api_key: "abcdefghijklmnopqrstuvwxyz"');
|
|
118
|
+
expect(result.status).toBe('blocked');
|
|
119
|
+
});
|
|
120
|
+
it('detects .env format', () => {
|
|
121
|
+
const result = scanInputForCredentials('DATABASE_PASSWORD=myS3cr3tP@ssw0rd!');
|
|
122
|
+
expect(result.status).toBe('blocked');
|
|
123
|
+
expect(result.detections.some(d => d.type === 'env_variable')).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
it('detects URL with embedded credentials', () => {
|
|
126
|
+
const result = scanInputForCredentials('http://admin:password123@internal-api.company.com/v1');
|
|
127
|
+
expect(result.status).toBe('blocked');
|
|
128
|
+
expect(result.detections.some(d => d.type === 'credentials_in_url')).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// ── Clean Input ──────────────────────────────────────────────────
|
|
132
|
+
describe('scanInputForCredentials — Clean input', () => {
|
|
133
|
+
it('returns clean for normal code', () => {
|
|
134
|
+
const result = scanInputForCredentials('function hello() { return "world"; }');
|
|
135
|
+
expect(result.status).toBe('clean');
|
|
136
|
+
expect(result.detections).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
it('returns clean for env var references (not values)', () => {
|
|
139
|
+
const result = scanInputForCredentials('const key = process.env.API_KEY;');
|
|
140
|
+
expect(result.status).toBe('clean');
|
|
141
|
+
});
|
|
142
|
+
it('returns clean for placeholder values', () => {
|
|
143
|
+
const result = scanInputForCredentials('password = "xxx"');
|
|
144
|
+
expect(result.status).toBe('clean'); // too short
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
// ── Config Options ───────────────────────────────────────────────
|
|
148
|
+
describe('scanInputForCredentials — Config', () => {
|
|
149
|
+
it('respects enabled: false', () => {
|
|
150
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE', { enabled: false });
|
|
151
|
+
expect(result.status).toBe('clean');
|
|
152
|
+
});
|
|
153
|
+
it('returns warning instead of blocked when block_on_detection is false', () => {
|
|
154
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE', { block_on_detection: false });
|
|
155
|
+
expect(result.status).toBe('warning');
|
|
156
|
+
expect(result.detections).toHaveLength(1);
|
|
157
|
+
});
|
|
158
|
+
it('respects custom min_secret_length', () => {
|
|
159
|
+
const result = scanInputForCredentials('password = "short"', { min_secret_length: 20 });
|
|
160
|
+
// "short" is only 5 chars, below default 8, so it would be skipped anyway
|
|
161
|
+
expect(result.status).toBe('clean');
|
|
162
|
+
});
|
|
163
|
+
it('applies custom patterns', () => {
|
|
164
|
+
const result = scanInputForCredentials('internal-token-XYZ123456', {
|
|
165
|
+
custom_patterns: ['internal-token-[A-Z0-9]+'],
|
|
166
|
+
});
|
|
167
|
+
expect(result.status).toBe('blocked');
|
|
168
|
+
expect(result.detections[0].type).toBe('custom_pattern');
|
|
169
|
+
});
|
|
170
|
+
it('respects ignore patterns', () => {
|
|
171
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE', {
|
|
172
|
+
ignore_patterns: ['AKIAIOSFODNN7EXAMPLE'],
|
|
173
|
+
});
|
|
174
|
+
expect(result.status).toBe('clean');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
// ── Performance ──────────────────────────────────────────────────
|
|
178
|
+
describe('scanInputForCredentials — Performance', () => {
|
|
179
|
+
it('completes in under 50ms for typical input', () => {
|
|
180
|
+
const input = 'A'.repeat(10000); // 10KB of text
|
|
181
|
+
const result = scanInputForCredentials(input);
|
|
182
|
+
expect(result.duration_ms).toBeLessThan(50);
|
|
183
|
+
});
|
|
184
|
+
it('tracks scanned_length correctly', () => {
|
|
185
|
+
const input = 'test input here';
|
|
186
|
+
const result = scanInputForCredentials(input);
|
|
187
|
+
expect(result.scanned_length).toBe(input.length);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// ── Deduplication ────────────────────────────────────────────────
|
|
191
|
+
describe('scanInputForCredentials — Deduplication', () => {
|
|
192
|
+
it('deduplicates overlapping detections and keeps higher severity', () => {
|
|
193
|
+
// Input that might trigger both generic password_assignment and a more specific pattern
|
|
194
|
+
const input = 'api_key = "sk-proj-abc1234567890ABCDEFGH"';
|
|
195
|
+
const result = scanInputForCredentials(input);
|
|
196
|
+
// Should not have duplicate detections for the same match region
|
|
197
|
+
const positions = result.detections.map(d => d.position?.start);
|
|
198
|
+
const uniquePositions = new Set(positions);
|
|
199
|
+
// The exact count depends on pattern overlaps, but there should be some deduplication
|
|
200
|
+
expect(result.detections.length).toBeLessThanOrEqual(positions.length);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
// ── Redaction ────────────────────────────────────────────────────
|
|
204
|
+
describe('scanInputForCredentials — Redaction', () => {
|
|
205
|
+
it('redacts matched credentials', () => {
|
|
206
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE');
|
|
207
|
+
expect(result.detections[0].redacted).toContain('****');
|
|
208
|
+
expect(result.detections[0].redacted).not.toBe('AKIAIOSFODNN7EXAMPLE');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ── Compliance ───────────────────────────────────────────────────
|
|
212
|
+
describe('scanInputForCredentials — Compliance', () => {
|
|
213
|
+
it('includes compliance tags for AWS keys', () => {
|
|
214
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE');
|
|
215
|
+
expect(result.detections[0].compliance).toContain('SOC2-CC6.1');
|
|
216
|
+
expect(result.detections[0].compliance).toContain('HIPAA-164.312');
|
|
217
|
+
expect(result.detections[0].compliance).toContain('PCI-DSS-3.4');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
// ── formatDLPAlert ───────────────────────────────────────────────
|
|
221
|
+
describe('formatDLPAlert', () => {
|
|
222
|
+
it('returns clean message for clean input', () => {
|
|
223
|
+
const result = scanInputForCredentials('just normal code');
|
|
224
|
+
const alert = formatDLPAlert(result);
|
|
225
|
+
expect(alert).toContain('clean');
|
|
226
|
+
});
|
|
227
|
+
it('shows BLOCKED header when credentials found', () => {
|
|
228
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE');
|
|
229
|
+
const alert = formatDLPAlert(result);
|
|
230
|
+
expect(alert).toContain('BLOCKED');
|
|
231
|
+
expect(alert).toContain('credential');
|
|
232
|
+
});
|
|
233
|
+
it('shows WARNING header when block_on_detection is false', () => {
|
|
234
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE', { block_on_detection: false });
|
|
235
|
+
const alert = formatDLPAlert(result);
|
|
236
|
+
expect(alert).toContain('WARNING');
|
|
237
|
+
});
|
|
238
|
+
it('includes severity, redacted value, and recommendation', () => {
|
|
239
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE');
|
|
240
|
+
const alert = formatDLPAlert(result);
|
|
241
|
+
expect(alert).toContain('CRITICAL');
|
|
242
|
+
expect(alert).toContain('****');
|
|
243
|
+
expect(alert).toContain('process.env');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
// ── createDLPAuditEntry ──────────────────────────────────────────
|
|
247
|
+
describe('createDLPAuditEntry', () => {
|
|
248
|
+
it('creates structured audit entry', () => {
|
|
249
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE');
|
|
250
|
+
const entry = createDLPAuditEntry(result, { agent: 'claude', userId: 'test-user' });
|
|
251
|
+
expect(entry.type).toBe('dlp_event');
|
|
252
|
+
expect(entry.agent).toBe('claude');
|
|
253
|
+
expect(entry.userId).toBe('test-user');
|
|
254
|
+
expect(entry.status).toBe('blocked');
|
|
255
|
+
expect(entry.timestamp).toBeDefined();
|
|
256
|
+
expect(Array.isArray(entry.detections)).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
it('uses provided timestamp if given', () => {
|
|
259
|
+
const result = scanInputForCredentials('just code');
|
|
260
|
+
const ts = '2025-01-01T00:00:00.000Z';
|
|
261
|
+
const entry = createDLPAuditEntry(result, { agent: 'cursor', timestamp: ts });
|
|
262
|
+
expect(entry.timestamp).toBe(ts);
|
|
263
|
+
});
|
|
264
|
+
it('redacts credentials in audit log (no raw match)', () => {
|
|
265
|
+
const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE');
|
|
266
|
+
const entry = createDLPAuditEntry(result, { agent: 'claude' });
|
|
267
|
+
const detections = entry.detections;
|
|
268
|
+
// Audit entry should have redacted field but NOT the raw match
|
|
269
|
+
expect(detections[0].redacted).toBeDefined();
|
|
270
|
+
expect(detections[0].match).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone DLP Checker — invoked directly by IDE hooks.
|
|
4
|
+
*
|
|
5
|
+
* Reads text from stdin, scans for credentials using the
|
|
6
|
+
* InputValidationGate, and outputs JSON result to stdout.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 — clean (no credentials found)
|
|
10
|
+
* 2 — blocked (credentials detected, transmission prevented)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* echo "my api_key = sk-abc123..." | node standalone-dlp-checker.js
|
|
14
|
+
* echo '{"content":"..."}' | node standalone-dlp-checker.js --json
|
|
15
|
+
*
|
|
16
|
+
* @since v4.2.0 — AI Agent DLP
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone DLP Checker — invoked directly by IDE hooks.
|
|
4
|
+
*
|
|
5
|
+
* Reads text from stdin, scans for credentials using the
|
|
6
|
+
* InputValidationGate, and outputs JSON result to stdout.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 — clean (no credentials found)
|
|
10
|
+
* 2 — blocked (credentials detected, transmission prevented)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* echo "my api_key = sk-abc123..." | node standalone-dlp-checker.js
|
|
14
|
+
* echo '{"content":"..."}' | node standalone-dlp-checker.js --json
|
|
15
|
+
*
|
|
16
|
+
* @since v4.2.0 — AI Agent DLP
|
|
17
|
+
*/
|
|
18
|
+
import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry } from './input-validator.js';
|
|
19
|
+
import fs from 'fs-extra';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
async function main() {
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const isJson = args.includes('--json');
|
|
24
|
+
const block = !args.includes('--warn-only');
|
|
25
|
+
const agent = args.find(a => a.startsWith('--agent='))?.split('=')[1] || 'unknown';
|
|
26
|
+
// Read stdin
|
|
27
|
+
const chunks = [];
|
|
28
|
+
for await (const chunk of process.stdin) {
|
|
29
|
+
chunks.push(chunk);
|
|
30
|
+
}
|
|
31
|
+
const input = Buffer.concat(chunks).toString('utf-8').trim();
|
|
32
|
+
if (!input) {
|
|
33
|
+
process.stdout.write(JSON.stringify({ status: 'clean', detections: [], duration_ms: 0 }));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Parse JSON input if flag set, otherwise scan raw text
|
|
37
|
+
let textToScan = input;
|
|
38
|
+
if (isJson) {
|
|
39
|
+
try {
|
|
40
|
+
const payload = JSON.parse(input);
|
|
41
|
+
// Scan all string values in the payload
|
|
42
|
+
const texts = [];
|
|
43
|
+
function extractStrings(obj) {
|
|
44
|
+
if (typeof obj === 'string' && obj.length > 5) {
|
|
45
|
+
texts.push(obj);
|
|
46
|
+
}
|
|
47
|
+
else if (Array.isArray(obj)) {
|
|
48
|
+
obj.forEach(extractStrings);
|
|
49
|
+
}
|
|
50
|
+
else if (obj && typeof obj === 'object') {
|
|
51
|
+
Object.values(obj).forEach(extractStrings);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
extractStrings(payload);
|
|
55
|
+
textToScan = texts.join('\n');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// If JSON parse fails, scan as raw text
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const result = scanInputForCredentials(textToScan, {
|
|
62
|
+
enabled: true,
|
|
63
|
+
block_on_detection: block,
|
|
64
|
+
});
|
|
65
|
+
// Output JSON result to stdout
|
|
66
|
+
process.stdout.write(JSON.stringify(result));
|
|
67
|
+
// Log to stderr for visibility in IDE panels
|
|
68
|
+
if (result.status !== 'clean') {
|
|
69
|
+
process.stderr.write(formatDLPAlert(result) + '\n');
|
|
70
|
+
// Append to audit trail
|
|
71
|
+
try {
|
|
72
|
+
const cwd = process.cwd();
|
|
73
|
+
const rigourDir = path.join(cwd, '.rigour');
|
|
74
|
+
await fs.ensureDir(rigourDir);
|
|
75
|
+
const eventsPath = path.join(rigourDir, 'events.jsonl');
|
|
76
|
+
const auditEntry = createDLPAuditEntry(result, { agent });
|
|
77
|
+
await fs.appendFile(eventsPath, JSON.stringify(auditEntry) + '\n');
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Silent fail on audit logging
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Exit code: 2 = blocked, 0 = clean/warning
|
|
84
|
+
if (result.status === 'blocked') {
|
|
85
|
+
process.exitCode = 2;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
main().catch(err => {
|
|
89
|
+
process.stderr.write(`Rigour DLP checker error: ${err.message}\n`);
|
|
90
|
+
process.stdout.write(JSON.stringify({ status: 'clean', detections: [], duration_ms: 0 }));
|
|
91
|
+
});
|
|
@@ -155,6 +155,38 @@ export const UNIVERSAL_CONFIG = {
|
|
|
155
155
|
max_mocks_per_test: 5,
|
|
156
156
|
ignore_patterns: [],
|
|
157
157
|
},
|
|
158
|
+
governance: {
|
|
159
|
+
enabled: true,
|
|
160
|
+
enforce_memory: true,
|
|
161
|
+
enforce_skills: true,
|
|
162
|
+
block_native_memory: true,
|
|
163
|
+
protected_memory_paths: [
|
|
164
|
+
'CLAUDE.md', '.claude/CLAUDE.md',
|
|
165
|
+
'.clinerules', '.clinerules/**',
|
|
166
|
+
'.windsurf/memories/**',
|
|
167
|
+
'.github/copilot-instructions.md',
|
|
168
|
+
],
|
|
169
|
+
protected_skills_paths: [
|
|
170
|
+
'.claude/skills/**', '.claude/rules/**', '.claude/commands/**',
|
|
171
|
+
'.cursorrules', '.cursor/rules/**', '.cursor/prompts/**',
|
|
172
|
+
'.cline/rules/**',
|
|
173
|
+
'.windsurf/rules/**', '.windsurfrules',
|
|
174
|
+
'.github/instructions/**', 'copilot-instructions.md',
|
|
175
|
+
],
|
|
176
|
+
exempt_paths: [
|
|
177
|
+
'.claude/settings.json',
|
|
178
|
+
'.cursor/hooks.json',
|
|
179
|
+
'.windsurf/hooks.json',
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
input_validation: {
|
|
183
|
+
enabled: true,
|
|
184
|
+
block_on_detection: true,
|
|
185
|
+
min_secret_length: 8,
|
|
186
|
+
custom_patterns: [],
|
|
187
|
+
ignore_patterns: [],
|
|
188
|
+
audit_log: true,
|
|
189
|
+
},
|
|
158
190
|
deep: {
|
|
159
191
|
enabled: false,
|
|
160
192
|
pro: false,
|
|
@@ -181,6 +213,7 @@ export const UNIVERSAL_CONFIG = {
|
|
|
181
213
|
fast_gates: ['hallucinated-imports', 'phantom-apis', 'deprecated-apis', 'promise-safety', 'security-patterns', 'file-size'],
|
|
182
214
|
timeout_ms: 5000,
|
|
183
215
|
block_on_failure: false,
|
|
216
|
+
dlp: true,
|
|
184
217
|
},
|
|
185
218
|
output: {
|
|
186
219
|
report_path: 'rigour-report.json',
|