@verglos/mcp 0.7.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/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +167 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/check-before-write.d.ts +36 -0
- package/dist/tools/check-before-write.d.ts.map +1 -0
- package/dist/tools/check-before-write.js +102 -0
- package/dist/tools/check-before-write.js.map +1 -0
- package/dist/tools/check-package.d.ts +34 -0
- package/dist/tools/check-package.d.ts.map +1 -0
- package/dist/tools/check-package.js +204 -0
- package/dist/tools/check-package.js.map +1 -0
- package/dist/tools/explain-finding.d.ts +18 -0
- package/dist/tools/explain-finding.d.ts.map +1 -0
- package/dist/tools/explain-finding.js +17 -0
- package/dist/tools/explain-finding.js.map +1 -0
- package/dist/tools/scan.d.ts +33 -0
- package/dist/tools/scan.d.ts.map +1 -0
- package/dist/tools/scan.js +56 -0
- package/dist/tools/scan.js.map +1 -0
- package/package.json +37 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @verglos/mcp — Model Context Protocol server for Verglos.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the free-tier scanner surface to AI agents (Claude Code,
|
|
5
|
+
* Cursor, Cline, Windsurf, …) as MCP tools:
|
|
6
|
+
*
|
|
7
|
+
* verglos_check_before_write the killer, <300ms, runs in the
|
|
8
|
+
* agent's write loop
|
|
9
|
+
* verglos_check_package slopsquat / typosquat / CVE check
|
|
10
|
+
* before an install
|
|
11
|
+
* verglos_scan full project scan
|
|
12
|
+
* verglos_explain_finding why + fix, framework-aware
|
|
13
|
+
*
|
|
14
|
+
* All tools are free forever per architecture spec §7. No network
|
|
15
|
+
* calls on the free path except registry lookups (which run against
|
|
16
|
+
* the public npm registry, not Verglos infra).
|
|
17
|
+
*/
|
|
18
|
+
export { createVerglosMcpServer, startStdioServer } from "./server.js";
|
|
19
|
+
export type { CheckBeforeWriteInput, CheckBeforeWriteResult } from "./server.js";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @verglos/mcp — Model Context Protocol server for Verglos.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the free-tier scanner surface to AI agents (Claude Code,
|
|
5
|
+
* Cursor, Cline, Windsurf, …) as MCP tools:
|
|
6
|
+
*
|
|
7
|
+
* verglos_check_before_write the killer, <300ms, runs in the
|
|
8
|
+
* agent's write loop
|
|
9
|
+
* verglos_check_package slopsquat / typosquat / CVE check
|
|
10
|
+
* before an install
|
|
11
|
+
* verglos_scan full project scan
|
|
12
|
+
* verglos_explain_finding why + fix, framework-aware
|
|
13
|
+
*
|
|
14
|
+
* All tools are free forever per architecture spec §7. No network
|
|
15
|
+
* calls on the free path except registry lookups (which run against
|
|
16
|
+
* the public npm registry, not Verglos infra).
|
|
17
|
+
*/
|
|
18
|
+
export { createVerglosMcpServer, startStdioServer } from "./server.js";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import type { CheckBeforeWriteInput as ToolInput, CheckBeforeWriteResult as ToolResult } from "./tools/check-before-write.js";
|
|
3
|
+
/**
|
|
4
|
+
* MCP server for Verglos.
|
|
5
|
+
*
|
|
6
|
+
* All tool handlers are stubs in W6-1 — they respond with a shape
|
|
7
|
+
* that describes the tool but doesn't execute yet. Real handlers
|
|
8
|
+
* land in W6-2 (check_before_write), W6-3 (check_package),
|
|
9
|
+
* W6-4 (scan), W6-5 (explain_finding).
|
|
10
|
+
*
|
|
11
|
+
* Design constraints from architecture spec §7:
|
|
12
|
+
* - Speaks JSON-RPC over stdio
|
|
13
|
+
* - Startup banner goes to stderr; stdout is reserved for MCP transport
|
|
14
|
+
* - Zero extra install (bundled in the CLI)
|
|
15
|
+
* - Free-tier only: no license check, no network on the hot path
|
|
16
|
+
*/
|
|
17
|
+
export type CheckBeforeWriteInput = ToolInput;
|
|
18
|
+
export type CheckBeforeWriteResult = ToolResult;
|
|
19
|
+
export declare function createVerglosMcpServer(): Server;
|
|
20
|
+
/**
|
|
21
|
+
* Start an MCP server over stdio. Prints a startup banner to stderr
|
|
22
|
+
* so stdout stays clean for the MCP transport (arch spec §7).
|
|
23
|
+
*/
|
|
24
|
+
export declare function startStdioServer(): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAQnE,OAAO,KAAK,EACV,qBAAqB,IAAI,SAAS,EAClC,sBAAsB,IAAI,UAAU,EACrC,MAAM,+BAA+B,CAAC;AAKvC;;;;;;;;;;;;;GAaG;AAIH,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAC9C,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC;AAsJhD,wBAAgB,sBAAsB,IAAI,MAAM,CA8B/C;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKtD"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { checkBeforeWrite } from "./tools/check-before-write.js";
|
|
5
|
+
import { checkPackage } from "./tools/check-package.js";
|
|
6
|
+
import { scanProject } from "./tools/scan.js";
|
|
7
|
+
import { explainFinding } from "./tools/explain-finding.js";
|
|
8
|
+
// ─── Tool registration ────────────────────────────────────────────────────
|
|
9
|
+
const TOOLS = [
|
|
10
|
+
{
|
|
11
|
+
name: "verglos_check_before_write",
|
|
12
|
+
description: "The killer tool. Agent submits code it's about to write; Verglos returns allow/warn/block and (when possible) a corrected version. Runs only AI-* rules + secret patterns + high-confidence injection checks. <300ms, no network, free forever.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
code: { type: "string", description: "The code the agent is about to write." },
|
|
17
|
+
targetPath: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Target file path (relative or absolute). Verglos uses the extension for language inference.",
|
|
20
|
+
},
|
|
21
|
+
language: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Optional language hint (e.g. 'ts', 'tsx').",
|
|
24
|
+
},
|
|
25
|
+
context: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Optional freeform description of what the code is for.",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["code", "targetPath"],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "verglos_check_package",
|
|
35
|
+
description: "Before `npm install`. Checks whether a package exists on npm (AI-005 slopsquat), whether it looks like a typo of a top-N package (AI-006), and whether it has known CVEs.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
packageName: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "The npm package name (e.g. 'reqeusts' or '@stripee/js').",
|
|
42
|
+
},
|
|
43
|
+
version: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Optional version to check for CVEs. Defaults to 'latest'.",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ["packageName"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "verglos_scan",
|
|
53
|
+
description: "Full project scan. Returns findings, score, and AI-provenance summary. Slower than check_before_write — use for pre-PR review, not per-line checks.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
projectRoot: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Absolute path to the project root. Defaults to cwd.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "verglos_explain_finding",
|
|
66
|
+
description: "Explain a Verglos rule id (e.g. 'AI-002') — why it matters, how to fix, and an example when available. Same explain-bank the CLI uses.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
rule: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Rule id: 'AI-002', 'D4-001', etc.",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
required: ["rule"],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
// ─── Handler dispatch ─────────────────────────────────────────────────────
|
|
80
|
+
async function stubResponse(name) {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `verglos:mcp: ${name} is registered but the handler ships in a later commit. Try again after the next release.`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function jsonResponse(payload) {
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function dispatchTool(name, args) {
|
|
96
|
+
const input = args ?? {};
|
|
97
|
+
switch (name) {
|
|
98
|
+
case "verglos_check_before_write": {
|
|
99
|
+
const parsed = {
|
|
100
|
+
code: String(input.code ?? ""),
|
|
101
|
+
targetPath: String(input.targetPath ?? "input.ts"),
|
|
102
|
+
language: typeof input.language === "string" ? input.language : undefined,
|
|
103
|
+
context: typeof input.context === "string" ? input.context : undefined,
|
|
104
|
+
};
|
|
105
|
+
const result = await checkBeforeWrite(parsed);
|
|
106
|
+
return jsonResponse(result);
|
|
107
|
+
}
|
|
108
|
+
case "verglos_check_package": {
|
|
109
|
+
const result = await checkPackage({
|
|
110
|
+
packageName: String(input.packageName ?? ""),
|
|
111
|
+
version: typeof input.version === "string" ? input.version : undefined,
|
|
112
|
+
});
|
|
113
|
+
return jsonResponse(result);
|
|
114
|
+
}
|
|
115
|
+
case "verglos_scan": {
|
|
116
|
+
const result = await scanProject({
|
|
117
|
+
projectRoot: typeof input.projectRoot === "string" ? input.projectRoot : undefined,
|
|
118
|
+
limit: typeof input.limit === "number" ? input.limit : undefined,
|
|
119
|
+
noProvenance: typeof input.noProvenance === "boolean"
|
|
120
|
+
? input.noProvenance
|
|
121
|
+
: undefined,
|
|
122
|
+
});
|
|
123
|
+
return jsonResponse(result);
|
|
124
|
+
}
|
|
125
|
+
case "verglos_explain_finding": {
|
|
126
|
+
const result = explainFinding({ rule: String(input.rule ?? "") });
|
|
127
|
+
return jsonResponse(result);
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
return stubResponse(name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ─── Server factory ───────────────────────────────────────────────────────
|
|
134
|
+
export function createVerglosMcpServer() {
|
|
135
|
+
const server = new Server({
|
|
136
|
+
name: "verglos",
|
|
137
|
+
version: "0.6.0",
|
|
138
|
+
}, {
|
|
139
|
+
capabilities: {
|
|
140
|
+
tools: {},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
144
|
+
tools: TOOLS.map((t) => ({
|
|
145
|
+
name: t.name,
|
|
146
|
+
description: t.description,
|
|
147
|
+
inputSchema: t.inputSchema,
|
|
148
|
+
})),
|
|
149
|
+
}));
|
|
150
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
151
|
+
const name = request.params.name;
|
|
152
|
+
const args = request.params.arguments;
|
|
153
|
+
return dispatchTool(name, args);
|
|
154
|
+
});
|
|
155
|
+
return server;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Start an MCP server over stdio. Prints a startup banner to stderr
|
|
159
|
+
* so stdout stays clean for the MCP transport (arch spec §7).
|
|
160
|
+
*/
|
|
161
|
+
export async function startStdioServer() {
|
|
162
|
+
const server = createVerglosMcpServer();
|
|
163
|
+
const transport = new StdioServerTransport();
|
|
164
|
+
process.stderr.write("verglos:mcp: server started on stdio\n");
|
|
165
|
+
await server.connect(transport);
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Finding } from "@verglos/shared";
|
|
2
|
+
/**
|
|
3
|
+
* check_before_write — the killer MCP tool.
|
|
4
|
+
*
|
|
5
|
+
* Runs a narrow subset of Verglos detectors on a single piece of
|
|
6
|
+
* code the agent is about to write:
|
|
7
|
+
*
|
|
8
|
+
* secrets Any pattern-matched credential in the snippet.
|
|
9
|
+
* injection High-confidence SQL/command/XSS/NoSQL/path/SSRF.
|
|
10
|
+
* ai-patterns AI-001..010 — the wedge. All the flagship rules.
|
|
11
|
+
*
|
|
12
|
+
* Skipped in this hot path (spec §7):
|
|
13
|
+
* - dependencies (needs a lockfile, network to OSV)
|
|
14
|
+
* - misconfig (needs project-wide context — .env, headers)
|
|
15
|
+
* - git-history (needs a git repo)
|
|
16
|
+
* - slopsquat (needs package.json + network)
|
|
17
|
+
* - provenance (needs git log + repo scan)
|
|
18
|
+
*
|
|
19
|
+
* Target budget: <300ms per architecture spec §9. On a fresh temp
|
|
20
|
+
* dir + one file the sync detectors run in ~15-40ms; total round-
|
|
21
|
+
* trip is dominated by the mkdtemp/rm calls (~5ms each on tmpfs).
|
|
22
|
+
*/
|
|
23
|
+
export interface CheckBeforeWriteInput {
|
|
24
|
+
code: string;
|
|
25
|
+
targetPath: string;
|
|
26
|
+
language?: string;
|
|
27
|
+
context?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CheckBeforeWriteResult {
|
|
30
|
+
verdict: "allow" | "warn" | "block";
|
|
31
|
+
findings: Finding[];
|
|
32
|
+
correctedCode?: string;
|
|
33
|
+
reasoning: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function checkBeforeWrite(input: CheckBeforeWriteInput): Promise<CheckBeforeWriteResult>;
|
|
36
|
+
//# sourceMappingURL=check-before-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-before-write.d.ts","sourceRoot":"","sources":["../../src/tools/check-before-write.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IACpC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoFD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CA8CjC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { aiPatternsDetector, injectionDetector, secretsDetector, } from "@verglos/scanner";
|
|
5
|
+
/**
|
|
6
|
+
* Try to auto-correct known AI-* patterns. Currently only handles
|
|
7
|
+
* AI-002 (Math.random / Date.now → token/OTP). Others land in a
|
|
8
|
+
* follow-up commit — spec §7's "correctedCode is best-effort".
|
|
9
|
+
*/
|
|
10
|
+
function tryCorrectCode(original, findings) {
|
|
11
|
+
const ai002 = findings.find((f) => f.rule === "AI-002");
|
|
12
|
+
if (!ai002)
|
|
13
|
+
return undefined;
|
|
14
|
+
// Two common shapes we can rewrite deterministically:
|
|
15
|
+
// const otp = Math.floor(100000 + Math.random() * 900000);
|
|
16
|
+
// const token = Math.random().toString(36).slice(2);
|
|
17
|
+
let corrected = original;
|
|
18
|
+
// 6-digit OTP shape
|
|
19
|
+
corrected = corrected.replace(/Math\.floor\s*\(\s*(\d+)\s*\+\s*Math\.random\s*\(\s*\)\s*\*\s*(\d+)\s*\)/g, (_, start, range) => {
|
|
20
|
+
const s = Number(start);
|
|
21
|
+
const r = Number(range);
|
|
22
|
+
return `crypto.randomInt(${s}, ${s + r})`;
|
|
23
|
+
});
|
|
24
|
+
// Direct token generation shape
|
|
25
|
+
corrected = corrected.replace(/Math\.random\s*\(\s*\)\s*\.\s*toString\s*\(\s*36\s*\)\s*\.\s*(slice|substring|substr)\s*\(\s*2\s*\)/g, "crypto.randomBytes(32).toString('base64url')");
|
|
26
|
+
// Plain `Math.random()` in an obvious token/OTP context — inject a comment
|
|
27
|
+
// pointing at the safer API rather than rewriting silently, since we
|
|
28
|
+
// don't know the desired range.
|
|
29
|
+
if (corrected === original) {
|
|
30
|
+
corrected = original.replace(/Math\.random\s*\(\s*\)/g, "/* verglos AI-002: replace with crypto.randomInt / crypto.randomBytes */ Math.random()");
|
|
31
|
+
}
|
|
32
|
+
// If the transform added a `crypto.` call and the source doesn't already
|
|
33
|
+
// import node:crypto, prepend a hint import at the top.
|
|
34
|
+
if (/\bcrypto\.(randomInt|randomBytes)\b/.test(corrected) &&
|
|
35
|
+
!/import\s+\*?\s*(?:crypto|\{[^}]*crypto[^}]*\})\s*from\s*['"]node:crypto['"]/.test(corrected) &&
|
|
36
|
+
!/require\s*\(\s*['"]node:crypto['"]\s*\)/.test(corrected)) {
|
|
37
|
+
corrected = `import * as crypto from "node:crypto";\n${corrected}`;
|
|
38
|
+
}
|
|
39
|
+
return corrected === original ? undefined : corrected;
|
|
40
|
+
}
|
|
41
|
+
function summarize(finding) {
|
|
42
|
+
const rule = finding.rule ? `[${finding.rule}] ` : "";
|
|
43
|
+
return `${rule}${finding.title}`;
|
|
44
|
+
}
|
|
45
|
+
function buildReasoning(verdict, findings) {
|
|
46
|
+
if (verdict === "allow") {
|
|
47
|
+
return "No fast-path detector flagged this snippet. Verglos has not run the slower full-project checks (deps, git-history, provenance) — those come from `verglos_scan`.";
|
|
48
|
+
}
|
|
49
|
+
const worst = findings
|
|
50
|
+
.filter((f) => f.severity === "critical" || f.severity === "high")
|
|
51
|
+
.slice(0, 5)
|
|
52
|
+
.map((f) => ` - ${summarize(f)}`)
|
|
53
|
+
.join("\n");
|
|
54
|
+
const bypassHint = verdict === "block"
|
|
55
|
+
? "Do not write this code as-is. Suggest the corrected version if provided, or ask the human to override."
|
|
56
|
+
: "This code will land but the flagged concern should be addressed before shipping.";
|
|
57
|
+
return `Verdict: ${verdict.toUpperCase()}\n${bypassHint}\n\nFindings:\n${worst}`;
|
|
58
|
+
}
|
|
59
|
+
export async function checkBeforeWrite(input) {
|
|
60
|
+
const tmpRoot = await mkdtemp(join(tmpdir(), "verglos-cbw-"));
|
|
61
|
+
try {
|
|
62
|
+
// Preserve extension so language-aware detectors (AI-* code-shape
|
|
63
|
+
// checks, injection regex) pick the right rule set.
|
|
64
|
+
const safeName = basename(input.targetPath) || "input.ts";
|
|
65
|
+
const abs = join(tmpRoot, safeName);
|
|
66
|
+
await writeFile(abs, input.code, "utf8");
|
|
67
|
+
const scannedFile = {
|
|
68
|
+
path: abs,
|
|
69
|
+
relativePath: safeName,
|
|
70
|
+
};
|
|
71
|
+
const detectorResults = await Promise.all([
|
|
72
|
+
secretsDetector.run([scannedFile], tmpRoot),
|
|
73
|
+
injectionDetector.run([scannedFile], tmpRoot),
|
|
74
|
+
aiPatternsDetector.run([scannedFile], tmpRoot),
|
|
75
|
+
]);
|
|
76
|
+
const findings = detectorResults.flat();
|
|
77
|
+
// Only certain/high-confidence findings drive the verdict per
|
|
78
|
+
// arch spec §3 (medium/low sit in "worth a look").
|
|
79
|
+
const scoreable = findings.filter((f) => {
|
|
80
|
+
const conf = typeof f.confidence === "string"
|
|
81
|
+
? f.confidence
|
|
82
|
+
: f.confidence >= 0.9
|
|
83
|
+
? "certain"
|
|
84
|
+
: f.confidence >= 0.7
|
|
85
|
+
? "high"
|
|
86
|
+
: "medium";
|
|
87
|
+
return conf === "certain" || conf === "high";
|
|
88
|
+
});
|
|
89
|
+
let verdict = "allow";
|
|
90
|
+
if (scoreable.some((f) => f.severity === "critical"))
|
|
91
|
+
verdict = "block";
|
|
92
|
+
else if (scoreable.some((f) => f.severity === "high"))
|
|
93
|
+
verdict = "warn";
|
|
94
|
+
const correctedCode = tryCorrectCode(input.code, findings);
|
|
95
|
+
const reasoning = buildReasoning(verdict, findings);
|
|
96
|
+
return { verdict, findings, correctedCode, reasoning };
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await rm(tmpRoot, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=check-before-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-before-write.js","sourceRoot":"","sources":["../../src/tools/check-before-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check_package — pre-install verdict for a single npm package.
|
|
3
|
+
*
|
|
4
|
+
* Answers three questions before an agent runs `npm install`:
|
|
5
|
+
* 1. Does this package exist? (AI-005 slopsquat)
|
|
6
|
+
* 2. Does it look like a typo of a top-N package? (AI-006)
|
|
7
|
+
* 3. Does the requested version have known CVEs? (D6-001, OSV)
|
|
8
|
+
*
|
|
9
|
+
* Each question is a lightweight network call. No temp files, no
|
|
10
|
+
* detector orchestration — this is a straight lookup.
|
|
11
|
+
*/
|
|
12
|
+
export interface CheckPackageInput {
|
|
13
|
+
packageName: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
}
|
|
16
|
+
export type CheckPackageVerdict = "safe" | "warn" | "block";
|
|
17
|
+
export interface CheckPackageResult {
|
|
18
|
+
verdict: CheckPackageVerdict;
|
|
19
|
+
packageName: string;
|
|
20
|
+
version: string;
|
|
21
|
+
exists: boolean;
|
|
22
|
+
typosquat?: {
|
|
23
|
+
top: string;
|
|
24
|
+
distance: number;
|
|
25
|
+
};
|
|
26
|
+
cves: {
|
|
27
|
+
id: string;
|
|
28
|
+
severity: string;
|
|
29
|
+
summary?: string;
|
|
30
|
+
}[];
|
|
31
|
+
reasoning: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function checkPackage(input: CheckPackageInput): Promise<CheckPackageResult>;
|
|
34
|
+
//# sourceMappingURL=check-package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-package.d.ts","sourceRoot":"","sources":["../../src/tools/check-package.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA2CH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB;AAkGD,wBAAsB,YAAY,CAChC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAwF7B"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check_package — pre-install verdict for a single npm package.
|
|
3
|
+
*
|
|
4
|
+
* Answers three questions before an agent runs `npm install`:
|
|
5
|
+
* 1. Does this package exist? (AI-005 slopsquat)
|
|
6
|
+
* 2. Does it look like a typo of a top-N package? (AI-006)
|
|
7
|
+
* 3. Does the requested version have known CVEs? (D6-001, OSV)
|
|
8
|
+
*
|
|
9
|
+
* Each question is a lightweight network call. No temp files, no
|
|
10
|
+
* detector orchestration — this is a straight lookup.
|
|
11
|
+
*/
|
|
12
|
+
const NPM_REGISTRY = "https://registry.npmjs.org";
|
|
13
|
+
const OSV_URL = "https://api.osv.dev/v1/query";
|
|
14
|
+
const HTTP_TIMEOUT_MS = 4000;
|
|
15
|
+
/**
|
|
16
|
+
* Small top-N list — same shape as slopsquat.ts. Kept private to
|
|
17
|
+
* the MCP hot path so the tool can answer without loading the full
|
|
18
|
+
* scanner list. Refresh occasionally.
|
|
19
|
+
*/
|
|
20
|
+
const TOP_NPM_PACKAGES = [
|
|
21
|
+
"react", "react-dom", "next", "vue", "svelte", "solid-js",
|
|
22
|
+
"express", "fastify", "koa", "nestjs", "hono",
|
|
23
|
+
"lodash", "underscore", "ramda", "immer",
|
|
24
|
+
"axios", "node-fetch", "got", "ky",
|
|
25
|
+
"moment", "dayjs", "date-fns", "luxon",
|
|
26
|
+
"typescript", "eslint", "prettier", "vite", "webpack", "rollup", "esbuild",
|
|
27
|
+
"vitest", "jest", "mocha", "playwright", "cypress",
|
|
28
|
+
"chalk", "commander", "yargs", "inquirer", "ora",
|
|
29
|
+
"chokidar", "fs-extra", "fast-glob", "globby",
|
|
30
|
+
"dotenv",
|
|
31
|
+
"zod", "yup", "joi", "ajv", "valibot",
|
|
32
|
+
"prisma", "@prisma/client", "drizzle-orm", "sequelize", "typeorm", "mongoose", "kysely",
|
|
33
|
+
"pg", "mysql2", "sqlite3", "better-sqlite3", "postgres",
|
|
34
|
+
"redis", "ioredis",
|
|
35
|
+
"socket.io", "ws",
|
|
36
|
+
"graphql", "@apollo/client", "@tanstack/react-query", "swr",
|
|
37
|
+
"next-auth", "@clerk/nextjs", "passport", "jsonwebtoken", "jose",
|
|
38
|
+
"bcrypt", "bcryptjs", "argon2",
|
|
39
|
+
"stripe",
|
|
40
|
+
"@aws-sdk/client-s3",
|
|
41
|
+
"@sentry/nextjs", "@sentry/node",
|
|
42
|
+
"openai", "@anthropic-ai/sdk", "ai",
|
|
43
|
+
"tsx", "ts-node", "tsup",
|
|
44
|
+
"react-router", "@tanstack/react-router",
|
|
45
|
+
"tailwindcss", "postcss",
|
|
46
|
+
"clsx", "tailwind-merge",
|
|
47
|
+
"uuid", "nanoid",
|
|
48
|
+
"pino", "winston",
|
|
49
|
+
];
|
|
50
|
+
const TYPOSQUAT_MAX_DISTANCE = 2;
|
|
51
|
+
async function withTimeout(p, ms) {
|
|
52
|
+
return Promise.race([
|
|
53
|
+
p,
|
|
54
|
+
new Promise((resolve) => setTimeout(() => resolve(null), ms)),
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
async function packageExists(name) {
|
|
58
|
+
const res = await withTimeout(fetch(`${NPM_REGISTRY}/${encodeURIComponent(name)}`, { method: "HEAD" }), HTTP_TIMEOUT_MS);
|
|
59
|
+
if (!res)
|
|
60
|
+
return null;
|
|
61
|
+
if (res.status === 200)
|
|
62
|
+
return true;
|
|
63
|
+
if (res.status === 404)
|
|
64
|
+
return false;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function levenshtein(a, b) {
|
|
68
|
+
if (a === b)
|
|
69
|
+
return 0;
|
|
70
|
+
if (!a.length)
|
|
71
|
+
return b.length;
|
|
72
|
+
if (!b.length)
|
|
73
|
+
return a.length;
|
|
74
|
+
const prev = new Array(b.length + 1);
|
|
75
|
+
const curr = new Array(b.length + 1);
|
|
76
|
+
for (let j = 0; j <= b.length; j++)
|
|
77
|
+
prev[j] = j;
|
|
78
|
+
for (let i = 1; i <= a.length; i++) {
|
|
79
|
+
curr[0] = i;
|
|
80
|
+
for (let j = 1; j <= b.length; j++) {
|
|
81
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
82
|
+
curr[j] = Math.min((curr[j - 1] ?? 0) + 1, (prev[j] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
83
|
+
}
|
|
84
|
+
for (let j = 0; j <= b.length; j++)
|
|
85
|
+
prev[j] = curr[j] ?? 0;
|
|
86
|
+
}
|
|
87
|
+
return prev[b.length] ?? 0;
|
|
88
|
+
}
|
|
89
|
+
function findTyposquat(name) {
|
|
90
|
+
const lower = name.toLowerCase();
|
|
91
|
+
let best = null;
|
|
92
|
+
for (const top of TOP_NPM_PACKAGES) {
|
|
93
|
+
const d = levenshtein(lower, top.toLowerCase());
|
|
94
|
+
if (d === 0)
|
|
95
|
+
return null;
|
|
96
|
+
if (d > TYPOSQUAT_MAX_DISTANCE)
|
|
97
|
+
continue;
|
|
98
|
+
if (!best || d < best.distance)
|
|
99
|
+
best = { top, distance: d };
|
|
100
|
+
}
|
|
101
|
+
return best;
|
|
102
|
+
}
|
|
103
|
+
async function queryOsv(name, version) {
|
|
104
|
+
try {
|
|
105
|
+
const res = await withTimeout(fetch(OSV_URL, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
package: { name, ecosystem: "npm" },
|
|
110
|
+
version,
|
|
111
|
+
}),
|
|
112
|
+
}), HTTP_TIMEOUT_MS);
|
|
113
|
+
if (!res || !res.ok)
|
|
114
|
+
return [];
|
|
115
|
+
const data = (await res.json());
|
|
116
|
+
return data.vulns ?? [];
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function resolveLatest(name) {
|
|
123
|
+
const res = await withTimeout(fetch(`${NPM_REGISTRY}/${encodeURIComponent(name)}/latest`), HTTP_TIMEOUT_MS);
|
|
124
|
+
if (!res || !res.ok)
|
|
125
|
+
return null;
|
|
126
|
+
try {
|
|
127
|
+
const data = (await res.json());
|
|
128
|
+
return data.version ?? null;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export async function checkPackage(input) {
|
|
135
|
+
const packageName = input.packageName.trim();
|
|
136
|
+
const exists = await packageExists(packageName);
|
|
137
|
+
if (exists === false) {
|
|
138
|
+
// AI-005 — package doesn't exist. Block.
|
|
139
|
+
return {
|
|
140
|
+
verdict: "block",
|
|
141
|
+
packageName,
|
|
142
|
+
version: input.version ?? "unknown",
|
|
143
|
+
exists: false,
|
|
144
|
+
cves: [],
|
|
145
|
+
reasoning: `\`${packageName}\` does not exist on the public npm registry. This is the AI-005 slopsquat failure mode — either the install will fail, or worse, an attacker has registered the exact hallucinated name and is now shipping malware to anyone who runs \`npm install\`. Do not install.`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (exists === null) {
|
|
149
|
+
// Network flaky — respond with a soft warn so the agent can proceed at its own risk.
|
|
150
|
+
return {
|
|
151
|
+
verdict: "warn",
|
|
152
|
+
packageName,
|
|
153
|
+
version: input.version ?? "unknown",
|
|
154
|
+
exists: false,
|
|
155
|
+
cves: [],
|
|
156
|
+
reasoning: `Could not reach registry.npmjs.org to verify \`${packageName}\`. Retry later, or check manually before installing.`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Resolve version if not given (we need it for OSV).
|
|
160
|
+
const version = input.version && input.version !== "latest"
|
|
161
|
+
? input.version
|
|
162
|
+
: ((await resolveLatest(packageName)) ?? "latest");
|
|
163
|
+
const typosquat = findTyposquat(packageName);
|
|
164
|
+
const cves = await queryOsv(packageName, version);
|
|
165
|
+
const cvesFmt = cves.map((v) => ({
|
|
166
|
+
id: v.id,
|
|
167
|
+
severity: v.database_specific?.severity ?? "UNKNOWN",
|
|
168
|
+
summary: v.summary,
|
|
169
|
+
}));
|
|
170
|
+
const criticals = cvesFmt.filter((v) => v.severity === "CRITICAL" || v.severity === "HIGH");
|
|
171
|
+
let verdict = "safe";
|
|
172
|
+
const notes = [];
|
|
173
|
+
if (criticals.length > 0) {
|
|
174
|
+
verdict = "block";
|
|
175
|
+
notes.push(`${criticals.length} critical/high CVE${criticals.length === 1 ? "" : "s"} for ${packageName}@${version}: ${criticals
|
|
176
|
+
.slice(0, 3)
|
|
177
|
+
.map((c) => c.id)
|
|
178
|
+
.join(", ")}${criticals.length > 3 ? "…" : ""}`);
|
|
179
|
+
}
|
|
180
|
+
else if (cvesFmt.length > 0) {
|
|
181
|
+
verdict = "warn";
|
|
182
|
+
notes.push(`${cvesFmt.length} known vulnerabilit${cvesFmt.length === 1 ? "y" : "ies"} in ${packageName}@${version}. Review before installing.`);
|
|
183
|
+
}
|
|
184
|
+
if (typosquat) {
|
|
185
|
+
// Typosquat elevates from safe → warn; if a CVE already put us at
|
|
186
|
+
// block or warn, we compound but don't downgrade.
|
|
187
|
+
if (verdict === "safe")
|
|
188
|
+
verdict = "warn";
|
|
189
|
+
notes.push(`\`${packageName}\` is Levenshtein distance ${typosquat.distance} from \`${typosquat.top}\`. If you meant \`${typosquat.top}\`, install that instead — the near-miss is exactly what squatters register.`);
|
|
190
|
+
}
|
|
191
|
+
if (notes.length === 0) {
|
|
192
|
+
notes.push(`\`${packageName}@${version}\` exists on npm and has no known CVEs recorded by OSV.dev.`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
verdict,
|
|
196
|
+
packageName,
|
|
197
|
+
version,
|
|
198
|
+
exists: true,
|
|
199
|
+
typosquat: typosquat ?? undefined,
|
|
200
|
+
cves: cvesFmt,
|
|
201
|
+
reasoning: notes.join("\n\n"),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=check-package.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-package.js","sourceRoot":"","sources":["../../src/tools/check-package.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ExplainEntry } from "@verglos/shared";
|
|
2
|
+
/**
|
|
3
|
+
* verglos_explain_finding MCP tool — same explain-bank the CLI
|
|
4
|
+
* (verglos explain <rule>) consumes. Returns the full rule entry
|
|
5
|
+
* as structured JSON so the agent can format it however it wants
|
|
6
|
+
* or hand the human a link to a docs URL.
|
|
7
|
+
*/
|
|
8
|
+
export interface ExplainFindingInput {
|
|
9
|
+
rule: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ExplainFindingResult {
|
|
12
|
+
found: boolean;
|
|
13
|
+
rule: string;
|
|
14
|
+
entry?: ExplainEntry;
|
|
15
|
+
message?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function explainFinding(input: ExplainFindingInput): ExplainFindingResult;
|
|
18
|
+
//# sourceMappingURL=explain-finding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain-finding.d.ts","sourceRoot":"","sources":["../../src/tools/explain-finding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;;GAKG;AAEH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAc/E"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { lookupRule } from "@verglos/shared";
|
|
2
|
+
export function explainFinding(input) {
|
|
3
|
+
const entry = lookupRule(input.rule);
|
|
4
|
+
if (!entry) {
|
|
5
|
+
return {
|
|
6
|
+
found: false,
|
|
7
|
+
rule: input.rule,
|
|
8
|
+
message: `Unknown rule id \`${input.rule}\`. Run \`verglos explain --list\` for every rule Verglos knows about.`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
found: true,
|
|
13
|
+
rule: entry.rule,
|
|
14
|
+
entry,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=explain-finding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain-finding.js","sourceRoot":"","sources":["../../src/tools/explain-finding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,iBAAiB,CAAC;AAoBhE,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,qBAAqB,KAAK,CAAC,IAAI,wEAAwE;SACjH,CAAC;IACJ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK;KACN,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Finding, RepoProvenance, ScanScore } from "@verglos/shared";
|
|
2
|
+
/**
|
|
3
|
+
* verglos_scan MCP tool — full project scan wrapped for agent use.
|
|
4
|
+
*
|
|
5
|
+
* Runs the same scan as `verglos scan` (all detectors, provenance,
|
|
6
|
+
* OSV, git history). Slower than check_before_write — use for
|
|
7
|
+
* pre-PR review or on-demand agent queries, not per-line checks.
|
|
8
|
+
*
|
|
9
|
+
* Returns a summarized shape (score + counts + provenance headline
|
|
10
|
+
* + top findings). The full findings list would blow up an MCP
|
|
11
|
+
* response for large repos; the agent can call this tool with
|
|
12
|
+
* `limit` set higher when it actually wants the full list.
|
|
13
|
+
*/
|
|
14
|
+
export interface ScanInput {
|
|
15
|
+
projectRoot?: string;
|
|
16
|
+
/** Cap on findings returned. Default 20; 0 = no cap. */
|
|
17
|
+
limit?: number;
|
|
18
|
+
/** Opt out of the provenance engine — same as `verglos scan --no-provenance`. */
|
|
19
|
+
noProvenance?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ScanResultSummary {
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
scannedAt: string;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
score: ScanScore;
|
|
26
|
+
provenance?: RepoProvenance;
|
|
27
|
+
findingCount: number;
|
|
28
|
+
findings: Finding[];
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
headline: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function scanProject(input: ScanInput): Promise<ScanResultSummary>;
|
|
33
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/tools/scan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE1E;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA2CD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,iBAAiB,CAAC,CA0B5B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { runScan } from "@verglos/scanner";
|
|
2
|
+
function severityRank(sev) {
|
|
3
|
+
switch (sev) {
|
|
4
|
+
case "critical":
|
|
5
|
+
return 4;
|
|
6
|
+
case "high":
|
|
7
|
+
return 3;
|
|
8
|
+
case "medium":
|
|
9
|
+
return 2;
|
|
10
|
+
case "low":
|
|
11
|
+
return 1;
|
|
12
|
+
default:
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function buildHeadline(score, provenance) {
|
|
17
|
+
const parts = [
|
|
18
|
+
`Score ${score.value}/100 — ${score.counts.critical} critical, ${score.counts.high} high, ${score.counts.medium} medium.`,
|
|
19
|
+
];
|
|
20
|
+
if (provenance && provenance.aiAuthoredPercent > 0) {
|
|
21
|
+
if (provenance.densityRatio !== null) {
|
|
22
|
+
parts.push(`${provenance.aiAuthoredPercent}% AI-authored; AI files carry ${provenance.densityRatio}× the finding density of human files.`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
parts.push(`${provenance.aiAuthoredPercent}% AI-authored (density ratio hidden — not enough LOC per bucket).`);
|
|
26
|
+
}
|
|
27
|
+
if (provenance.criticalsTotal > 0) {
|
|
28
|
+
parts.push(`${provenance.criticalsInAIFiles} of ${provenance.criticalsTotal} criticals live in AI-authored files.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return parts.join(" ");
|
|
32
|
+
}
|
|
33
|
+
export async function scanProject(input) {
|
|
34
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
35
|
+
const limit = input.limit ?? 20;
|
|
36
|
+
const result = await runScan({
|
|
37
|
+
projectRoot,
|
|
38
|
+
unlocked: true,
|
|
39
|
+
includeGitHistory: true,
|
|
40
|
+
noProvenance: input.noProvenance,
|
|
41
|
+
});
|
|
42
|
+
const sorted = [...result.findings].sort((a, b) => severityRank(b.severity) - severityRank(a.severity));
|
|
43
|
+
const capped = limit === 0 ? sorted : sorted.slice(0, limit);
|
|
44
|
+
return {
|
|
45
|
+
projectRoot: result.projectRoot,
|
|
46
|
+
scannedAt: result.scannedAt,
|
|
47
|
+
durationMs: result.durationMs,
|
|
48
|
+
score: result.score,
|
|
49
|
+
provenance: result.provenance,
|
|
50
|
+
findingCount: result.findings.length,
|
|
51
|
+
findings: capped,
|
|
52
|
+
truncated: capped.length < result.findings.length,
|
|
53
|
+
headline: buildHeadline(result.score, result.provenance),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/tools/scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAoC3C,SAAS,YAAY,CAAC,GAAwB;IAC5C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,UAAU;YACb,OAAO,CAAC,CAAC;QACX,KAAK,MAAM;YACT,OAAO,CAAC,CAAC;QACX,KAAK,QAAQ;YACX,OAAO,CAAC,CAAC;QACX,KAAK,KAAK;YACR,OAAO,CAAC,CAAC;QACX;YACE,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CACpB,KAAgB,EAChB,UAAsC;IAEtC,MAAM,KAAK,GAAG;QACZ,SAAS,KAAK,CAAC,KAAK,UAAU,KAAK,CAAC,MAAM,CAAC,QAAQ,cAAc,KAAK,CAAC,MAAM,CAAC,IAAI,UAAU,KAAK,CAAC,MAAM,CAAC,MAAM,UAAU;KAC1H,CAAC;IACF,IAAI,UAAU,IAAI,UAAU,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACnD,IAAI,UAAU,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CACR,GAAG,UAAU,CAAC,iBAAiB,iCAAiC,UAAU,CAAC,YAAY,uCAAuC,CAC/H,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CACR,GAAG,UAAU,CAAC,iBAAiB,mEAAmE,CACnG,CAAC;QACJ,CAAC;QACD,IAAI,UAAU,CAAC,cAAc,GAAG,CAAC,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CACR,GAAG,UAAU,CAAC,kBAAkB,OAAO,UAAU,CAAC,cAAc,uCAAuC,CACxG,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAgB;IAEhB,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACvD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;QAC3B,WAAW;QACX,QAAQ,EAAE,IAAI;QACd,iBAAiB,EAAE,IAAI;QACvB,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAC9D,CAAC;IACF,MAAM,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAE7D,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;QACpC,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM;QACjD,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC;KACzD,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@verglos/mcp",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Model Context Protocol server for Verglos — lets AI agents scan and get security verdicts before writing code.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"import": "./dist/server.js",
|
|
18
|
+
"types": "./dist/server.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
23
|
+
"@verglos/scanner": "0.7.0",
|
|
24
|
+
"@verglos/shared": "0.7.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.15.29",
|
|
28
|
+
"typescript": "^5.8.3"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
36
|
+
}
|
|
37
|
+
}
|