aienvmp 0.1.27 → 0.1.28

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.28
4
+
5
+ - Linked vulnerable package summaries to the dependency snapshot when the package is directly declared.
6
+ - Added direct dependency metadata to remediation steps and dashboard security rows.
7
+ - Kept the linkage read-only and file-based so security context stays lightweight and non-disruptive.
8
+
3
9
  ## 0.1.27
4
10
 
5
11
  - 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, write a read-only action plan, and hand off safe next steps.
13
+ Core loop: scan once, link runtime/dependency/security context, give AI a shared decision contract, and hand off safe next steps.
14
14
 
15
15
  ## Quick Start
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -175,6 +175,8 @@ function remediationSteps(security = {}) {
175
175
  package: pkg.name,
176
176
  scanner: pkg.scanner || "unknown",
177
177
  severity: pkg.severity || "unknown",
178
+ directDependency: pkg.directDependency === true,
179
+ dependency: pkg.dependency || null,
178
180
  fixAvailable: pkg.fixAvailable === true,
179
181
  fixVersions,
180
182
  advisories,
@@ -23,6 +23,26 @@ 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
+ return {
33
+ ...pkg,
34
+ directDependency: Boolean(dependency),
35
+ dependency: dependency ? {
36
+ ecosystem: dependency.ecosystem,
37
+ manifest: dependency.manifest,
38
+ group: dependency.group,
39
+ version: dependency.version
40
+ } : null
41
+ };
42
+ })
43
+ };
44
+ }
45
+
26
46
  async function scanNodeDependencies(dir) {
27
47
  const file = path.join(dir, "package.json");
28
48
  if (!(await exists(file))) return { manifests: [], packages: [] };
@@ -102,6 +122,15 @@ export function parseRequirementLine(line) {
102
122
  };
103
123
  }
104
124
 
125
+ function scannerEcosystem(scanner = "") {
126
+ if (String(scanner).includes("pip")) return "python";
127
+ return "npm";
128
+ }
129
+
130
+ function dependencyKey(ecosystem = "", name = "") {
131
+ return `${String(ecosystem).toLowerCase()}:${String(name).toLowerCase()}`;
132
+ }
133
+
105
134
  export function parsePyprojectDependencies(raw) {
106
135
  const lines = [];
107
136
  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: await scanDependencySnapshot(dir),
33
+ dependencySnapshot,
32
34
  inventory: await scanGlobalInventory({ deep: options.deep }),
33
- security: await scanSecurity(dir, { security: options.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,9 @@ 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";
214
215
  return [
215
- `- ${item.package}: ${item.severity}; ${fix}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
216
+ `- ${item.package}: ${item.severity}; ${fix}${dependency}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
216
217
  ...item.steps.slice(0, 4).map((step) => ` - ${step}`)
217
218
  ];
218
219
  }
@@ -285,7 +286,8 @@ const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0
285
286
  const secPackages=sec.topPackages||[];
286
287
  const securityFix=p=>p.fixVersions?.length?\`fix \${p.fixVersions.slice(0,3).join(', ')}\`:(p.fixAvailable?'fix available':'review required');
287
288
  const securityRefs=p=>p.advisories?.length?\` - \${p.advisories.map(a=>a.id||a.title).filter(Boolean).slice(0,2).join(', ')}\`:'';
288
- 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> \${esc(securityFix(p))}\${esc(securityRefs(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
+ 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>';
290
+ 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> \${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
291
  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
292
  const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
291
293
  const agentNames={agents:'Codex',claude:'Claude',gemini:'Gemini'};
@@ -463,11 +465,12 @@ function securityLines(security = {}) {
463
465
 
464
466
  function securityPackageNote(pkg) {
465
467
  const fix = pkg.fixVersions?.length ? `fix ${pkg.fixVersions.slice(0, 3).join(", ")}` : pkg.fixAvailable ? "fix available" : "review required";
468
+ const dependency = pkg.directDependency && pkg.dependency ? `; declared in ${pkg.dependency.manifest} ${pkg.dependency.version}` : "; not found in dependency snapshot";
466
469
  const advisories = (pkg.advisories || [])
467
470
  .map((item) => item.id || item.title)
468
471
  .filter(Boolean)
469
472
  .slice(0, 2);
470
- return advisories.length ? `${fix}; advisories ${advisories.join(", ")}` : fix;
473
+ return advisories.length ? `${fix}${dependency}; advisories ${advisories.join(", ")}` : `${fix}${dependency}`;
471
474
  }
472
475
 
473
476
  function policyLines(policy) {