@rigour-labs/mcp 4.1.1 → 4.2.1

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/index.js CHANGED
@@ -125,12 +125,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
125
125
  case "rigour_handoff_accept":
126
126
  result = await handleHandoffAccept(cwd, args.handoffId, args.agentId, requestId);
127
127
  break;
128
- // Real-time hooks (v3.0)
128
+ // Real-time hooks + DLP (v3.0 / v4.2)
129
129
  case "rigour_hooks_check":
130
- result = await handleHooksCheck(cwd, args.files, args.timeout);
130
+ result = await handleHooksCheck(cwd, args.files ?? [], args.timeout, args.text, args.agent);
131
131
  break;
132
132
  case "rigour_hooks_init":
133
- result = await handleHooksInit(cwd, args.tool, args.force, args.dryRun);
133
+ result = await handleHooksInit(cwd, args.tool, args.force, args.dryRun, args.dlp ?? true);
134
134
  break;
135
135
  // Deep analysis (v4.0+)
136
136
  case "rigour_check_deep": {
@@ -438,6 +438,14 @@ export declare const TOOL_DEFINITIONS: ({
438
438
  type: string;
439
439
  description: string;
440
440
  };
441
+ text: {
442
+ type: string;
443
+ description: string;
444
+ };
445
+ agent: {
446
+ type: string;
447
+ description: string;
448
+ };
441
449
  cwd: {
442
450
  type: "string";
443
451
  description: string;
@@ -470,6 +478,10 @@ export declare const TOOL_DEFINITIONS: ({
470
478
  type: string;
471
479
  description: string;
472
480
  };
481
+ dlp: {
482
+ type: string;
483
+ description: string;
484
+ };
473
485
  cwd: {
474
486
  type: "string";
475
487
  description: string;
@@ -413,21 +413,23 @@ export const TOOL_DEFINITIONS = [
413
413
  openWorldHint: false,
414
414
  },
415
415
  },
416
- // ─── Real-Time Hooks (v3.0) ────────────────────────────
416
+ // ─── Real-Time Hooks + DLP (v3.0 / v4.2) ───────────────
417
417
  {
418
418
  name: "rigour_hooks_check",
419
- description: "Run the fast hook checker on specific files. Same checks that run inside IDE hooks (Claude, Cursor, Cline, Windsurf). Catches: hardcoded secrets, hallucinated imports, command injection, file size. Completes in <100ms.",
419
+ description: "Run the fast hook checker on specific files. Same checks that run inside IDE hooks (Claude, Cursor, Cline, Windsurf). Catches: hardcoded secrets, hallucinated imports, command injection, file size. Completes in <100ms. NEW: Pass 'text' param for DLP mode — scans user input for credentials (AWS keys, API tokens, database URLs, private keys, JWTs) before agent processing.",
420
420
  inputSchema: {
421
421
  type: "object",
422
422
  properties: {
423
423
  ...cwdParam(),
424
424
  files: { type: "array", items: { type: "string" }, description: "List of file paths (relative to cwd) to check." },
425
425
  timeout: { type: "number", description: "Optional timeout in milliseconds (default: 5000)." },
426
+ text: { type: "string", description: "Text to scan for credentials (DLP mode). When provided, scans text instead of files. Use this to validate user input before agent processing." },
427
+ agent: { type: "string", description: "Agent name for DLP audit trail (e.g., 'claude', 'cursor'). Only used in DLP mode." },
426
428
  },
427
- required: ["cwd", "files"],
429
+ required: ["cwd"],
428
430
  },
429
431
  annotations: {
430
- title: "Fast Hook Check",
432
+ title: "Fast Hook Check + DLP",
431
433
  readOnlyHint: true,
432
434
  destructiveHint: false,
433
435
  idempotentHint: true,
@@ -436,7 +438,7 @@ export const TOOL_DEFINITIONS = [
436
438
  },
437
439
  {
438
440
  name: "rigour_hooks_init",
439
- description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks that run on every file write.",
441
+ description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks AND DLP (credential interception) hooks by default. DLP hooks intercept credentials in user input BEFORE they reach the AI agent. Pass dlp=false to disable DLP hooks only.",
440
442
  inputSchema: {
441
443
  type: "object",
442
444
  properties: {
@@ -444,11 +446,12 @@ export const TOOL_DEFINITIONS = [
444
446
  tool: { type: "string", description: "Target tool: 'claude', 'cursor', 'cline', or 'windsurf'." },
445
447
  force: { type: "boolean", description: "Overwrite existing hook files (default: false)." },
446
448
  dryRun: { type: "boolean", description: "Preview changes without writing files (default: false)." },
449
+ dlp: { type: "boolean", description: "Generate DLP (Data Loss Prevention) hooks for pre-input credential interception (default: true). Set false to skip DLP hooks." },
447
450
  },
448
451
  required: ["cwd", "tool"],
449
452
  },
450
453
  annotations: {
451
- title: "Install IDE Hooks",
454
+ title: "Install IDE Hooks + DLP",
452
455
  readOnlyHint: false,
453
456
  destructiveHint: false,
454
457
  idempotentHint: true,
@@ -0,0 +1,36 @@
1
+ /**
2
+ * DLP Tool Handlers
3
+ *
4
+ * Handlers for: rigour_input_scan, rigour_dlp_init
5
+ *
6
+ * AI Agent Data Loss Prevention — scans user input for credentials
7
+ * before they reach AI agents.
8
+ *
9
+ * @since v4.2.0 — AI Agent DLP layer
10
+ */
11
+ type ToolResult = {
12
+ content: {
13
+ type: string;
14
+ text: string;
15
+ }[];
16
+ isError?: boolean;
17
+ };
18
+ /**
19
+ * rigour_input_scan — Scan text for credentials before agent processing.
20
+ *
21
+ * This is the core DLP tool. Agents can call this to validate user input
22
+ * contains no secrets before processing it.
23
+ */
24
+ export declare function handleInputScan(cwd: string, text: string, agent?: string, block?: boolean): Promise<ToolResult>;
25
+ /**
26
+ * rigour_dlp_init — Generate DLP hook configs for AI coding tools.
27
+ *
28
+ * Creates pre-input hooks that intercept credentials before they
29
+ * reach the AI agent.
30
+ */
31
+ export declare function handleDLPInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean): Promise<ToolResult>;
32
+ /**
33
+ * rigour_dlp_audit — Query the DLP audit log for recent credential interceptions.
34
+ */
35
+ export declare function handleDLPAudit(cwd: string, limit?: number): Promise<ToolResult>;
36
+ export {};
@@ -0,0 +1,183 @@
1
+ /**
2
+ * DLP Tool Handlers
3
+ *
4
+ * Handlers for: rigour_input_scan, rigour_dlp_init
5
+ *
6
+ * AI Agent Data Loss Prevention — scans user input for credentials
7
+ * before they reach AI agents.
8
+ *
9
+ * @since v4.2.0 — AI Agent DLP layer
10
+ */
11
+ import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from '@rigour-labs/core';
12
+ import fs from 'fs-extra';
13
+ import path from 'path';
14
+ /**
15
+ * rigour_input_scan — Scan text for credentials before agent processing.
16
+ *
17
+ * This is the core DLP tool. Agents can call this to validate user input
18
+ * contains no secrets before processing it.
19
+ */
20
+ export async function handleInputScan(cwd, text, agent, block) {
21
+ if (!text || text.trim().length === 0) {
22
+ return {
23
+ content: [{
24
+ type: 'text',
25
+ text: '✓ No input to scan.',
26
+ }],
27
+ };
28
+ }
29
+ const result = scanInputForCredentials(text, {
30
+ enabled: true,
31
+ block_on_detection: block ?? true,
32
+ audit_log: true,
33
+ });
34
+ // Log to audit trail
35
+ if (result.status !== 'clean') {
36
+ try {
37
+ const rigourDir = path.join(cwd, '.rigour');
38
+ await fs.ensureDir(rigourDir);
39
+ const eventsPath = path.join(rigourDir, 'events.jsonl');
40
+ const auditEntry = createDLPAuditEntry(result, {
41
+ agent: agent ?? 'mcp',
42
+ });
43
+ await fs.appendFile(eventsPath, JSON.stringify(auditEntry) + '\n');
44
+ }
45
+ catch {
46
+ // Silent
47
+ }
48
+ }
49
+ if (result.status === 'clean') {
50
+ return {
51
+ content: [{
52
+ type: 'text',
53
+ text: `✓ CLEAN — No credentials detected in input.\nScanned: ${result.scanned_length} chars | Duration: ${result.duration_ms}ms`,
54
+ }],
55
+ };
56
+ }
57
+ const alert = formatDLPAlert(result);
58
+ return {
59
+ content: [{
60
+ type: 'text',
61
+ text: alert,
62
+ }],
63
+ isError: result.status === 'blocked',
64
+ };
65
+ }
66
+ /**
67
+ * rigour_dlp_init — Generate DLP hook configs for AI coding tools.
68
+ *
69
+ * Creates pre-input hooks that intercept credentials before they
70
+ * reach the AI agent.
71
+ */
72
+ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
73
+ try {
74
+ const hookTool = tool;
75
+ const checkerCommand = 'rigour hooks check';
76
+ const files = generateDLPHookFiles(hookTool, checkerCommand);
77
+ if (dryRun) {
78
+ const preview = files.map(f => `${f.path}:\n${f.content.slice(0, 200)}...`).join('\n\n');
79
+ return {
80
+ content: [{
81
+ type: 'text',
82
+ text: `[DRY RUN] Would generate ${files.length} DLP hook file(s) for '${tool}':\n\n${preview}`,
83
+ }],
84
+ };
85
+ }
86
+ const written = [];
87
+ const skipped = [];
88
+ for (const file of files) {
89
+ const fullPath = path.join(cwd, file.path);
90
+ if (!force && await fs.pathExists(fullPath)) {
91
+ skipped.push(file.path);
92
+ continue;
93
+ }
94
+ await fs.ensureDir(path.dirname(fullPath));
95
+ await fs.writeFile(fullPath, file.content);
96
+ if (file.executable) {
97
+ await fs.chmod(fullPath, 0o755);
98
+ }
99
+ written.push(file.path);
100
+ }
101
+ const parts = [];
102
+ if (written.length > 0)
103
+ parts.push(`✓ Created: ${written.join(', ')}`);
104
+ if (skipped.length > 0)
105
+ parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
106
+ parts.push(`Tool: ${tool}`);
107
+ parts.push('DLP Protection: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
108
+ parts.push('');
109
+ parts.push('🛑 Credentials will now be intercepted BEFORE reaching the AI agent.');
110
+ return {
111
+ content: [{ type: 'text', text: parts.join('\n') }],
112
+ };
113
+ }
114
+ catch (error) {
115
+ return {
116
+ content: [{
117
+ type: 'text',
118
+ text: `DLP hook init failed: ${error.message}`,
119
+ }],
120
+ isError: true,
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * rigour_dlp_audit — Query the DLP audit log for recent credential interceptions.
126
+ */
127
+ export async function handleDLPAudit(cwd, limit = 20) {
128
+ try {
129
+ const eventsPath = path.join(cwd, '.rigour', 'events.jsonl');
130
+ if (!await fs.pathExists(eventsPath)) {
131
+ return {
132
+ content: [{
133
+ type: 'text',
134
+ text: 'No DLP events found. The audit trail is empty.',
135
+ }],
136
+ };
137
+ }
138
+ const content = await fs.readFile(eventsPath, 'utf-8');
139
+ const lines = content.trim().split('\n').filter(Boolean);
140
+ // Filter for DLP events only
141
+ const dlpEvents = lines
142
+ .map(line => {
143
+ try {
144
+ return JSON.parse(line);
145
+ }
146
+ catch {
147
+ return null;
148
+ }
149
+ })
150
+ .filter(event => event?.type === 'dlp_event')
151
+ .slice(-limit);
152
+ if (dlpEvents.length === 0) {
153
+ return {
154
+ content: [{
155
+ type: 'text',
156
+ text: 'No DLP interceptions found in the audit trail.',
157
+ }],
158
+ };
159
+ }
160
+ const summary = dlpEvents.map((event) => {
161
+ const types = (event.detections || []).map((d) => d.type).join(', ');
162
+ const severities = (event.detections || []).map((d) => d.severity).join(', ');
163
+ return `[${event.timestamp}] ${event.status.toUpperCase()} | Agent: ${event.agent} | Types: ${types} | Severity: ${severities}`;
164
+ }).join('\n');
165
+ const totalBlocked = dlpEvents.filter((e) => e.status === 'blocked').length;
166
+ const totalWarnings = dlpEvents.filter((e) => e.status === 'warning').length;
167
+ return {
168
+ content: [{
169
+ type: 'text',
170
+ text: `DLP Audit Trail (last ${dlpEvents.length} events)\n\nBlocked: ${totalBlocked} | Warnings: ${totalWarnings}\n\n${summary}`,
171
+ }],
172
+ };
173
+ }
174
+ catch (error) {
175
+ return {
176
+ content: [{
177
+ type: 'text',
178
+ text: `DLP audit query failed: ${error.message}`,
179
+ }],
180
+ isError: true,
181
+ };
182
+ }
183
+ }
@@ -9,10 +9,16 @@ type ToolResult = {
9
9
  * rigour_hooks_check — Run the fast hook checker on specific files.
10
10
  * This is the same check that runs inside IDE hooks (Claude, Cursor, Cline, Windsurf).
11
11
  * Catches: hardcoded secrets, hallucinated imports, command injection, file size.
12
+ *
13
+ * NEW in v4.2.0: When `text` param is provided, runs in DLP mode —
14
+ * scans text for credentials instead of checking files.
12
15
  */
13
- export declare function handleHooksCheck(cwd: string, files: string[], timeout?: number): Promise<ToolResult>;
16
+ export declare function handleHooksCheck(cwd: string, files: string[], timeout?: number, text?: string, agent?: string): Promise<ToolResult>;
14
17
  /**
15
18
  * rigour_hooks_init — Generate hook configs for AI coding tools.
19
+ *
20
+ * NEW in v4.2.0: When `dlp` param is true, also generates pre-input
21
+ * DLP hooks that intercept credentials before agent processing.
16
22
  */
17
- export declare function handleHooksInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean): Promise<ToolResult>;
23
+ export declare function handleHooksInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean, dlp?: boolean): Promise<ToolResult>;
18
24
  export {};
@@ -2,18 +2,63 @@
2
2
  * Hooks Tool Handlers
3
3
  *
4
4
  * Handlers for: rigour_hooks_check, rigour_hooks_init
5
+ * Now includes DLP (Data Loss Prevention) capabilities.
5
6
  *
6
7
  * @since v3.0.0 — real-time hooks for AI coding tools
8
+ * @since v4.2.0 — AI Agent DLP layer (credential interception)
7
9
  */
8
10
  import { runHookChecker, generateHookFiles } from "@rigour-labs/core";
11
+ import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from "@rigour-labs/core";
9
12
  import fs from "fs-extra";
10
13
  import path from "path";
11
14
  /**
12
15
  * rigour_hooks_check — Run the fast hook checker on specific files.
13
16
  * This is the same check that runs inside IDE hooks (Claude, Cursor, Cline, Windsurf).
14
17
  * Catches: hardcoded secrets, hallucinated imports, command injection, file size.
18
+ *
19
+ * NEW in v4.2.0: When `text` param is provided, runs in DLP mode —
20
+ * scans text for credentials instead of checking files.
15
21
  */
16
- export async function handleHooksCheck(cwd, files, timeout) {
22
+ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
23
+ // ── DLP Mode: Scan text for credentials ──────────────────
24
+ if (text) {
25
+ const result = scanInputForCredentials(text, {
26
+ enabled: true,
27
+ block_on_detection: true,
28
+ audit_log: true,
29
+ });
30
+ // Log to audit trail
31
+ if (result.status !== 'clean') {
32
+ try {
33
+ const rigourDir = path.join(cwd, '.rigour');
34
+ await fs.ensureDir(rigourDir);
35
+ const eventsPath = path.join(rigourDir, 'events.jsonl');
36
+ const auditEntry = createDLPAuditEntry(result, {
37
+ agent: agent ?? 'mcp',
38
+ });
39
+ await fs.appendFile(eventsPath, JSON.stringify(auditEntry) + '\n');
40
+ }
41
+ catch {
42
+ // Silent
43
+ }
44
+ }
45
+ if (result.status === 'clean') {
46
+ return {
47
+ content: [{
48
+ type: "text",
49
+ text: `✓ CLEAN — No credentials detected in input.\nScanned: ${result.scanned_length} chars | Duration: ${result.duration_ms}ms`,
50
+ }],
51
+ };
52
+ }
53
+ return {
54
+ content: [{
55
+ type: "text",
56
+ text: formatDLPAlert(result),
57
+ }],
58
+ isError: result.status === 'blocked',
59
+ };
60
+ }
61
+ // ── Standard Mode: Check files ───────────────────────────
17
62
  const input = { cwd, files };
18
63
  if (timeout)
19
64
  input.timeout_ms = timeout;
@@ -36,24 +81,31 @@ export async function handleHooksCheck(cwd, files, timeout) {
36
81
  }
37
82
  /**
38
83
  * rigour_hooks_init — Generate hook configs for AI coding tools.
84
+ *
85
+ * NEW in v4.2.0: When `dlp` param is true, also generates pre-input
86
+ * DLP hooks that intercept credentials before agent processing.
39
87
  */
40
- export async function handleHooksInit(cwd, tool, force = false, dryRun = false) {
88
+ export async function handleHooksInit(cwd, tool, force = false, dryRun = false, dlp = true) {
41
89
  try {
42
90
  const hookTool = tool;
43
91
  const checkerCommand = 'rigour hooks check';
92
+ // Generate post-output hooks (existing)
44
93
  const files = generateHookFiles(hookTool, checkerCommand);
94
+ // Generate DLP pre-input hooks (new)
95
+ const dlpFiles = dlp ? generateDLPHookFiles(hookTool, checkerCommand) : [];
96
+ const allFiles = [...files, ...dlpFiles];
45
97
  if (dryRun) {
46
- const preview = files.map(f => `${f.path}:\n${f.content}`).join('\n\n');
98
+ const preview = allFiles.map(f => `${f.path}:\n${f.content.slice(0, 300)}...`).join('\n\n');
47
99
  return {
48
100
  content: [{
49
101
  type: "text",
50
- text: `[DRY RUN] Would generate ${files.length} hook file(s) for '${tool}':\n\n${preview}`,
102
+ text: `[DRY RUN] Would generate ${allFiles.length} hook file(s) for '${tool}'${dlp ? ' (+ DLP)' : ''}:\n\n${preview}`,
51
103
  }],
52
104
  };
53
105
  }
54
106
  const written = [];
55
107
  const skipped = [];
56
- for (const file of files) {
108
+ for (const file of allFiles) {
57
109
  const fullPath = path.join(cwd, file.path);
58
110
  if (!force && await fs.pathExists(fullPath)) {
59
111
  skipped.push(file.path);
@@ -73,6 +125,11 @@ export async function handleHooksInit(cwd, tool, force = false, dryRun = false)
73
125
  parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
74
126
  parts.push(`Tool: ${tool}`);
75
127
  parts.push('Checks: file-size, security-patterns, hallucinated-imports, command-injection');
128
+ if (dlp) {
129
+ parts.push('');
130
+ parts.push('🛑 DLP Protection: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
131
+ parts.push('Credentials will be intercepted BEFORE reaching the AI agent.');
132
+ }
76
133
  return {
77
134
  content: [{ type: "text", text: parts.join('\n') }],
78
135
  };
@@ -3,13 +3,85 @@
3
3
  *
4
4
  * Handlers for: rigour_remember, rigour_recall, rigour_forget
5
5
  *
6
+ * DLP enforcement: rigour_remember scans BOTH key and value for
7
+ * credentials before persisting. Blocked if secrets detected.
8
+ *
6
9
  * @since v2.17.0 — extracted from monolithic index.ts
10
+ * @since v4.2.0 — DLP gate on memory persistence
7
11
  */
8
12
  import { loadMemory, saveMemory } from '../utils/config.js';
13
+ import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry } from '@rigour-labs/core';
14
+ import fs from 'fs-extra';
15
+ import path from 'path';
16
+ /**
17
+ * Append a DLP audit event to .rigour/events.jsonl
18
+ */
19
+ async function appendDLPAudit(cwd, entry) {
20
+ try {
21
+ const eventsPath = path.join(cwd, '.rigour', 'events.jsonl');
22
+ await fs.ensureDir(path.dirname(eventsPath));
23
+ await fs.appendFile(eventsPath, JSON.stringify(entry) + '\n');
24
+ }
25
+ catch {
26
+ // Audit logging is best-effort, never block on failure
27
+ }
28
+ }
29
+ /**
30
+ * Deep-scan a value: if JSON, extract all nested string values and scan each.
31
+ * Catches agents serializing credentials as JSON to bypass flat-text scanning.
32
+ */
33
+ function deepScanValue(key, value) {
34
+ const texts = [key, value];
35
+ try {
36
+ const parsed = JSON.parse(value);
37
+ extractStrings(parsed, texts);
38
+ }
39
+ catch { /* not JSON — flat scan is sufficient */ }
40
+ return texts.join('\n');
41
+ }
42
+ function extractStrings(obj, out) {
43
+ if (typeof obj === 'string' && obj.length >= 8)
44
+ out.push(obj);
45
+ else if (Array.isArray(obj))
46
+ obj.forEach(v => extractStrings(v, out));
47
+ else if (obj && typeof obj === 'object') {
48
+ for (const v of Object.values(obj))
49
+ extractStrings(v, out);
50
+ }
51
+ }
9
52
  export async function handleRemember(cwd, key, value) {
53
+ // ── DLP Gate: deep-scan key + value (including JSON interiors) ──
54
+ const textToScan = deepScanValue(key, value);
55
+ const dlpResult = scanInputForCredentials(textToScan);
56
+ if (dlpResult.status === 'blocked') {
57
+ // Log the blocked attempt
58
+ const auditEntry = createDLPAuditEntry(dlpResult, {
59
+ agent: 'rigour_remember',
60
+ userId: key,
61
+ });
62
+ await appendDLPAudit(cwd, { ...auditEntry, memory_key: key });
63
+ const alert = formatDLPAlert(dlpResult);
64
+ return {
65
+ content: [{
66
+ type: "text",
67
+ text: `🛑 MEMORY BLOCKED — credentials detected in value for key "${key}".\n\n${alert}\n\nRigour prevented storing secrets in persistent memory. Use environment variables instead.`,
68
+ }],
69
+ };
70
+ }
71
+ // Clean — proceed with storage
10
72
  const store = await loadMemory(cwd);
11
73
  store.memories[key] = { value, timestamp: new Date().toISOString() };
12
74
  await saveMemory(cwd, store);
75
+ // If there were warnings (non-blocking), include them
76
+ if (dlpResult.status === 'warning') {
77
+ const alert = formatDLPAlert(dlpResult);
78
+ return {
79
+ content: [{
80
+ type: "text",
81
+ text: `MEMORY STORED: "${key}" has been saved (with warnings).\n\n${alert}\n\nStored value: ${value}`,
82
+ }],
83
+ };
84
+ }
13
85
  return {
14
86
  content: [{
15
87
  type: "text",
@@ -24,22 +96,55 @@ export async function handleRecall(cwd, key) {
24
96
  if (!memory) {
25
97
  return { content: [{ type: "text", text: `NO MEMORY FOUND for key "${key}". Use rigour_remember to store instructions.` }] };
26
98
  }
99
+ // ── DLP Gate on recall: catch credentials stored before DLP existed ──
100
+ const dlpResult = scanInputForCredentials(memory.value);
101
+ if (dlpResult.status === 'blocked') {
102
+ const alert = formatDLPAlert(dlpResult);
103
+ await appendDLPAudit(cwd, {
104
+ ...createDLPAuditEntry(dlpResult, { agent: 'rigour_recall' }),
105
+ memory_key: key,
106
+ action: 'recall_blocked',
107
+ });
108
+ return {
109
+ content: [{
110
+ type: "text",
111
+ text: `🛑 RECALL BLOCKED — memory "${key}" contains ${dlpResult.detections.length} credential(s).\n\n${alert}\n\nThis memory was stored before DLP was active. Remove it with rigour_forget("${key}") and use environment variables instead.`,
112
+ }],
113
+ };
114
+ }
27
115
  return { content: [{ type: "text", text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})` }] };
28
116
  }
29
117
  const keys = Object.keys(store.memories);
30
118
  if (keys.length === 0) {
31
119
  return { content: [{ type: "text", text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions." }] };
32
120
  }
33
- const allMemories = keys.map(k => {
121
+ // ── DLP scan all memories on bulk recall ──
122
+ const cleanMemories = [];
123
+ const taintedKeys = [];
124
+ for (const k of keys) {
34
125
  const mem = store.memories[k];
35
- return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
36
- }).join("\n\n---\n\n");
37
- return {
38
- content: [{
39
- type: "text",
40
- text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
41
- }],
42
- };
126
+ const dlpResult = scanInputForCredentials(mem.value);
127
+ if (dlpResult.status === 'blocked') {
128
+ taintedKeys.push(k);
129
+ }
130
+ else {
131
+ cleanMemories.push(`## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`);
132
+ }
133
+ }
134
+ let text = '';
135
+ if (taintedKeys.length > 0) {
136
+ text += `🛑 ${taintedKeys.length} memory(ies) BLOCKED — contain credentials: ${taintedKeys.join(', ')}\nUse rigour_forget to remove them.\n\n---\n\n`;
137
+ }
138
+ if (cleanMemories.length > 0) {
139
+ text += `RECALLED ${cleanMemories.length} CLEAN MEMORIES:\n\n${cleanMemories.join('\n\n---\n\n')}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`;
140
+ }
141
+ else if (taintedKeys.length > 0) {
142
+ text += `No clean memories to recall. All stored memories contain credentials.`;
143
+ }
144
+ else {
145
+ text = "NO MEMORIES STORED. Use rigour_remember to persist important instructions.";
146
+ }
147
+ return { content: [{ type: "text", text }] };
43
148
  }
44
149
  export async function handleForget(cwd, key) {
45
150
  const store = await loadMemory(cwd);
@@ -178,6 +178,23 @@ export declare function loadConfig(cwd: string): Promise<{
178
178
  check_assertion_free_async: boolean;
179
179
  max_mocks_per_test: number;
180
180
  };
181
+ governance: {
182
+ enabled: boolean;
183
+ enforce_memory: boolean;
184
+ enforce_skills: boolean;
185
+ block_native_memory: boolean;
186
+ protected_memory_paths: string[];
187
+ protected_skills_paths: string[];
188
+ exempt_paths: string[];
189
+ };
190
+ input_validation: {
191
+ enabled: boolean;
192
+ ignore_patterns: string[];
193
+ block_on_detection: boolean;
194
+ min_secret_length: number;
195
+ custom_patterns: string[];
196
+ audit_log: boolean;
197
+ };
181
198
  };
182
199
  hooks: {
183
200
  enabled: boolean;
@@ -185,6 +202,7 @@ export declare function loadConfig(cwd: string): Promise<{
185
202
  timeout_ms: number;
186
203
  fast_gates: string[];
187
204
  block_on_failure: boolean;
205
+ dlp: boolean;
188
206
  };
189
207
  output: {
190
208
  report_path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/mcp",
3
- "version": "4.1.1",
3
+ "version": "4.2.1",
4
4
  "description": "MCP server for AI code governance — OWASP LLM Top 10 (10/10), real-time hooks, 25+ security patterns, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, Cline, Windsurf, Gemini. Industry presets for HIPAA, SOC2, FedRAMP.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -48,7 +48,7 @@
48
48
  "execa": "^8.0.1",
49
49
  "fs-extra": "^11.2.0",
50
50
  "yaml": "^2.8.2",
51
- "@rigour-labs/core": "4.1.1"
51
+ "@rigour-labs/core": "4.2.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^25.0.3",