aienvmp 0.1.8 → 0.1.10
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/commands/context.js +2 -1
- package/src/commands/handoff.js +2 -1
- package/src/render.js +11 -2
- package/src/security.js +67 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
- Added optional `pip-audit` JSON parsing for Python security summaries.
|
|
6
|
+
- Combined npm and Python vulnerable package summaries into the same AI-facing security context.
|
|
7
|
+
- Kept missing Python scanners non-blocking with explicit scanner availability metadata.
|
|
8
|
+
|
|
9
|
+
## 0.1.9
|
|
10
|
+
|
|
11
|
+
- Added top vulnerable package summaries for AI context, handoff, `AIENV.md`, and the dashboard.
|
|
12
|
+
- Ranked vulnerable packages by severity so agents can prioritize review without reading full audit output.
|
|
13
|
+
- Kept security package details bounded to preserve lightweight output.
|
|
14
|
+
|
|
3
15
|
## 0.1.8
|
|
4
16
|
|
|
5
17
|
- Added optional `sync --security` / `scan --security` vulnerability summary collection.
|
package/package.json
CHANGED
package/src/commands/context.js
CHANGED
|
@@ -42,7 +42,8 @@ function securitySummary(security = {}) {
|
|
|
42
42
|
return {
|
|
43
43
|
mode: security.mode || "basic",
|
|
44
44
|
enabled: security.enabled === true,
|
|
45
|
-
summary: security.summary || { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 }
|
|
45
|
+
summary: security.summary || { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 },
|
|
46
|
+
topPackages: security.topPackages || []
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
package/src/commands/handoff.js
CHANGED
|
@@ -79,7 +79,8 @@ function securitySummary(security = {}) {
|
|
|
79
79
|
return {
|
|
80
80
|
mode: security.mode || "basic",
|
|
81
81
|
enabled: security.enabled === true,
|
|
82
|
-
summary: security.summary || { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 }
|
|
82
|
+
summary: security.summary || { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 },
|
|
83
|
+
topPackages: security.topPackages || []
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
|
package/src/render.js
CHANGED
|
@@ -219,7 +219,8 @@ const inventoryCount=Object.values(inventoryGroups).reduce((sum,items)=>sum+(Arr
|
|
|
219
219
|
const inventoryHtml=manifest.inventory?.enabled?('<table>'+Object.entries(inventoryGroups).map(([k,v])=>\`<tr><th>\${esc(k)}</th><td><code>\${Array.isArray(v)?v.length:0} tools</code></td></tr>\`).join('')+'</table>'):'<div class="okline">Deep global inventory is off. Run <code>aienvmp sync --deep</code> when an AI needs global tool awareness.</div>';
|
|
220
220
|
const sec=manifest.security||{};
|
|
221
221
|
const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
|
|
222
|
-
const
|
|
222
|
+
const secPackages=sec.topPackages||[];
|
|
223
|
+
const securityHtml=sec.enabled?\`<table><tr><th>Total</th><td><code>\${esc(secSummary.total||0)}</code></td></tr><tr><th>Critical</th><td><code>\${esc(secSummary.critical||0)}</code></td></tr><tr><th>High</th><td><code>\${esc(secSummary.high||0)}</code></td></tr><tr><th>Moderate</th><td><code>\${esc(secSummary.moderate||0)}</code></td></tr><tr><th>Low</th><td><code>\${esc(secSummary.low||0)}</code></td></tr></table>\${secPackages.length?'<div class="timeline">'+secPackages.slice(0,5).map(p=>\`<div class="event"><time>\${esc(p.severity)}</time><div><b>\${esc(p.name)}</b> \${p.fixAvailable?'fix available':'review required'}</div></div>\`).join('')+'</div>':'<div class="okline" style="margin-top:10px">No vulnerable packages reported.</div>'}\`:'<div class="okline">Security scan is off. Run <code>aienvmp sync --security</code> for read-only vulnerability summary.</div>';
|
|
223
224
|
const change=c=>c.type==='changed'?\`\${c.scope} \${c.key}: \${c.before} -> \${c.after}\`:\`\${c.scope} \${c.key}: \${c.type} \${c.after||c.before}\`;
|
|
224
225
|
const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
|
|
225
226
|
const agentNames={agents:'Codex',claude:'Claude',gemini:'Gemini'};
|
|
@@ -337,7 +338,7 @@ function inventoryLines(inventory = {}) {
|
|
|
337
338
|
function securityLines(security = {}) {
|
|
338
339
|
if (!security.enabled) return ["- Mode: basic", "- Security scan is disabled. Run `aienvmp sync --security` when vulnerability context is needed."];
|
|
339
340
|
const summary = security.summary || {};
|
|
340
|
-
|
|
341
|
+
const lines = [
|
|
341
342
|
"- Mode: security",
|
|
342
343
|
`- Total vulnerabilities: ${summary.total || 0}`,
|
|
343
344
|
`- Critical: ${summary.critical || 0}`,
|
|
@@ -345,6 +346,14 @@ function securityLines(security = {}) {
|
|
|
345
346
|
`- Moderate: ${summary.moderate || 0}`,
|
|
346
347
|
`- Low: ${summary.low || 0}`
|
|
347
348
|
];
|
|
349
|
+
const packages = security.topPackages || [];
|
|
350
|
+
if (packages.length) {
|
|
351
|
+
lines.push("- Top vulnerable packages:");
|
|
352
|
+
for (const pkg of packages.slice(0, 8)) {
|
|
353
|
+
lines.push(` - ${pkg.name}: ${pkg.severity}; ${pkg.fixAvailable ? "fix available" : "review required"}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return lines;
|
|
348
357
|
}
|
|
349
358
|
|
|
350
359
|
function policyLines(policy) {
|
package/src/security.js
CHANGED
|
@@ -14,14 +14,15 @@ export async function scanSecurity(dir, options = {}) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const npmAudit = await scanNpmAudit(dir);
|
|
17
|
+
const pipAudit = await scanPipAudit(dir);
|
|
18
|
+
const scanners = { npmAudit, pipAudit };
|
|
17
19
|
return {
|
|
18
20
|
mode: "security",
|
|
19
21
|
enabled: true,
|
|
20
22
|
note: "Read-only vulnerability summary. Use for review and CI policy, not automatic fixes.",
|
|
21
|
-
scanners
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
summary: summarizeScanners({ npmAudit })
|
|
23
|
+
scanners,
|
|
24
|
+
summary: summarizeScanners(scanners),
|
|
25
|
+
topPackages: topVulnerablePackages(scanners)
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -49,6 +50,29 @@ export async function scanNpmAudit(dir) {
|
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
export async function scanPipAudit(dir) {
|
|
54
|
+
const hasPythonHints = await exists(path.join(dir, "pyproject.toml")) || await exists(path.join(dir, "requirements.txt"));
|
|
55
|
+
if (!hasPythonHints) return unavailable("pip-audit", "Python project hints not found");
|
|
56
|
+
|
|
57
|
+
const result = await commandResult("pip-audit", ["-f", "json"], {
|
|
58
|
+
cwd: dir,
|
|
59
|
+
timeout: 15000,
|
|
60
|
+
maxBuffer: 4 * 1024 * 1024
|
|
61
|
+
});
|
|
62
|
+
const parsed = parsePipAudit(result.stdout);
|
|
63
|
+
if (!parsed.available) {
|
|
64
|
+
return unavailable("pip-audit", result.stderr || "pip-audit did not return parseable JSON");
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
scanner: "pip-audit",
|
|
68
|
+
available: true,
|
|
69
|
+
ok: result.ok,
|
|
70
|
+
exitCode: result.code,
|
|
71
|
+
summary: parsed.summary,
|
|
72
|
+
vulnerablePackages: parsed.vulnerablePackages
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
52
76
|
export function parseNpmAudit(raw) {
|
|
53
77
|
try {
|
|
54
78
|
const parsed = JSON.parse(raw || "{}");
|
|
@@ -72,6 +96,32 @@ export function parseNpmAudit(raw) {
|
|
|
72
96
|
}
|
|
73
97
|
}
|
|
74
98
|
|
|
99
|
+
export function parsePipAudit(raw) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(raw || "{}");
|
|
102
|
+
const dependencies = Array.isArray(parsed.dependencies) ? parsed.dependencies : [];
|
|
103
|
+
const vulnerablePackages = dependencies
|
|
104
|
+
.filter((dependency) => Array.isArray(dependency.vulns) && dependency.vulns.length)
|
|
105
|
+
.slice(0, 20)
|
|
106
|
+
.map((dependency) => ({
|
|
107
|
+
name: dependency.name || "unknown",
|
|
108
|
+
version: dependency.version || "unknown",
|
|
109
|
+
severity: "unknown",
|
|
110
|
+
viaCount: dependency.vulns.length,
|
|
111
|
+
fixAvailable: dependency.vulns.some((vuln) => Array.isArray(vuln.fix_versions) && vuln.fix_versions.length),
|
|
112
|
+
fixVersions: unique(dependency.vulns.flatMap((vuln) => vuln.fix_versions || [])).slice(0, 5)
|
|
113
|
+
}));
|
|
114
|
+
const total = vulnerablePackages.reduce((sum, pkg) => sum + pkg.viaCount, 0);
|
|
115
|
+
return {
|
|
116
|
+
available: true,
|
|
117
|
+
summary: { total, critical: 0, high: 0, moderate: 0, low: 0, info: 0 },
|
|
118
|
+
vulnerablePackages
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
return { available: false };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
75
125
|
export function summarizeScanners(scanners = {}) {
|
|
76
126
|
const summary = { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 };
|
|
77
127
|
for (const scanner of Object.values(scanners)) {
|
|
@@ -81,6 +131,15 @@ export function summarizeScanners(scanners = {}) {
|
|
|
81
131
|
return summary;
|
|
82
132
|
}
|
|
83
133
|
|
|
134
|
+
export function topVulnerablePackages(scanners = {}, limit = 8) {
|
|
135
|
+
const rank = { critical: 0, high: 1, moderate: 2, low: 3, info: 4, unknown: 5 };
|
|
136
|
+
return Object.values(scanners)
|
|
137
|
+
.filter((scanner) => scanner?.available)
|
|
138
|
+
.flatMap((scanner) => (scanner.vulnerablePackages || []).map((pkg) => ({ ...pkg, scanner: scanner.scanner })))
|
|
139
|
+
.sort((a, b) => (rank[a.severity] ?? rank.unknown) - (rank[b.severity] ?? rank.unknown) || a.name.localeCompare(b.name))
|
|
140
|
+
.slice(0, limit);
|
|
141
|
+
}
|
|
142
|
+
|
|
84
143
|
function unavailable(scanner, reason) {
|
|
85
144
|
return {
|
|
86
145
|
scanner,
|
|
@@ -88,3 +147,7 @@ function unavailable(scanner, reason) {
|
|
|
88
147
|
reason
|
|
89
148
|
};
|
|
90
149
|
}
|
|
150
|
+
|
|
151
|
+
function unique(items) {
|
|
152
|
+
return [...new Set(items.filter(Boolean))];
|
|
153
|
+
}
|