@rigour-labs/mcp 5.4.0 → 5.5.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/README.md +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/cli-command.d.ts +1 -0
- package/dist/tools/cli-command.js +30 -0
- package/dist/tools/context-handlers.js +78 -12
- package/dist/tools/definitions.js +2 -2
- package/dist/tools/dlp-handler.d.ts +5 -5
- package/dist/tools/dlp-handler.js +13 -29
- package/dist/tools/hook-file-writer.d.ts +14 -0
- package/dist/tools/hook-file-writer.js +71 -0
- package/dist/tools/hooks-handler.d.ts +1 -1
- package/dist/tools/hooks-handler.js +11 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**AI Agent Governance via Model Context Protocol — quality gates, DLP, drift detection, and deep analysis.**
|
|
4
4
|
|
|
5
|
-
Rigour is a local-first MCP server that governs AI agents (Claude, Cursor, Cline, Windsurf) with deterministic quality gates, credential
|
|
5
|
+
Rigour is a local-first MCP server that governs AI agents (Claude, Cursor, Cline, Windsurf) with deterministic quality gates, credential warnings, and memory governance.
|
|
6
6
|
|
|
7
7
|
[](https://github.com/mcp)
|
|
8
8
|
[](https://www.npmjs.com/package/@rigour-labs/mcp)
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import { handleContextStats, handleTaskCost, handleCacheStats, handleContextExpl
|
|
|
34
34
|
import { handleIndex } from './tools/index-handlers.js';
|
|
35
35
|
import { recordContextEvent, estimateTokenCount } from '@rigour-labs/core';
|
|
36
36
|
// ─── Server Setup ─────────────────────────────────────────────────
|
|
37
|
-
const server = new Server({ name: "rigour-mcp", version: getMcpVersion('5.
|
|
37
|
+
const server = new Server({ name: "rigour-mcp", version: getMcpVersion('5.5.0') }, { capabilities: { tools: {}, prompts: {}, logging: {}, resources: {} } });
|
|
38
38
|
// Bind server for logging notifications from handlers
|
|
39
39
|
bindServer(server);
|
|
40
40
|
// ─── Tool Listing ─────────────────────────────────────────────────
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getPinnedCheckerCommand(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readJsonSync } from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
function readVersion(packagePath) {
|
|
5
|
+
try {
|
|
6
|
+
const pkg = readJsonSync(packagePath);
|
|
7
|
+
const version = pkg.version?.trim();
|
|
8
|
+
return version && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)
|
|
9
|
+
? version
|
|
10
|
+
: null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function getPinnedCheckerCommand() {
|
|
17
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const packageCandidates = [
|
|
19
|
+
path.resolve(thisDir, '../../../cli/package.json'),
|
|
20
|
+
path.resolve(thisDir, '../../../rigour-cli/package.json'),
|
|
21
|
+
path.resolve(thisDir, '../../package.json'),
|
|
22
|
+
];
|
|
23
|
+
const version = packageCandidates
|
|
24
|
+
.map(readVersion)
|
|
25
|
+
.find((candidate) => candidate !== null);
|
|
26
|
+
if (!version) {
|
|
27
|
+
throw new Error('Unable to resolve a compatible Rigour CLI version');
|
|
28
|
+
}
|
|
29
|
+
return `npx --yes @rigour-labs/cli@${version} hooks check`;
|
|
30
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import fs from 'fs-extra';
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import { createHash } from 'crypto';
|
|
14
|
-
import { getTaskContextStats, getTaskCostStats, getCacheStats, explainContext, getSemanticQueryCache, setSemanticQueryCache, estimateTokenCount, } from '@rigour-labs/core';
|
|
14
|
+
import { getTaskContextStats, getTaskCostStats, getCacheStats, explainContext, getSemanticQueryCache, setSemanticQueryCache, findRelatedSemanticQueryCache, filterExistingEditScope, estimateTokenCount, } from '@rigour-labs/core';
|
|
15
15
|
import { loadPatternIndex, getDefaultIndexPath, generateEmbedding, semanticSearch, } from '@rigour-labs/core/pattern-index';
|
|
16
16
|
import { buildTelemetryMeta, getWorkspaceCommitSha } from '../utils/context-telemetry.js';
|
|
17
17
|
import { appendContextFooter } from '../utils/context-footer.js';
|
|
@@ -122,26 +122,88 @@ function textMatchPatterns(query, patterns, limit) {
|
|
|
122
122
|
.slice(0, limit)
|
|
123
123
|
.map(s => s.pattern);
|
|
124
124
|
}
|
|
125
|
+
async function serveCachedScope(cached, opts) {
|
|
126
|
+
const originalScope = cached.editScope || [];
|
|
127
|
+
const { valid, missing } = await filterExistingEditScope(originalScope, opts.cwd);
|
|
128
|
+
// Quality guard: require majority of scoped files still present, else recompute.
|
|
129
|
+
if (originalScope.length > 0) {
|
|
130
|
+
const retention = valid.length / originalScope.length;
|
|
131
|
+
if (valid.length === 0 || retention < 0.5) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const validSet = new Set(valid);
|
|
136
|
+
const validationScope = (cached.validationScope || []).filter((item) => {
|
|
137
|
+
// Keep entries that don't look like file paths, or whose path still exists.
|
|
138
|
+
const maybePath = item.replace(/^review\s+/i, '').trim();
|
|
139
|
+
if (!maybePath.includes('/') && !maybePath.endsWith('.ts') && !maybePath.endsWith('.tsx')) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return validSet.has(maybePath) || valid.some((f) => item.includes(f));
|
|
143
|
+
});
|
|
144
|
+
const evidence = (cached.evidence || []).filter((line) => {
|
|
145
|
+
if (missing.length === 0)
|
|
146
|
+
return true;
|
|
147
|
+
return !missing.some((m) => line.includes(m));
|
|
148
|
+
});
|
|
149
|
+
const payload = {
|
|
150
|
+
...cached,
|
|
151
|
+
editScope: valid,
|
|
152
|
+
validationScope,
|
|
153
|
+
evidence,
|
|
154
|
+
staleFilesDropped: missing,
|
|
155
|
+
cacheNote: opts.note,
|
|
156
|
+
};
|
|
157
|
+
const cachedText = JSON.stringify(payload, null, 2);
|
|
158
|
+
const telemetry = scopeTelemetryMeta({
|
|
159
|
+
candidateText: opts.fullIndexCandidate,
|
|
160
|
+
returnedText: cachedText,
|
|
161
|
+
cacheStatus: opts.cacheStatus,
|
|
162
|
+
query: opts.query,
|
|
163
|
+
taskId: opts.taskId,
|
|
164
|
+
agentId: opts.agentId,
|
|
165
|
+
});
|
|
166
|
+
const label = opts.cacheStatus === 'partial-hit'
|
|
167
|
+
? 'CONTEXT SCOPE (partial cache)'
|
|
168
|
+
: 'CONTEXT SCOPE (cached)';
|
|
169
|
+
const text = `${label}\n\n${cachedText}`;
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: 'text', text: appendContextFooter(text, telemetry, 'rigour_check_pattern before creating code') }],
|
|
172
|
+
_telemetry: telemetry,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
125
175
|
export async function handleContextScope(cwd, query, limit = 10, taskId, agentId) {
|
|
126
176
|
const commitSha = await getWorkspaceCommitSha(cwd);
|
|
127
177
|
const indexPath = getDefaultIndexPath(cwd);
|
|
128
178
|
const fullIndexCandidate = `Full repository scan for: ${query}`;
|
|
129
179
|
const cached = await getSemanticQueryCache(query, commitSha, cwd);
|
|
130
180
|
if (cached) {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
candidateText: fullIndexCandidate,
|
|
134
|
-
returnedText: cachedText,
|
|
135
|
-
cacheStatus: 'semantic-hit',
|
|
181
|
+
const served = await serveCachedScope(cached, {
|
|
182
|
+
cwd,
|
|
136
183
|
query,
|
|
184
|
+
fullIndexCandidate,
|
|
185
|
+
// Exact query+commit key hit — content-addressed semantic layer.
|
|
186
|
+
cacheStatus: 'exact-hit',
|
|
137
187
|
taskId,
|
|
138
188
|
agentId,
|
|
139
189
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
190
|
+
if (served)
|
|
191
|
+
return served;
|
|
192
|
+
}
|
|
193
|
+
// Quality-safe power-up: reuse highly overlapping queries at the same commit only.
|
|
194
|
+
const related = await findRelatedSemanticQueryCache(query, commitSha, cwd);
|
|
195
|
+
if (related) {
|
|
196
|
+
const served = await serveCachedScope(related.entry, {
|
|
197
|
+
cwd,
|
|
198
|
+
query,
|
|
199
|
+
fullIndexCandidate,
|
|
200
|
+
cacheStatus: 'partial-hit',
|
|
201
|
+
taskId,
|
|
202
|
+
agentId,
|
|
203
|
+
note: `Reused related query at same commit (overlap ${(related.overlap * 100).toFixed(0)}%): "${related.sourceQuery}"`,
|
|
204
|
+
});
|
|
205
|
+
if (served)
|
|
206
|
+
return served;
|
|
145
207
|
}
|
|
146
208
|
const index = await loadPatternIndex(indexPath);
|
|
147
209
|
if (!index) {
|
|
@@ -165,12 +227,16 @@ export async function handleContextScope(cwd, query, limit = 10, taskId, agentId
|
|
|
165
227
|
try {
|
|
166
228
|
const queryVector = await generateEmbedding(query);
|
|
167
229
|
const similarities = semanticSearch(queryVector, index.patterns);
|
|
230
|
+
// Slightly stricter threshold — quality over vanity hit-rate; partial cache covers near-queries.
|
|
168
231
|
matched = index.patterns
|
|
169
232
|
.map((p, i) => ({ pattern: p, similarity: similarities[i] ?? 0 }))
|
|
170
|
-
.filter(r => r.similarity > 0.
|
|
233
|
+
.filter(r => r.similarity > 0.4)
|
|
171
234
|
.sort((a, b) => b.similarity - a.similarity)
|
|
172
235
|
.slice(0, limit)
|
|
173
236
|
.map(r => r.pattern);
|
|
237
|
+
if (matched.length === 0) {
|
|
238
|
+
matched = textMatchPatterns(query, index.patterns, limit);
|
|
239
|
+
}
|
|
174
240
|
}
|
|
175
241
|
catch {
|
|
176
242
|
matched = textMatchPatterns(query, index.patterns, limit);
|
|
@@ -441,7 +441,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
441
441
|
},
|
|
442
442
|
{
|
|
443
443
|
name: "rigour_hooks_init",
|
|
444
|
-
description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks
|
|
444
|
+
description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks and non-blocking DLP credential warnings by default. Pass dlp=false to disable DLP hooks only.",
|
|
445
445
|
inputSchema: {
|
|
446
446
|
type: "object",
|
|
447
447
|
properties: {
|
|
@@ -449,7 +449,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
449
449
|
tool: { type: "string", description: "Target tool: 'claude', 'cursor', 'cline', or 'windsurf'." },
|
|
450
450
|
force: { type: "boolean", description: "Overwrite existing hook files (default: false)." },
|
|
451
451
|
dryRun: { type: "boolean", description: "Preview changes without writing files (default: false)." },
|
|
452
|
-
dlp: { type: "boolean", description: "Generate DLP
|
|
452
|
+
dlp: { type: "boolean", description: "Generate DLP hooks for pre-input credential warnings (default: true). Set false to skip DLP hooks." },
|
|
453
453
|
},
|
|
454
454
|
required: ["cwd", "tool"],
|
|
455
455
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Handlers for: rigour_input_scan, rigour_dlp_init
|
|
5
5
|
*
|
|
6
6
|
* AI Agent Data Loss Prevention — scans user input for credentials
|
|
7
|
-
* before
|
|
7
|
+
* before agent actions.
|
|
8
8
|
*
|
|
9
9
|
* @since v4.2.0 — AI Agent DLP layer
|
|
10
10
|
*/
|
|
@@ -19,18 +19,18 @@ type ToolResult = {
|
|
|
19
19
|
* rigour_input_scan — Scan text for credentials before agent processing.
|
|
20
20
|
*
|
|
21
21
|
* This is the core DLP tool. Agents can call this to validate user input
|
|
22
|
-
*
|
|
22
|
+
* may contain secrets before processing it.
|
|
23
23
|
*/
|
|
24
24
|
export declare function handleInputScan(cwd: string, text: string, agent?: string, block?: boolean): Promise<ToolResult>;
|
|
25
25
|
/**
|
|
26
26
|
* rigour_dlp_init — Generate DLP hook configs for AI coding tools.
|
|
27
27
|
*
|
|
28
|
-
* Creates pre-input hooks that
|
|
29
|
-
*
|
|
28
|
+
* Creates pre-input hooks that warn about possible credentials before
|
|
29
|
+
* agent actions.
|
|
30
30
|
*/
|
|
31
31
|
export declare function handleDLPInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean): Promise<ToolResult>;
|
|
32
32
|
/**
|
|
33
|
-
* rigour_dlp_audit — Query the DLP audit log for recent credential
|
|
33
|
+
* rigour_dlp_audit — Query the DLP audit log for recent credential detections.
|
|
34
34
|
*/
|
|
35
35
|
export declare function handleDLPAudit(cwd: string, limit?: number): Promise<ToolResult>;
|
|
36
36
|
export {};
|
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
* Handlers for: rigour_input_scan, rigour_dlp_init
|
|
5
5
|
*
|
|
6
6
|
* AI Agent Data Loss Prevention — scans user input for credentials
|
|
7
|
-
* before
|
|
7
|
+
* before agent actions.
|
|
8
8
|
*
|
|
9
9
|
* @since v4.2.0 — AI Agent DLP layer
|
|
10
10
|
*/
|
|
11
11
|
import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from '@rigour-labs/core';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import path from 'path';
|
|
14
|
+
import { getPinnedCheckerCommand } from './cli-command.js';
|
|
15
|
+
import { formatHookWriteResult, writeHookFiles } from './hook-file-writer.js';
|
|
14
16
|
/**
|
|
15
17
|
* rigour_input_scan — Scan text for credentials before agent processing.
|
|
16
18
|
*
|
|
17
19
|
* This is the core DLP tool. Agents can call this to validate user input
|
|
18
|
-
*
|
|
20
|
+
* may contain secrets before processing it.
|
|
19
21
|
*/
|
|
20
22
|
export async function handleInputScan(cwd, text, agent, block) {
|
|
21
23
|
if (!text || text.trim().length === 0) {
|
|
@@ -28,7 +30,7 @@ export async function handleInputScan(cwd, text, agent, block) {
|
|
|
28
30
|
}
|
|
29
31
|
const result = scanInputForCredentials(text, {
|
|
30
32
|
enabled: true,
|
|
31
|
-
block_on_detection: block ??
|
|
33
|
+
block_on_detection: block ?? false,
|
|
32
34
|
audit_log: true,
|
|
33
35
|
});
|
|
34
36
|
// Log to audit trail
|
|
@@ -66,13 +68,13 @@ export async function handleInputScan(cwd, text, agent, block) {
|
|
|
66
68
|
/**
|
|
67
69
|
* rigour_dlp_init — Generate DLP hook configs for AI coding tools.
|
|
68
70
|
*
|
|
69
|
-
* Creates pre-input hooks that
|
|
70
|
-
*
|
|
71
|
+
* Creates pre-input hooks that warn about possible credentials before
|
|
72
|
+
* agent actions.
|
|
71
73
|
*/
|
|
72
74
|
export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
|
|
73
75
|
try {
|
|
74
76
|
const hookTool = tool;
|
|
75
|
-
const checkerCommand =
|
|
77
|
+
const checkerCommand = getPinnedCheckerCommand();
|
|
76
78
|
const files = generateDLPHookFiles(hookTool, checkerCommand);
|
|
77
79
|
if (dryRun) {
|
|
78
80
|
const preview = files.map(f => `${f.path}:\n${f.content.slice(0, 200)}...`).join('\n\n');
|
|
@@ -83,30 +85,12 @@ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
|
|
|
83
85
|
}],
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
|
-
const
|
|
87
|
-
const
|
|
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.`);
|
|
88
|
+
const writeResult = await writeHookFiles(cwd, files, force);
|
|
89
|
+
const parts = formatHookWriteResult(writeResult);
|
|
106
90
|
parts.push(`Tool: ${tool}`);
|
|
107
|
-
parts.push('DLP
|
|
91
|
+
parts.push('DLP warnings: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
|
|
108
92
|
parts.push('');
|
|
109
|
-
parts.push('
|
|
93
|
+
parts.push('⚠ Possible credentials will be reported before agent actions.');
|
|
110
94
|
return {
|
|
111
95
|
content: [{ type: 'text', text: parts.join('\n') }],
|
|
112
96
|
};
|
|
@@ -122,7 +106,7 @@ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
|
|
|
122
106
|
}
|
|
123
107
|
}
|
|
124
108
|
/**
|
|
125
|
-
* rigour_dlp_audit — Query the DLP audit log for recent credential
|
|
109
|
+
* rigour_dlp_audit — Query the DLP audit log for recent credential detections.
|
|
126
110
|
*/
|
|
127
111
|
export async function handleDLPAudit(cwd, limit = 20) {
|
|
128
112
|
try {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface HookFile {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
executable?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function mergeHookFiles(files: HookFile[]): HookFile[];
|
|
7
|
+
export interface HookWriteResult {
|
|
8
|
+
written: string[];
|
|
9
|
+
skipped: string[];
|
|
10
|
+
incompatible: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function writeHookFiles(cwd: string, files: HookFile[], force: boolean): Promise<HookWriteResult>;
|
|
13
|
+
export declare function formatHookWriteResult(result: HookWriteResult): string[];
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
function parseHookConfig(content, filePath) {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.parse(content);
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
9
|
+
throw new Error(`Cannot merge generated hook config ${filePath}: ${reason}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function mergeHookFiles(files) {
|
|
13
|
+
const merged = new Map();
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const existing = merged.get(file.path);
|
|
16
|
+
if (!existing) {
|
|
17
|
+
merged.set(file.path, file);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const existingConfig = parseHookConfig(existing.content, file.path);
|
|
21
|
+
const nextConfig = parseHookConfig(file.content, file.path);
|
|
22
|
+
merged.set(file.path, {
|
|
23
|
+
...existing,
|
|
24
|
+
content: JSON.stringify({
|
|
25
|
+
...existingConfig,
|
|
26
|
+
...nextConfig,
|
|
27
|
+
hooks: { ...existingConfig.hooks, ...nextConfig.hooks },
|
|
28
|
+
}, null, 2),
|
|
29
|
+
executable: existing.executable || file.executable,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return [...merged.values()];
|
|
33
|
+
}
|
|
34
|
+
export async function writeHookFiles(cwd, files, force) {
|
|
35
|
+
const result = { written: [], skipped: [], incompatible: [] };
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const fullPath = path.join(cwd, file.path);
|
|
38
|
+
if (!force && await fs.pathExists(fullPath)) {
|
|
39
|
+
result.skipped.push(file.path);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
44
|
+
await fs.writeFile(fullPath, file.content);
|
|
45
|
+
if (file.executable)
|
|
46
|
+
await fs.chmod(fullPath, 0o755);
|
|
47
|
+
result.written.push(file.path);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const code = error instanceof Error && 'code' in error
|
|
51
|
+
? String(error.code)
|
|
52
|
+
: '';
|
|
53
|
+
if (code !== 'ENOTDIR' && code !== 'EEXIST')
|
|
54
|
+
throw error;
|
|
55
|
+
result.incompatible.push(file.path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
export function formatHookWriteResult(result) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
if (result.written.length > 0)
|
|
63
|
+
parts.push(`✓ Created: ${result.written.join(', ')}`);
|
|
64
|
+
if (result.skipped.length > 0) {
|
|
65
|
+
parts.push(`⊘ Skipped (exists): ${result.skipped.join(', ')}. Use force=true to overwrite.`);
|
|
66
|
+
}
|
|
67
|
+
if (result.incompatible.length > 0) {
|
|
68
|
+
parts.push(`⚠ Not configured (parent path is a file): ${result.incompatible.join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
return parts;
|
|
71
|
+
}
|
|
@@ -18,7 +18,7 @@ export declare function handleHooksCheck(cwd: string, files: string[], timeout?:
|
|
|
18
18
|
* rigour_hooks_init — Generate hook configs for AI coding tools.
|
|
19
19
|
*
|
|
20
20
|
* NEW in v4.2.0: When `dlp` param is true, also generates pre-input
|
|
21
|
-
* DLP hooks that
|
|
21
|
+
* DLP hooks that warn about possible credentials before agent processing.
|
|
22
22
|
*/
|
|
23
23
|
export declare function handleHooksInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean, dlp?: boolean): Promise<ToolResult>;
|
|
24
24
|
export {};
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* Now includes DLP (Data Loss Prevention) capabilities.
|
|
6
6
|
*
|
|
7
7
|
* @since v3.0.0 — real-time hooks for AI coding tools
|
|
8
|
-
* @since v4.2.0 — AI Agent DLP layer
|
|
8
|
+
* @since v4.2.0 — AI Agent DLP warning layer
|
|
9
9
|
*/
|
|
10
10
|
import { runHookChecker, generateHookFiles } from "@rigour-labs/core";
|
|
11
11
|
import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from "@rigour-labs/core";
|
|
12
12
|
import fs from "fs-extra";
|
|
13
13
|
import path from "path";
|
|
14
|
+
import { getPinnedCheckerCommand } from "./cli-command.js";
|
|
15
|
+
import { formatHookWriteResult, mergeHookFiles, writeHookFiles } from "./hook-file-writer.js";
|
|
14
16
|
/**
|
|
15
17
|
* rigour_hooks_check — Run the fast hook checker on specific files.
|
|
16
18
|
* This is the same check that runs inside IDE hooks (Claude, Cursor, Cline, Windsurf).
|
|
@@ -24,7 +26,7 @@ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
|
|
|
24
26
|
if (text) {
|
|
25
27
|
const result = scanInputForCredentials(text, {
|
|
26
28
|
enabled: true,
|
|
27
|
-
block_on_detection:
|
|
29
|
+
block_on_detection: false,
|
|
28
30
|
audit_log: true,
|
|
29
31
|
});
|
|
30
32
|
// Log to audit trail (with rotation to prevent unbounded growth)
|
|
@@ -92,17 +94,17 @@ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
|
|
|
92
94
|
* rigour_hooks_init — Generate hook configs for AI coding tools.
|
|
93
95
|
*
|
|
94
96
|
* NEW in v4.2.0: When `dlp` param is true, also generates pre-input
|
|
95
|
-
* DLP hooks that
|
|
97
|
+
* DLP hooks that warn about possible credentials before agent processing.
|
|
96
98
|
*/
|
|
97
99
|
export async function handleHooksInit(cwd, tool, force = false, dryRun = false, dlp = true) {
|
|
98
100
|
try {
|
|
99
101
|
const hookTool = tool;
|
|
100
|
-
const checkerCommand =
|
|
102
|
+
const checkerCommand = getPinnedCheckerCommand();
|
|
101
103
|
// Generate post-output hooks (existing)
|
|
102
104
|
const files = generateHookFiles(hookTool, checkerCommand);
|
|
103
105
|
// Generate DLP pre-input hooks (new)
|
|
104
106
|
const dlpFiles = dlp ? generateDLPHookFiles(hookTool, checkerCommand) : [];
|
|
105
|
-
const allFiles = [...files, ...dlpFiles];
|
|
107
|
+
const allFiles = mergeHookFiles([...files, ...dlpFiles]);
|
|
106
108
|
if (dryRun) {
|
|
107
109
|
const preview = allFiles.map(f => `${f.path}:\n${f.content.slice(0, 300)}...`).join('\n\n');
|
|
108
110
|
return {
|
|
@@ -112,32 +114,14 @@ export async function handleHooksInit(cwd, tool, force = false, dryRun = false,
|
|
|
112
114
|
}],
|
|
113
115
|
};
|
|
114
116
|
}
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
for (const file of allFiles) {
|
|
118
|
-
const fullPath = path.join(cwd, file.path);
|
|
119
|
-
if (!force && await fs.pathExists(fullPath)) {
|
|
120
|
-
skipped.push(file.path);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
await fs.ensureDir(path.dirname(fullPath));
|
|
124
|
-
await fs.writeFile(fullPath, file.content);
|
|
125
|
-
if (file.executable) {
|
|
126
|
-
await fs.chmod(fullPath, 0o755);
|
|
127
|
-
}
|
|
128
|
-
written.push(file.path);
|
|
129
|
-
}
|
|
130
|
-
const parts = [];
|
|
131
|
-
if (written.length > 0)
|
|
132
|
-
parts.push(`✓ Created: ${written.join(', ')}`);
|
|
133
|
-
if (skipped.length > 0)
|
|
134
|
-
parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
|
|
117
|
+
const writeResult = await writeHookFiles(cwd, allFiles, force);
|
|
118
|
+
const parts = formatHookWriteResult(writeResult);
|
|
135
119
|
parts.push(`Tool: ${tool}`);
|
|
136
120
|
parts.push('Checks: file-size, security-patterns, hallucinated-imports, command-injection');
|
|
137
121
|
if (dlp) {
|
|
138
122
|
parts.push('');
|
|
139
|
-
parts.push('
|
|
140
|
-
parts.push('
|
|
123
|
+
parts.push('⚠ DLP warnings: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
|
|
124
|
+
parts.push('Possible credentials will be reported before agent actions.');
|
|
141
125
|
}
|
|
142
126
|
return {
|
|
143
127
|
content: [{ type: "text", text: parts.join('\n') }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/mcp",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.1",
|
|
4
4
|
"description": "MCP server + live dashboard for AI code governance — OWASP LLM Top 10 (10/10), real-time MCP App UI, 25+ security patterns, Bayesian learning Brain, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, VS Code, ChatGPT, Goose, Windsurf. Industry presets for HIPAA, SOC2, FedRAMP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://rigour.run",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"execa": "^8.0.1",
|
|
63
63
|
"fs-extra": "^11.2.0",
|
|
64
64
|
"yaml": "^2.8.2",
|
|
65
|
-
"@rigour-labs/core": "5.
|
|
65
|
+
"@rigour-labs/core": "5.5.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^25.0.3",
|