pkgxray 0.1.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.
@@ -0,0 +1,358 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { auditEvidence, renderMarkdown } = require("../src/auditor");
5
+ const { guardExtension } = require("../src/quarantine");
6
+ const { reasonAbout } = require("../src/reasoner");
7
+
8
+ const TOOL_NAME = "audit_agent_extension_supply_chain";
9
+ const GUARD_TOOL_NAME = "guard_agent_extension_install";
10
+ const REASON_TOOL_NAME = "reason_about_extension_supply_chain";
11
+ let buffer = "";
12
+
13
+ function send(message) {
14
+ process.stdout.write(`${JSON.stringify(message)}\n`);
15
+ }
16
+
17
+ function textContent(text) {
18
+ return [{ type: "text", text }];
19
+ }
20
+
21
+ function toolDefinition() {
22
+ return {
23
+ name: TOOL_NAME,
24
+ description:
25
+ "Audit evidence for an AI coding-agent extension, Codex plugin, Claude Code extension, or MCP server and return a conservative supply-chain security verdict.",
26
+ inputSchema: {
27
+ type: "object",
28
+ additionalProperties: false,
29
+ properties: {
30
+ packageName: { type: "string" },
31
+ npmMetadata: {},
32
+ githubMetadata: {},
33
+ webPresence: {},
34
+ sourceFiles: {
35
+ description:
36
+ "Map of file path to source text, or an array of objects with path/name and content/text.",
37
+ anyOf: [
38
+ { type: "object", additionalProperties: { type: "string" } },
39
+ {
40
+ type: "array",
41
+ items: {
42
+ type: "object",
43
+ additionalProperties: true,
44
+ properties: {
45
+ path: { type: "string" },
46
+ name: { type: "string" },
47
+ content: { type: "string" },
48
+ text: { type: "string" }
49
+ }
50
+ }
51
+ }
52
+ ]
53
+ },
54
+ outputFormat: {
55
+ type: "string",
56
+ enum: ["markdown", "json"],
57
+ default: "markdown"
58
+ }
59
+ },
60
+ required: ["sourceFiles"]
61
+ }
62
+ };
63
+ }
64
+
65
+ function guardToolDefinition() {
66
+ return {
67
+ name: GUARD_TOOL_NAME,
68
+ description:
69
+ "Stage an agent extension in a local quarantine directory, audit it without installing or running it, and optionally promote it if policy allows.",
70
+ inputSchema: {
71
+ type: "object",
72
+ additionalProperties: false,
73
+ properties: {
74
+ reference: {
75
+ type: "string",
76
+ description:
77
+ "Extension reference: npm package, npm:name@version, file:path, or local directory path."
78
+ },
79
+ quarantineRoot: {
80
+ type: "string",
81
+ description: "Optional quarantine root. Defaults to the OS temp directory."
82
+ },
83
+ promoteTo: {
84
+ type: "string",
85
+ description:
86
+ "Optional destination directory. The staged package is copied here only when policy allows."
87
+ },
88
+ policy: {
89
+ type: "string",
90
+ enum: ["safe-only", "allow-review"],
91
+ default: "safe-only"
92
+ },
93
+ force: {
94
+ type: "boolean",
95
+ default: false
96
+ },
97
+ sourceScan: {
98
+ type: "boolean",
99
+ default: true,
100
+ description: "Set false to stage and vulnerability-check without collecting source files."
101
+ },
102
+ vulnerabilityCheck: {
103
+ type: "boolean",
104
+ default: true,
105
+ description: "Set false to skip OSV vulnerability intelligence checks."
106
+ },
107
+ outputFormat: {
108
+ type: "string",
109
+ enum: ["markdown", "json"],
110
+ default: "markdown"
111
+ }
112
+ },
113
+ required: ["reference"]
114
+ }
115
+ };
116
+ }
117
+
118
+ function reasonToolDefinition() {
119
+ return {
120
+ name: REASON_TOOL_NAME,
121
+ description:
122
+ "Consult an LLM as an authoritative reasoning layer over supplied evidence. Supports Anthropic, OpenAI, and Gemini providers; defaults to Anthropic + claude-opus-4-7. Returns a structured JSON verdict (verdict, summary, findings, evidenceGaps, promotable). Requires the matching env key (ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY) and SDK installed.",
123
+ inputSchema: {
124
+ type: "object",
125
+ additionalProperties: false,
126
+ properties: {
127
+ packageName: { type: "string" },
128
+ npmMetadata: {},
129
+ githubMetadata: {},
130
+ webPresence: {},
131
+ sourceFiles: {
132
+ description:
133
+ "Map of file path to source text, or an array of objects with path/name and content/text.",
134
+ anyOf: [
135
+ { type: "object", additionalProperties: { type: "string" } },
136
+ {
137
+ type: "array",
138
+ items: {
139
+ type: "object",
140
+ additionalProperties: true,
141
+ properties: {
142
+ path: { type: "string" },
143
+ name: { type: "string" },
144
+ content: { type: "string" },
145
+ text: { type: "string" }
146
+ }
147
+ }
148
+ }
149
+ ]
150
+ },
151
+ provider: {
152
+ type: "string",
153
+ enum: ["anthropic", "openai", "gemini"],
154
+ description: "LLM provider. Defaults to anthropic, or auto-detected from model."
155
+ },
156
+ model: {
157
+ type: "string",
158
+ description:
159
+ "Model ID. Defaults per provider: anthropic=claude-opus-4-7, openai=gpt-5, gemini=gemini-2.5-pro."
160
+ },
161
+ maxFiles: {
162
+ type: "integer",
163
+ description: "Cap on source files sent to the model. Default 200."
164
+ }
165
+ },
166
+ required: ["sourceFiles"]
167
+ }
168
+ };
169
+ }
170
+
171
+ function handleRequest(request) {
172
+ const { id, method, params } = request;
173
+
174
+ if (method === "initialize") {
175
+ return {
176
+ jsonrpc: "2.0",
177
+ id,
178
+ result: {
179
+ protocolVersion: params && params.protocolVersion ? params.protocolVersion : "2024-11-05",
180
+ capabilities: { tools: {} },
181
+ serverInfo: {
182
+ name: "pkgxray",
183
+ version: "0.1.0"
184
+ }
185
+ }
186
+ };
187
+ }
188
+
189
+ if (method === "ping") {
190
+ return { jsonrpc: "2.0", id, result: {} };
191
+ }
192
+
193
+ if (method === "tools/list") {
194
+ return {
195
+ jsonrpc: "2.0",
196
+ id,
197
+ result: {
198
+ tools: [toolDefinition(), guardToolDefinition(), reasonToolDefinition()]
199
+ }
200
+ };
201
+ }
202
+
203
+ if (method === "tools/call") {
204
+ const name = params && params.name;
205
+ if (name !== TOOL_NAME && name !== GUARD_TOOL_NAME && name !== REASON_TOOL_NAME) {
206
+ return {
207
+ jsonrpc: "2.0",
208
+ id,
209
+ error: {
210
+ code: -32602,
211
+ message: `Unknown tool: ${name}`
212
+ }
213
+ };
214
+ }
215
+
216
+ const args = (params && params.arguments) || {};
217
+
218
+ if (name === REASON_TOOL_NAME) {
219
+ return reasonAbout(args, {
220
+ provider: args.provider,
221
+ model: args.model,
222
+ maxFiles: args.maxFiles
223
+ })
224
+ .then((reasoning) => ({
225
+ jsonrpc: "2.0",
226
+ id,
227
+ result: {
228
+ content: textContent(JSON.stringify(reasoning, null, 2)),
229
+ structuredContent: reasoning
230
+ }
231
+ }))
232
+ .catch((error) => ({
233
+ jsonrpc: "2.0",
234
+ id,
235
+ error: { code: -32603, message: `${error.code || "REASONER_ERROR"}: ${error.message}` }
236
+ }));
237
+ }
238
+
239
+ if (name === GUARD_TOOL_NAME) {
240
+ return guardExtension(args.reference, args).then((guardResult) => {
241
+ const text =
242
+ args.outputFormat === "json"
243
+ ? JSON.stringify(guardResult, null, 2)
244
+ : renderGuardMarkdown(guardResult);
245
+ return {
246
+ jsonrpc: "2.0",
247
+ id,
248
+ result: {
249
+ content: textContent(text),
250
+ structuredContent: guardResult
251
+ }
252
+ };
253
+ });
254
+ }
255
+
256
+ const report = auditEvidence(args);
257
+ const text =
258
+ args.outputFormat === "json"
259
+ ? JSON.stringify(report, null, 2)
260
+ : renderMarkdown(report);
261
+
262
+ return {
263
+ jsonrpc: "2.0",
264
+ id,
265
+ result: {
266
+ content: textContent(text),
267
+ structuredContent: report
268
+ }
269
+ };
270
+ }
271
+
272
+ if (method && method.startsWith("notifications/")) {
273
+ return null;
274
+ }
275
+
276
+ return {
277
+ jsonrpc: "2.0",
278
+ id,
279
+ error: {
280
+ code: -32601,
281
+ message: `Method not found: ${method}`
282
+ }
283
+ };
284
+ }
285
+
286
+ function renderGuardMarkdown(result) {
287
+ const lines = [
288
+ `Decision: **${result.decision.toUpperCase()}**`,
289
+ `Reference: \`${result.reference}\``,
290
+ `Quarantine: \`${result.quarantinePath}\``,
291
+ ""
292
+ ];
293
+
294
+ if (result.promotedPath) {
295
+ lines.push(`Promoted to: \`${result.promotedPath}\``, "");
296
+ }
297
+
298
+ lines.push(renderMarkdown(result.report));
299
+ return lines.join("\n");
300
+ }
301
+
302
+ function processLine(line) {
303
+ if (!line.trim()) {
304
+ return;
305
+ }
306
+ let request;
307
+ try {
308
+ request = JSON.parse(line);
309
+ } catch (error) {
310
+ send({
311
+ jsonrpc: "2.0",
312
+ id: null,
313
+ error: { code: -32700, message: `Parse error: ${error.message}` }
314
+ });
315
+ return;
316
+ }
317
+
318
+ try {
319
+ const response = handleRequest(request);
320
+ if (response && typeof response.then === "function") {
321
+ response.then(send).catch((error) => {
322
+ send({
323
+ jsonrpc: "2.0",
324
+ id: request.id || null,
325
+ error: { code: -32603, message: error.message }
326
+ });
327
+ });
328
+ return;
329
+ }
330
+ if (response) {
331
+ send(response);
332
+ }
333
+ } catch (error) {
334
+ send({
335
+ jsonrpc: "2.0",
336
+ id: request.id || null,
337
+ error: { code: -32603, message: error.message }
338
+ });
339
+ }
340
+ }
341
+
342
+ process.stdin.setEncoding("utf8");
343
+ process.stdin.on("data", (chunk) => {
344
+ buffer += chunk;
345
+ let newlineIndex = buffer.indexOf("\n");
346
+ while (newlineIndex !== -1) {
347
+ const line = buffer.slice(0, newlineIndex);
348
+ buffer = buffer.slice(newlineIndex + 1);
349
+ processLine(line);
350
+ newlineIndex = buffer.indexOf("\n");
351
+ }
352
+ });
353
+
354
+ process.stdin.on("end", () => {
355
+ if (buffer.trim()) {
356
+ processLine(buffer);
357
+ }
358
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "pkgxray",
3
+ "version": "0.1.0",
4
+ "description": "Local CLI and MCP server that audits AI-agent extensions and npm packages for supply-chain risk. Zero-dep static heuristics + sandboxed quarantine + optional multi-provider (Claude / GPT / Gemini) reasoning layer.",
5
+ "license": "MIT",
6
+ "author": "Jack Adams-Lovell",
7
+ "type": "commonjs",
8
+ "bin": {
9
+ "pkgxray": "./bin/audit.js",
10
+ "pkgxray-mcp": "./bin/mcp-server.js"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "src/",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build:browser": "node ./scripts/build-browser-extension.js",
20
+ "test": "node --test",
21
+ "audit:evidence": "node ./bin/audit.js",
22
+ "mcp": "node ./bin/mcp-server.js"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/adamsjack711-ux/pkgxray.git"
27
+ },
28
+ "homepage": "https://github.com/adamsjack711-ux/pkgxray#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/adamsjack711-ux/pkgxray/issues"
31
+ },
32
+ "keywords": [
33
+ "mcp",
34
+ "security",
35
+ "supply-chain",
36
+ "npm-audit",
37
+ "agent-extension",
38
+ "ai-safety",
39
+ "claude",
40
+ "openai",
41
+ "gemini"
42
+ ],
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "peerDependencies": {
47
+ "@anthropic-ai/sdk": ">=0.40.0",
48
+ "openai": ">=4.0.0",
49
+ "@google/generative-ai": ">=0.20.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "@anthropic-ai/sdk": { "optional": true },
53
+ "openai": { "optional": true },
54
+ "@google/generative-ai": { "optional": true }
55
+ },
56
+ "devDependencies": {
57
+ "@anthropic-ai/sdk": "^0.105.0"
58
+ }
59
+ }