aienvmp 0.1.27 → 0.1.29
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/README.md +1 -1
- package/package.json +1 -1
- package/src/actions.js +2 -1
- package/src/commands/plan.js +5 -0
- package/src/dependencies.js +52 -0
- package/src/manifest.js +5 -3
- package/src/render.js +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.29
|
|
4
|
+
|
|
5
|
+
- Added lightweight remediation priority scoring for vulnerable packages.
|
|
6
|
+
- Exposed priority level, score, and reasons in security summaries, plans, compact context, and dashboard rows.
|
|
7
|
+
- Kept scoring advisory-only so AI agents can choose safer next steps without blocking local operation.
|
|
8
|
+
|
|
9
|
+
## 0.1.28
|
|
10
|
+
|
|
11
|
+
- Linked vulnerable package summaries to the dependency snapshot when the package is directly declared.
|
|
12
|
+
- Added direct dependency metadata to remediation steps and dashboard security rows.
|
|
13
|
+
- Kept the linkage read-only and file-based so security context stays lightweight and non-disruptive.
|
|
14
|
+
|
|
3
15
|
## 0.1.27
|
|
4
16
|
|
|
5
17
|
- Added a read-only dependency snapshot to the manifest, context, AIENV.md, and dashboard.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
It helps Codex, Claude, Gemini, and humans avoid silent Node, Python, package manager, dependency, Docker, and security drift.
|
|
12
12
|
|
|
13
|
-
Core loop: scan once, give AI a shared decision contract
|
|
13
|
+
Core loop: scan once, link runtime/dependency/security context, give AI a shared decision contract with advisory priorities, and hand off safe next steps.
|
|
14
14
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -42,7 +42,8 @@ function securityActions(security = {}) {
|
|
|
42
42
|
|
|
43
43
|
const packageHints = packages.map((pkg) => {
|
|
44
44
|
const fix = pkg.fixVersions?.length ? `fix ${pkg.fixVersions.slice(0, 2).join(", ")}` : pkg.fixAvailable ? "fix available" : "review required";
|
|
45
|
-
|
|
45
|
+
const priority = pkg.remediationPriority ? `${pkg.remediationPriority.level}/${pkg.remediationPriority.score}, ` : "";
|
|
46
|
+
return `${pkg.name} (${priority}${pkg.severity}, ${fix})`;
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
return [action(
|
package/src/commands/plan.js
CHANGED
|
@@ -73,6 +73,8 @@ export function compactStepSummary(plan = {}) {
|
|
|
73
73
|
remediation: (plan.remediationSteps || []).slice(0, 3).map((item) => ({
|
|
74
74
|
package: item.package,
|
|
75
75
|
severity: item.severity,
|
|
76
|
+
priority: item.remediationPriority?.level || "low",
|
|
77
|
+
score: item.remediationPriority?.score || 0,
|
|
76
78
|
fixVersions: (item.fixVersions || []).slice(0, 3),
|
|
77
79
|
advisoryIds: (item.advisories || []).map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 3)
|
|
78
80
|
})),
|
|
@@ -175,6 +177,9 @@ function remediationSteps(security = {}) {
|
|
|
175
177
|
package: pkg.name,
|
|
176
178
|
scanner: pkg.scanner || "unknown",
|
|
177
179
|
severity: pkg.severity || "unknown",
|
|
180
|
+
remediationPriority: pkg.remediationPriority || { level: "low", score: 0, reasons: [] },
|
|
181
|
+
directDependency: pkg.directDependency === true,
|
|
182
|
+
dependency: pkg.dependency || null,
|
|
178
183
|
fixAvailable: pkg.fixAvailable === true,
|
|
179
184
|
fixVersions,
|
|
180
185
|
advisories,
|
package/src/dependencies.js
CHANGED
|
@@ -23,6 +23,49 @@ export async function scanDependencySnapshot(dir) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export function linkVulnerableDependencies(security = {}, snapshot = {}) {
|
|
27
|
+
const dependencyIndex = new Map((snapshot.packages || []).map((pkg) => [dependencyKey(pkg.ecosystem, pkg.name), pkg]));
|
|
28
|
+
return {
|
|
29
|
+
...security,
|
|
30
|
+
topPackages: (security.topPackages || []).map((pkg) => {
|
|
31
|
+
const dependency = dependencyIndex.get(dependencyKey(scannerEcosystem(pkg.scanner), pkg.name));
|
|
32
|
+
const directDependency = Boolean(dependency);
|
|
33
|
+
return {
|
|
34
|
+
...pkg,
|
|
35
|
+
directDependency,
|
|
36
|
+
dependency: dependency ? {
|
|
37
|
+
ecosystem: dependency.ecosystem,
|
|
38
|
+
manifest: dependency.manifest,
|
|
39
|
+
group: dependency.group,
|
|
40
|
+
version: dependency.version
|
|
41
|
+
} : null,
|
|
42
|
+
remediationPriority: remediationPriority(pkg, { directDependency })
|
|
43
|
+
};
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function remediationPriority(pkg = {}, context = {}) {
|
|
49
|
+
const severityScore = { critical: 90, high: 70, moderate: 45, low: 20, info: 5, unknown: 30 };
|
|
50
|
+
const severity = String(pkg.severity || "unknown").toLowerCase();
|
|
51
|
+
const reasons = [`severity:${severity}`];
|
|
52
|
+
let score = severityScore[severity] ?? severityScore.unknown;
|
|
53
|
+
if (context.directDependency) {
|
|
54
|
+
score += 15;
|
|
55
|
+
reasons.push("direct-dependency");
|
|
56
|
+
} else {
|
|
57
|
+
reasons.push("not-direct-in-snapshot");
|
|
58
|
+
}
|
|
59
|
+
if (pkg.fixAvailable === true || (Array.isArray(pkg.fixVersions) && pkg.fixVersions.length)) {
|
|
60
|
+
score += 5;
|
|
61
|
+
reasons.push("fix-available");
|
|
62
|
+
} else {
|
|
63
|
+
reasons.push("fix-review-needed");
|
|
64
|
+
}
|
|
65
|
+
const level = score >= 95 ? "urgent" : score >= 75 ? "high" : score >= 50 ? "medium" : "low";
|
|
66
|
+
return { level, score, reasons };
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
async function scanNodeDependencies(dir) {
|
|
27
70
|
const file = path.join(dir, "package.json");
|
|
28
71
|
if (!(await exists(file))) return { manifests: [], packages: [] };
|
|
@@ -102,6 +145,15 @@ export function parseRequirementLine(line) {
|
|
|
102
145
|
};
|
|
103
146
|
}
|
|
104
147
|
|
|
148
|
+
function scannerEcosystem(scanner = "") {
|
|
149
|
+
if (String(scanner).includes("pip")) return "python";
|
|
150
|
+
return "npm";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function dependencyKey(ecosystem = "", name = "") {
|
|
154
|
+
return `${String(ecosystem).toLowerCase()}:${String(name).toLowerCase()}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
105
157
|
export function parsePyprojectDependencies(raw) {
|
|
106
158
|
const lines = [];
|
|
107
159
|
const match = String(raw).match(/dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
package/src/manifest.js
CHANGED
|
@@ -6,10 +6,12 @@ import { exists } from "./fsutil.js";
|
|
|
6
6
|
import { observedTrust } from "./trust.js";
|
|
7
7
|
import { scanGlobalInventory } from "./inventory.js";
|
|
8
8
|
import { scanSecurity } from "./security.js";
|
|
9
|
-
import { scanDependencySnapshot } from "./dependencies.js";
|
|
9
|
+
import { linkVulnerableDependencies, scanDependencySnapshot } from "./dependencies.js";
|
|
10
10
|
|
|
11
11
|
export async function buildManifest(dir, options = {}) {
|
|
12
12
|
const now = new Date().toISOString();
|
|
13
|
+
const dependencySnapshot = await scanDependencySnapshot(dir);
|
|
14
|
+
const security = linkVulnerableDependencies(await scanSecurity(dir, { security: options.security }), dependencySnapshot);
|
|
13
15
|
const manifest = {
|
|
14
16
|
schemaName: "aienvmp.runtime-sbom",
|
|
15
17
|
schemaVersion: 1,
|
|
@@ -28,9 +30,9 @@ export async function buildManifest(dir, options = {}) {
|
|
|
28
30
|
packageManagers: await scanPackageManagers(),
|
|
29
31
|
containers: await scanContainers(),
|
|
30
32
|
projectHints: await scanProjectHints(dir),
|
|
31
|
-
dependencySnapshot
|
|
33
|
+
dependencySnapshot,
|
|
32
34
|
inventory: await scanGlobalInventory({ deep: options.deep }),
|
|
33
|
-
security
|
|
35
|
+
security,
|
|
34
36
|
agentFiles: await scanAgentFiles(dir),
|
|
35
37
|
agentProtocol: {
|
|
36
38
|
sourceOfTruth: "AIENV.md",
|
package/src/render.js
CHANGED
|
@@ -211,8 +211,10 @@ function environmentLines(item) {
|
|
|
211
211
|
function remediationLines(item) {
|
|
212
212
|
const fix = item.fixVersions?.length ? `fix ${item.fixVersions.join(", ")}` : item.fixAvailable ? "fix available" : "review required";
|
|
213
213
|
const advisories = (item.advisories || []).map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 2);
|
|
214
|
+
const dependency = item.directDependency && item.dependency ? `; declared in ${item.dependency.manifest} ${item.dependency.version}` : "; not found in dependency snapshot";
|
|
215
|
+
const priority = item.remediationPriority ? `priority ${item.remediationPriority.level}/${item.remediationPriority.score}` : "priority unscored";
|
|
214
216
|
return [
|
|
215
|
-
`- ${item.package}: ${item.severity}; ${fix}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
|
|
217
|
+
`- ${item.package}: ${item.severity}; ${priority}; ${fix}${dependency}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
|
|
216
218
|
...item.steps.slice(0, 4).map((step) => ` - ${step}`)
|
|
217
219
|
];
|
|
218
220
|
}
|
|
@@ -285,7 +287,9 @@ const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0
|
|
|
285
287
|
const secPackages=sec.topPackages||[];
|
|
286
288
|
const securityFix=p=>p.fixVersions?.length?\`fix \${p.fixVersions.slice(0,3).join(', ')}\`:(p.fixAvailable?'fix available':'review required');
|
|
287
289
|
const securityRefs=p=>p.advisories?.length?\` - \${p.advisories.map(a=>a.id||a.title).filter(Boolean).slice(0,2).join(', ')}\`:'';
|
|
288
|
-
const
|
|
290
|
+
const securityDep=p=>p.directDependency&&p.dependency?\`<div class="path">\${esc(p.dependency.manifest)} / \${esc(p.dependency.group)} / \${esc(p.dependency.version)}</div>\`:'<div class="path">not found in dependency snapshot</div>';
|
|
291
|
+
const securityPriority=p=>p.remediationPriority?\`<code>\${esc(p.remediationPriority.level)} \${esc(p.remediationPriority.score)}</code> \`:'';
|
|
292
|
+
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> \${securityPriority(p)}\${esc(securityFix(p))}\${esc(securityRefs(p))}\${securityDep(p)}</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>';
|
|
289
293
|
const change=c=>c.type==='changed'?\`\${c.scope} \${c.key}: \${c.before} -> \${c.after}\`:\`\${c.scope} \${c.key}: \${c.type} \${c.after||c.before}\`;
|
|
290
294
|
const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
|
|
291
295
|
const agentNames={agents:'Codex',claude:'Claude',gemini:'Gemini'};
|
|
@@ -463,11 +467,13 @@ function securityLines(security = {}) {
|
|
|
463
467
|
|
|
464
468
|
function securityPackageNote(pkg) {
|
|
465
469
|
const fix = pkg.fixVersions?.length ? `fix ${pkg.fixVersions.slice(0, 3).join(", ")}` : pkg.fixAvailable ? "fix available" : "review required";
|
|
470
|
+
const priority = pkg.remediationPriority ? `priority ${pkg.remediationPriority.level}/${pkg.remediationPriority.score}; ` : "";
|
|
471
|
+
const dependency = pkg.directDependency && pkg.dependency ? `; declared in ${pkg.dependency.manifest} ${pkg.dependency.version}` : "; not found in dependency snapshot";
|
|
466
472
|
const advisories = (pkg.advisories || [])
|
|
467
473
|
.map((item) => item.id || item.title)
|
|
468
474
|
.filter(Boolean)
|
|
469
475
|
.slice(0, 2);
|
|
470
|
-
return advisories.length ? `${fix}; advisories ${advisories.join(", ")}` : fix
|
|
476
|
+
return advisories.length ? `${priority}${fix}${dependency}; advisories ${advisories.join(", ")}` : `${priority}${fix}${dependency}`;
|
|
471
477
|
}
|
|
472
478
|
|
|
473
479
|
function policyLines(policy) {
|