kirograph 1.0.0 → 1.2.1

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.
Files changed (99) hide show
  1. package/README.md +4 -2
  2. package/dist/architecture/manifest/python.js +2 -2
  3. package/dist/architecture/manifest/python.js.map +2 -2
  4. package/dist/bin/commands/help.js +3 -1
  5. package/dist/bin/commands/help.js.map +2 -2
  6. package/dist/bin/commands/memory.js +29 -9
  7. package/dist/bin/commands/memory.js.map +2 -2
  8. package/dist/bin/commands/reachability.js +7 -2
  9. package/dist/bin/commands/reachability.js.map +2 -2
  10. package/dist/bin/commands/vulns.js +16 -1
  11. package/dist/bin/commands/vulns.js.map +2 -2
  12. package/dist/bin/commands/wiki.js +59 -2
  13. package/dist/bin/commands/wiki.js.map +2 -2
  14. package/dist/bin/installer/steering.js +9 -0
  15. package/dist/bin/installer/steering.js.map +2 -2
  16. package/dist/bin/kirograph.js +1 -1
  17. package/dist/config.js +12 -0
  18. package/dist/config.js.map +2 -2
  19. package/dist/db/memory-schema.sql +2 -1
  20. package/dist/extraction/extractor.js +46 -41
  21. package/dist/extraction/extractor.js.map +2 -2
  22. package/dist/extraction/notebook.js +15 -7
  23. package/dist/extraction/notebook.js.map +2 -2
  24. package/dist/jev/client.js +99 -0
  25. package/dist/jev/client.js.map +7 -0
  26. package/dist/jev/types.js +17 -0
  27. package/dist/jev/types.js.map +7 -0
  28. package/dist/mcp/handlers/memory.js +15 -8
  29. package/dist/mcp/handlers/memory.js.map +2 -2
  30. package/dist/mcp/handlers/security.js +5 -3
  31. package/dist/mcp/handlers/security.js.map +2 -2
  32. package/dist/mcp/handlers/wiki.js +4 -2
  33. package/dist/mcp/handlers/wiki.js.map +2 -2
  34. package/dist/mcp/tools.js +3 -2
  35. package/dist/mcp/tools.js.map +2 -2
  36. package/dist/memory/database.js +17 -4
  37. package/dist/memory/database.js.map +2 -2
  38. package/dist/memory/index.js +31 -2
  39. package/dist/memory/index.js.map +3 -3
  40. package/dist/memory/relation-jev.js +59 -0
  41. package/dist/memory/relation-jev.js.map +7 -0
  42. package/dist/memory/schema.js +92 -0
  43. package/dist/memory/schema.js.map +7 -0
  44. package/dist/memory/types.js.map +1 -1
  45. package/dist/patterns/loader.js +2 -146
  46. package/dist/patterns/loader.js.map +3 -3
  47. package/dist/security/auth-detection-jev.js +47 -0
  48. package/dist/security/auth-detection-jev.js.map +7 -0
  49. package/dist/security/manifest/adapter.js +17 -3
  50. package/dist/security/manifest/adapter.js.map +3 -3
  51. package/dist/security/manifest/plugins/cargo.js +23 -0
  52. package/dist/security/manifest/plugins/cargo.js.map +2 -2
  53. package/dist/security/manifest/plugins/composer.js +12 -0
  54. package/dist/security/manifest/plugins/composer.js.map +2 -2
  55. package/dist/security/manifest/plugins/gradle.js +12 -0
  56. package/dist/security/manifest/plugins/gradle.js.map +2 -2
  57. package/dist/security/manifest/plugins/hex.js +12 -0
  58. package/dist/security/manifest/plugins/hex.js.map +2 -2
  59. package/dist/security/manifest/plugins/maven.js +101 -4
  60. package/dist/security/manifest/plugins/maven.js.map +2 -2
  61. package/dist/security/manifest/plugins/npm.js +117 -2
  62. package/dist/security/manifest/plugins/npm.js.map +2 -2
  63. package/dist/security/manifest/plugins/nuget.js +13 -0
  64. package/dist/security/manifest/plugins/nuget.js.map +2 -2
  65. package/dist/security/manifest/plugins/pubspec.js +12 -0
  66. package/dist/security/manifest/plugins/pubspec.js.map +2 -2
  67. package/dist/security/manifest/plugins/pyproject.js +12 -0
  68. package/dist/security/manifest/plugins/pyproject.js.map +2 -2
  69. package/dist/security/manifest/plugins/rubygems.js +12 -0
  70. package/dist/security/manifest/plugins/rubygems.js.map +2 -2
  71. package/dist/security/pipeline.js +1 -1
  72. package/dist/security/pipeline.js.map +2 -2
  73. package/dist/security/reachability.js +54 -5
  74. package/dist/security/reachability.js.map +3 -3
  75. package/dist/security/types.js.map +1 -1
  76. package/dist/security/vuln/client.js +45 -1
  77. package/dist/security/vuln/client.js.map +2 -2
  78. package/dist/security/vuln/osv-adapter.js +45 -1
  79. package/dist/security/vuln/osv-adapter.js.map +2 -2
  80. package/dist/shared/json-schema.js +136 -0
  81. package/dist/shared/json-schema.js.map +7 -0
  82. package/dist/shared/yaml.js +174 -0
  83. package/dist/shared/yaml.js.map +7 -0
  84. package/dist/wiki/contradiction-jev.js +49 -0
  85. package/dist/wiki/contradiction-jev.js.map +7 -0
  86. package/dist/wiki/index.js +16 -1
  87. package/dist/wiki/index.js.map +2 -2
  88. package/dist/wiki/ingest.js +2 -0
  89. package/dist/wiki/ingest.js.map +2 -2
  90. package/dist/wiki/links.js +53 -0
  91. package/dist/wiki/links.js.map +7 -0
  92. package/dist/wiki/lint.js +17 -7
  93. package/dist/wiki/lint.js.map +2 -2
  94. package/dist/wiki/rename.js +103 -0
  95. package/dist/wiki/rename.js.map +7 -0
  96. package/dist/wiki/typed-pages.js +67 -0
  97. package/dist/wiki/typed-pages.js.map +7 -0
  98. package/dist/wiki/types.js.map +1 -1
  99. package/package.json +1 -1
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var npm_exports = {};
30
30
  __export(npm_exports, {
31
+ findNpmDuplicateVersions: () => findNpmDuplicateVersions,
31
32
  parseNpmManifest: () => parseNpmManifest
32
33
  });
33
34
  module.exports = __toCommonJS(npm_exports);
@@ -56,7 +57,7 @@ async function parseNpmManifest(manifestPath, projectRoot) {
56
57
  return [];
57
58
  }
58
59
  const resolvedVersions = loadResolvedVersions(manifestDir);
59
- const license = extractNpmLicense(pkg);
60
+ const licenses = loadNpmLicenses(manifestDir);
60
61
  const dependencies = [];
61
62
  const warnings = [];
62
63
  for (const { field, scope } of SCOPE_FIELDS) {
@@ -79,6 +80,7 @@ async function parseNpmManifest(manifestPath, projectRoot) {
79
80
  continue;
80
81
  }
81
82
  const resolvedVersion = resolvedVersions.get(name);
83
+ const license = licenses.get(name);
82
84
  dependencies.push({
83
85
  name,
84
86
  declaredConstraint: constraint,
@@ -107,6 +109,7 @@ async function parseNpmManifest(manifestPath, projectRoot) {
107
109
  const alreadyDeclared = dependencies.some((d) => d.name === name);
108
110
  if (!alreadyDeclared) {
109
111
  const resolvedVersion = resolvedVersions.get(name);
112
+ const license = licenses.get(name);
110
113
  dependencies.push({
111
114
  name,
112
115
  declaredConstraint: constraint,
@@ -119,8 +122,94 @@ async function parseNpmManifest(manifestPath, projectRoot) {
119
122
  }
120
123
  }
121
124
  }
125
+ const directNames = new Set(dependencies.map((d) => d.name));
126
+ const lockRelativePath = relativeManifest.replace(/package\.json$/, "package-lock.json");
127
+ const npmLockPath = path.join(manifestDir, "package-lock.json");
128
+ if (fs.existsSync(npmLockPath)) {
129
+ dependencies.push(...extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames));
130
+ } else {
131
+ for (const [name, version] of resolvedVersions) {
132
+ if (directNames.has(name)) continue;
133
+ dependencies.push({
134
+ name,
135
+ declaredConstraint: version,
136
+ resolvedVersion: version,
137
+ scope: "production",
138
+ ecosystem: "npm",
139
+ sourceManifest: relativeManifest
140
+ });
141
+ }
142
+ }
122
143
  return dependencies;
123
144
  }
145
+ function findNpmDuplicateVersions(projectRoot) {
146
+ const versionsByName = /* @__PURE__ */ new Map();
147
+ const lockPath = path.join(projectRoot, "package-lock.json");
148
+ if (!fs.existsSync(lockPath)) return versionsByName;
149
+ let lockData;
150
+ try {
151
+ lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
152
+ } catch {
153
+ return versionsByName;
154
+ }
155
+ if (typeof lockData !== "object" || lockData === null) return versionsByName;
156
+ const packages = lockData.packages;
157
+ if (typeof packages !== "object" || packages === null) return versionsByName;
158
+ for (const [key, value] of Object.entries(packages)) {
159
+ if (key === "") continue;
160
+ if (typeof value !== "object" || value === null) continue;
161
+ const pkg = value;
162
+ const version = pkg.version;
163
+ if (typeof version !== "string") continue;
164
+ const name = key.replace(/^.*node_modules\//, "");
165
+ if (!name) continue;
166
+ if (!versionsByName.has(name)) versionsByName.set(name, /* @__PURE__ */ new Set());
167
+ versionsByName.get(name).add(version);
168
+ }
169
+ for (const [name, versions] of versionsByName) {
170
+ if (versions.size <= 1) versionsByName.delete(name);
171
+ }
172
+ return versionsByName;
173
+ }
174
+ function extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames) {
175
+ const lockPath = path.join(manifestDir, "package-lock.json");
176
+ if (!fs.existsSync(lockPath)) return [];
177
+ let lockData;
178
+ try {
179
+ lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
180
+ } catch (err) {
181
+ (0, import_errors.logWarn)(`[sec:npm] Failed to parse ${lockRelativePath} for transitive dependencies: ${err instanceof Error ? err.message : String(err)}`);
182
+ return [];
183
+ }
184
+ if (typeof lockData !== "object" || lockData === null) return [];
185
+ const packages = lockData.packages;
186
+ if (typeof packages !== "object" || packages === null) return [];
187
+ const transitive = [];
188
+ const seen = /* @__PURE__ */ new Set();
189
+ for (const [key, value] of Object.entries(packages)) {
190
+ if (key === "") continue;
191
+ if (typeof value !== "object" || value === null) continue;
192
+ const pkg = value;
193
+ const name = key.replace(/^.*node_modules\//, "");
194
+ if (!name || directNames.has(name) || seen.has(name)) continue;
195
+ const version = pkg.version;
196
+ if (typeof version !== "string") continue;
197
+ seen.add(name);
198
+ const license = normalizeLicenseField(pkg);
199
+ transitive.push({
200
+ name,
201
+ // No real semver range exists at the project level for a transitive
202
+ // package — the resolved version is the only constraint we have.
203
+ declaredConstraint: version,
204
+ resolvedVersion: version,
205
+ scope: pkg.dev === true ? "development" : "production",
206
+ ecosystem: "npm",
207
+ sourceManifest: lockRelativePath,
208
+ ...license !== void 0 ? { license } : {}
209
+ });
210
+ }
211
+ return transitive;
212
+ }
124
213
  function loadResolvedVersions(manifestDir) {
125
214
  const map = /* @__PURE__ */ new Map();
126
215
  const lockPath = path.join(manifestDir, "package-lock.json");
@@ -156,6 +245,31 @@ function loadResolvedVersions(manifestDir) {
156
245
  }
157
246
  return map;
158
247
  }
248
+ function loadNpmLicenses(manifestDir) {
249
+ const map = /* @__PURE__ */ new Map();
250
+ const lockPath = path.join(manifestDir, "package-lock.json");
251
+ if (!fs.existsSync(lockPath)) return map;
252
+ let lockData;
253
+ try {
254
+ lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
255
+ } catch (err) {
256
+ (0, import_errors.logWarn)(`[sec:npm] Failed to parse package-lock.json for license data: ${err instanceof Error ? err.message : String(err)}`);
257
+ return map;
258
+ }
259
+ if (typeof lockData !== "object" || lockData === null) return map;
260
+ const packages = lockData.packages;
261
+ if (typeof packages !== "object" || packages === null) return map;
262
+ for (const [key, value] of Object.entries(packages)) {
263
+ if (key === "") continue;
264
+ if (typeof value !== "object" || value === null) continue;
265
+ const pkg = value;
266
+ const name = key.replace(/^.*node_modules\//, "");
267
+ if (!name || map.has(name)) continue;
268
+ const license = normalizeLicenseField(pkg);
269
+ if (license !== void 0) map.set(name, license);
270
+ }
271
+ return map;
272
+ }
159
273
  function extractFromPnpmLock(content, map) {
160
274
  for (const m of content.matchAll(/^[\s]*\/?(@?[a-zA-Z0-9._-]+(?:\/[a-zA-Z0-9._-]+)?)[@/]([0-9][a-zA-Z0-9._-]*)(?:\(.*?\))?:/gm)) {
161
275
  const name = m[1].replace(/^\//, "");
@@ -239,7 +353,7 @@ function extractPackageNameFromYarnHeader(header) {
239
353
  }
240
354
  return cleaned.slice(0, lastAtIndex);
241
355
  }
242
- function extractNpmLicense(pkg) {
356
+ function normalizeLicenseField(pkg) {
243
357
  const raw = pkg["license"];
244
358
  if (typeof raw === "string" && raw.trim() !== "") {
245
359
  return raw.trim();
@@ -279,6 +393,7 @@ function isValidDependencyName(name) {
279
393
  }
280
394
  // Annotate the CommonJS export names for ESM import in node:
281
395
  0 && (module.exports = {
396
+ findNpmDuplicateVersions,
282
397
  parseNpmManifest
283
398
  });
284
399
  //# sourceMappingURL=npm.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/security/manifest/plugins/npm.ts"],
4
- "sourcesContent": ["/**\n * NPM Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture npm parser to extract version constraints,\n * resolved versions (from lock files), and dependency scopes for security analysis.\n *\n * Reuses `npmParser` from `src/architecture/manifest/npm.ts` for discovery and\n * basic parsing, then layers on version/scope extraction.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/** Scope fields in package.json mapped to their ParsedDependency scope */\nconst SCOPE_FIELDS: Array<{ field: string; scope: ParsedDependency['scope'] }> = [\n { field: 'dependencies', scope: 'production' },\n { field: 'devDependencies', scope: 'development' },\n { field: 'optionalDependencies', scope: 'optional' },\n];\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from package-lock.json or yarn.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse an npm package.json manifest and extract dependency declarations\n * with version constraints, scopes, and resolved versions from lock files.\n *\n * @param manifestPath - Absolute path to the package.json file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parseNpmManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read and parse the package.json\n let pkg: Record<string, unknown>;\n try {\n const raw = fs.readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n logWarn(`[sec:npm] Invalid package.json structure at ${relativeManifest}`);\n return [];\n }\n pkg = parsed as Record<string, unknown>;\n } catch (err) {\n logWarn(`[sec:npm] Failed to read/parse ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from lock file\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract the package-level license\n const license = extractNpmLicense(pkg);\n\n // Extract dependencies from each scope field\n const dependencies: ParsedDependency[] = [];\n const warnings: Array<{ name: string; reason: string }> = [];\n\n for (const { field, scope } of SCOPE_FIELDS) {\n const depsObj = pkg[field];\n if (depsObj === undefined || depsObj === null) continue;\n\n if (typeof depsObj !== 'object' || Array.isArray(depsObj)) {\n logWarn(`[sec:npm] Invalid \"${field}\" field in ${relativeManifest} \u2014 expected object, got ${typeof depsObj}`);\n continue;\n }\n\n const entries = Object.entries(depsObj as Record<string, unknown>);\n for (const [name, constraint] of entries) {\n // Validate the dependency entry\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid dependency name \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n const resolvedVersion = resolvedVersions.get(name);\n\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope,\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Also handle peerDependencies \u2014 default to production scope\n const peerDeps = pkg.peerDependencies;\n if (peerDeps && typeof peerDeps === 'object' && !Array.isArray(peerDeps)) {\n const peerEntries = Object.entries(peerDeps as Record<string, unknown>);\n for (const [name, constraint] of peerEntries) {\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid peer dependency name \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for peer dependency \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n // Only add if not already declared in another scope\n const alreadyDeclared = dependencies.some(d => d.name === name);\n if (!alreadyDeclared) {\n const resolvedVersion = resolvedVersions.get(name);\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n }\n\n return dependencies;\n}\n\n/**\n * Attempt to load resolved versions from package-lock.json, pnpm-lock.yaml,\n * or yarn.lock in the given directory. Returns an empty map if none is available.\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n // Try package-lock.json first (preferred \u2014 npm)\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(lockPath)) {\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n const lockData = JSON.parse(lockContent);\n extractFromPackageLock(lockData, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse package-lock.json: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Try pnpm-lock.yaml (pnpm)\n const pnpmLockPath = path.join(manifestDir, 'pnpm-lock.yaml');\n if (fs.existsSync(pnpmLockPath)) {\n try {\n const pnpmContent = fs.readFileSync(pnpmLockPath, 'utf8');\n extractFromPnpmLock(pnpmContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse pnpm-lock.yaml: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Fall back to yarn.lock\n const yarnLockPath = path.join(manifestDir, 'yarn.lock');\n if (fs.existsSync(yarnLockPath)) {\n try {\n const yarnContent = fs.readFileSync(yarnLockPath, 'utf8');\n extractFromYarnLock(yarnContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse yarn.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pnpm-lock.yaml (v5, v6, v9 formats).\n *\n * v5/v6: packages section with keys like \"/express/4.18.2\" or \"/express@4.18.2\"\n * v9: snapshots section with keys like \"express@4.18.2\" and\n * packages section with \"express@4.18.2: {version: ...}\" (optional)\n */\nfunction extractFromPnpmLock(content: string, map: ResolvedVersionMap): void {\n // v9 format: package entries under `packages:` or `snapshots:`\n // Key format: \"name@version\" or \"@scope/name@version\"\n // We extract name@version pairs from both sections.\n\n // Match lines like: /express/4.18.2: or /express@4.18.2: or express@4.18.2:\n for (const m of content.matchAll(/^[\\s]*\\/?(@?[a-zA-Z0-9._-]+(?:\\/[a-zA-Z0-9._-]+)?)[@/]([0-9][a-zA-Z0-9._-]*)(?:\\(.*?\\))?:/gm)) {\n const name = m[1].replace(/^\\//, ''); // strip leading slash for scoped packages\n const version = m[2];\n if (name && version && !map.has(name)) {\n map.set(name, version);\n }\n }\n}\n\n/**\n * Extract resolved versions from package-lock.json (supports v2 and v3 format).\n */\nfunction extractFromPackageLock(lockData: unknown, map: ResolvedVersionMap): void {\n if (typeof lockData !== 'object' || lockData === null) return;\n const lock = lockData as Record<string, unknown>;\n\n // lockfileVersion 2/3: uses \"packages\" field with \"\" as root\n const packages = lock.packages;\n if (typeof packages === 'object' && packages !== null) {\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip root\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n // Key format: \"node_modules/<name>\" or \"node_modules/@scope/name\"\n const name = key.replace(/^.*node_modules\\//, '');\n if (name && !map.has(name)) {\n map.set(name, version);\n }\n }\n return;\n }\n\n // lockfileVersion 1: uses \"dependencies\" field\n const dependencies = lock.dependencies;\n if (typeof dependencies === 'object' && dependencies !== null) {\n extractFromLockV1Dependencies(dependencies as Record<string, unknown>, map);\n }\n}\n\n/**\n * Recursively extract versions from lockfileVersion 1 dependencies.\n */\nfunction extractFromLockV1Dependencies(\n deps: Record<string, unknown>,\n map: ResolvedVersionMap,\n): void {\n for (const [name, value] of Object.entries(deps)) {\n if (typeof value !== 'object' || value === null) continue;\n const dep = value as Record<string, unknown>;\n const version = dep.version;\n if (typeof version === 'string' && !map.has(name)) {\n map.set(name, version);\n }\n // Recurse into nested dependencies\n const nested = dep.dependencies;\n if (typeof nested === 'object' && nested !== null) {\n extractFromLockV1Dependencies(nested as Record<string, unknown>, map);\n }\n }\n}\n\n/**\n * Extract resolved versions from yarn.lock (v1 format).\n * yarn.lock uses a custom format, not JSON.\n */\nfunction extractFromYarnLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let currentPackageName: string | null = null;\n\n for (const line of lines) {\n // Skip comments and empty lines\n if (line.startsWith('#') || line.trim() === '') {\n currentPackageName = null;\n continue;\n }\n\n // Package header line: \"package-name@version-range:\" or \"@scope/name@range:\"\n // Can also be: \"package-name@range1, package-name@range2:\"\n if (!line.startsWith(' ') && line.endsWith(':')) {\n const header = line.slice(0, -1); // remove trailing ':'\n // Extract package name from the first entry (before @version)\n const name = extractPackageNameFromYarnHeader(header);\n currentPackageName = name;\n continue;\n }\n\n // Version line: \" version \"x.y.z\"\"\n if (currentPackageName && line.trim().startsWith('version ')) {\n const versionMatch = line.match(/version\\s+\"([^\"]+)\"/);\n if (versionMatch && !map.has(currentPackageName)) {\n map.set(currentPackageName, versionMatch[1]);\n }\n currentPackageName = null;\n }\n }\n}\n\n/**\n * Extract the package name from a yarn.lock header line.\n * Handles formats like:\n * - \"express@^4.17.1\"\n * - \"@types/node@^18.0.0\"\n * - \"express@^4.17.1, express@^4.18.0\"\n * - '\"express@^4.17.1\"'\n */\nfunction extractPackageNameFromYarnHeader(header: string): string | null {\n // Take the first entry if comma-separated\n const firstEntry = header.split(',')[0].trim();\n // Remove surrounding quotes if present\n const cleaned = firstEntry.replace(/^[\"']|[\"']$/g, '');\n\n // Find the last @ that separates name from version\n // For scoped packages (@scope/name@version), we need the last @\n const lastAtIndex = cleaned.lastIndexOf('@');\n if (lastAtIndex <= 0) return null; // no @ or starts with @ (scoped without version)\n\n // For scoped packages, check if the first char is @\n if (cleaned.startsWith('@')) {\n // @scope/name@version \u2014 find the @ after the scope\n const afterScope = cleaned.indexOf('/', 1);\n if (afterScope === -1) return null;\n const atAfterName = cleaned.indexOf('@', afterScope);\n if (atAfterName === -1) return cleaned; // no version part\n return cleaned.slice(0, atAfterName);\n }\n\n return cleaned.slice(0, lastAtIndex);\n}\n\n/**\n * Extract the license field from a parsed package.json object.\n * Handles both string format (`\"MIT\"`) and object format (`{\"type\":\"MIT\"}`).\n */\nfunction extractNpmLicense(pkg: Record<string, unknown>): string | undefined {\n const raw = pkg['license'];\n if (typeof raw === 'string' && raw.trim() !== '') {\n return raw.trim();\n }\n if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n const licObj = raw as Record<string, unknown>;\n if (typeof licObj['type'] === 'string' && licObj['type'].trim() !== '') {\n return licObj['type'].trim();\n }\n }\n // Handle legacy \"licenses\" array\n const licenses = pkg['licenses'];\n if (Array.isArray(licenses) && licenses.length > 0) {\n const first = licenses[0];\n if (typeof first === 'object' && first !== null) {\n const lic = first as Record<string, unknown>;\n if (typeof lic['type'] === 'string' && lic['type'].trim() !== '') {\n return lic['type'].trim();\n }\n }\n if (typeof first === 'string' && first.trim() !== '') {\n return first.trim();\n }\n }\n return undefined;\n}\n\n/**\n * Validate that a dependency name follows npm naming conventions.\n * Must be non-empty, not start with a dot or underscore (unless scoped),\n * and contain only valid characters.\n */\nfunction isValidDependencyName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 214) return false;\n\n // Scoped packages: @scope/name\n if (name.startsWith('@')) {\n const parts = name.split('/');\n if (parts.length !== 2) return false;\n if (!parts[0] || !parts[1]) return false;\n return true;\n }\n\n // Unscoped: must not start with . or _\n if (name.startsWith('.') || name.startsWith('_')) return false;\n\n return true;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * NPM Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture npm parser to extract version constraints,\n * resolved versions (from lock files), and dependency scopes for security analysis.\n *\n * Reuses `npmParser` from `src/architecture/manifest/npm.ts` for discovery and\n * basic parsing, then layers on version/scope extraction.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/** Scope fields in package.json mapped to their ParsedDependency scope */\nconst SCOPE_FIELDS: Array<{ field: string; scope: ParsedDependency['scope'] }> = [\n { field: 'dependencies', scope: 'production' },\n { field: 'devDependencies', scope: 'development' },\n { field: 'optionalDependencies', scope: 'optional' },\n];\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from package-lock.json or yarn.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/** Package name \u2192 SPDX license string. Built from package-lock.json's \"packages\" map. */\ntype LicenseMap = Map<string, string>;\n\n/**\n * Parse an npm package.json manifest and extract dependency declarations\n * with version constraints, scopes, and resolved versions from lock files.\n *\n * @param manifestPath - Absolute path to the package.json file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parseNpmManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read and parse the package.json\n let pkg: Record<string, unknown>;\n try {\n const raw = fs.readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n logWarn(`[sec:npm] Invalid package.json structure at ${relativeManifest}`);\n return [];\n }\n pkg = parsed as Record<string, unknown>;\n } catch (err) {\n logWarn(`[sec:npm] Failed to read/parse ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from lock file\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Per-package licenses, keyed by package name \u2014 read from package-lock.json's\n // \"packages\" map (npm v7+ lockfile v2/v3 records each installed package's own\n // \"license\" field there). This is each dependency's *own* license, not the\n // consuming project's \u2014 see loadNpmLicenses() for why that distinction matters.\n const licenses = loadNpmLicenses(manifestDir);\n\n // Extract dependencies from each scope field\n const dependencies: ParsedDependency[] = [];\n const warnings: Array<{ name: string; reason: string }> = [];\n\n for (const { field, scope } of SCOPE_FIELDS) {\n const depsObj = pkg[field];\n if (depsObj === undefined || depsObj === null) continue;\n\n if (typeof depsObj !== 'object' || Array.isArray(depsObj)) {\n logWarn(`[sec:npm] Invalid \"${field}\" field in ${relativeManifest} \u2014 expected object, got ${typeof depsObj}`);\n continue;\n }\n\n const entries = Object.entries(depsObj as Record<string, unknown>);\n for (const [name, constraint] of entries) {\n // Validate the dependency entry\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid dependency name \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for \"${name}\" in ${relativeManifest} (${field}) \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n const resolvedVersion = resolvedVersions.get(name);\n\n const license = licenses.get(name);\n\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope,\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Also handle peerDependencies \u2014 default to production scope\n const peerDeps = pkg.peerDependencies;\n if (peerDeps && typeof peerDeps === 'object' && !Array.isArray(peerDeps)) {\n const peerEntries = Object.entries(peerDeps as Record<string, unknown>);\n for (const [name, constraint] of peerEntries) {\n if (!isValidDependencyName(name)) {\n logWarn(`[sec:npm] Invalid peer dependency name \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid name' });\n continue;\n }\n\n if (typeof constraint !== 'string' || constraint.trim() === '') {\n logWarn(`[sec:npm] Invalid version constraint for peer dependency \"${name}\" in ${relativeManifest} \u2014 skipping`);\n warnings.push({ name, reason: 'invalid constraint' });\n continue;\n }\n\n // Only add if not already declared in another scope\n const alreadyDeclared = dependencies.some(d => d.name === name);\n if (!alreadyDeclared) {\n const resolvedVersion = resolvedVersions.get(name);\n const license = licenses.get(name);\n dependencies.push({\n name,\n declaredConstraint: constraint,\n resolvedVersion,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n }\n\n // Transitive dependencies (present only in the lock file, never declared\n // directly in package.json) would otherwise never become a Dependency_Node,\n // so they'd silently skip vulnerability scanning entirely. Add them here.\n const directNames = new Set(dependencies.map(d => d.name));\n const lockRelativePath = relativeManifest.replace(/package\\.json$/, 'package-lock.json');\n const npmLockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(npmLockPath)) {\n // package-lock.json (v2/v3) carries a per-package \"dev\" flag \u2014 parse it\n // directly so transitive dev-only dependencies keep the right scope.\n dependencies.push(...extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames));\n } else {\n // pnpm-lock.yaml, yarn.lock, or a lockfileVersion 1 package-lock.json:\n // resolvedVersions already has every package the lock file resolved,\n // direct or transitive \u2014 it just doesn't carry a dev/prod distinction,\n // so default newly-added transitive entries to 'production'.\n for (const [name, version] of resolvedVersions) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n declaredConstraint: version,\n resolvedVersion: version,\n scope: 'production',\n ecosystem: 'npm',\n sourceManifest: relativeManifest,\n });\n }\n }\n\n return dependencies;\n}\n\n/**\n * Find every npm package name that resolves to more than one distinct\n * version somewhere in package-lock.json's dependency tree. npm's nested\n * node_modules layout can install different versions of the same package\n * at different paths (e.g. a top-level `qs@6.16.0` alongside a\n * `body-parser`-nested `qs@6.15.3`) \u2014 but a Dependency_Node only records one\n * `resolved_version`. Used by vulnerability enrichment to also check every\n * other installed copy, not just whichever version ended up recorded.\n *\n * Only the top-level package-lock.json (lockfile v2/v3 \"packages\" map) is\n * checked \u2014 a monorepo with per-package lock files, or pnpm/yarn projects,\n * won't get this extra check (they still get the single recorded version).\n *\n * Returns a map from package name to the full set of distinct versions\n * found; names with only one version are omitted.\n */\nexport function findNpmDuplicateVersions(projectRoot: string): Map<string, Set<string>> {\n const versionsByName = new Map<string, Set<string>>();\n\n const lockPath = path.join(projectRoot, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return versionsByName;\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch {\n return versionsByName;\n }\n\n if (typeof lockData !== 'object' || lockData === null) return versionsByName;\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return versionsByName;\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name) continue;\n\n if (!versionsByName.has(name)) versionsByName.set(name, new Set());\n versionsByName.get(name)!.add(version);\n }\n\n for (const [name, versions] of versionsByName) {\n if (versions.size <= 1) versionsByName.delete(name);\n }\n\n return versionsByName;\n}\n\n/**\n * Extract every package listed in package-lock.json's \"packages\" map (lockfile\n * v2/v3) that isn't already a direct dependency. These are transitive-only\n * packages \u2014 e.g. a sub-dependency three levels deep \u2014 that `npm audit` reports\n * on but that package.json never mentions.\n */\nfunction extractTransitiveNpmDependencies(\n manifestDir: string,\n lockRelativePath: string,\n directNames: Set<string>,\n): ParsedDependency[] {\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return [];\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse ${lockRelativePath} for transitive dependencies: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n if (typeof lockData !== 'object' || lockData === null) return [];\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return [];\n\n const transitive: ParsedDependency[] = [];\n const seen = new Set<string>();\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name || directNames.has(name) || seen.has(name)) continue;\n\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n seen.add(name);\n const license = normalizeLicenseField(pkg);\n transitive.push({\n name,\n // No real semver range exists at the project level for a transitive\n // package \u2014 the resolved version is the only constraint we have.\n declaredConstraint: version,\n resolvedVersion: version,\n scope: pkg.dev === true ? 'development' : 'production',\n ecosystem: 'npm',\n sourceManifest: lockRelativePath,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n return transitive;\n}\n\n/**\n * Attempt to load resolved versions from package-lock.json, pnpm-lock.yaml,\n * or yarn.lock in the given directory. Returns an empty map if none is available.\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n // Try package-lock.json first (preferred \u2014 npm)\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (fs.existsSync(lockPath)) {\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n const lockData = JSON.parse(lockContent);\n extractFromPackageLock(lockData, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse package-lock.json: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Try pnpm-lock.yaml (pnpm)\n const pnpmLockPath = path.join(manifestDir, 'pnpm-lock.yaml');\n if (fs.existsSync(pnpmLockPath)) {\n try {\n const pnpmContent = fs.readFileSync(pnpmLockPath, 'utf8');\n extractFromPnpmLock(pnpmContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse pnpm-lock.yaml: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Fall back to yarn.lock\n const yarnLockPath = path.join(manifestDir, 'yarn.lock');\n if (fs.existsSync(yarnLockPath)) {\n try {\n const yarnContent = fs.readFileSync(yarnLockPath, 'utf8');\n extractFromYarnLock(yarnContent, map);\n return map;\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse yarn.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return map;\n}\n\n/**\n * Load each installed package's own SPDX license from package-lock.json's\n * \"packages\" map (npm v7+ lockfile v2/v3). npm records the \"license\" field\n * from each package's own package.json there when it exists \u2014 this is the\n * dependency's *own* license, not the consuming project's, so it's the\n * correct source for license-compliance checks (issue #39 follow-up: the\n * project's own root package.json \"license\" field was previously applied to\n * every dependency, which is both wrong when present and leaves every\n * transitive-only package \u2014 the majority in most trees \u2014 as \"unknown\").\n *\n * pnpm-lock.yaml and yarn.lock don't carry a comparably structured per-package\n * license field, so this only covers package-lock.json for now; other lock\n * formats fall back to no license data, same as before this fix.\n */\nfunction loadNpmLicenses(manifestDir: string): LicenseMap {\n const map: LicenseMap = new Map();\n\n const lockPath = path.join(manifestDir, 'package-lock.json');\n if (!fs.existsSync(lockPath)) return map;\n\n let lockData: unknown;\n try {\n lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));\n } catch (err) {\n logWarn(`[sec:npm] Failed to parse package-lock.json for license data: ${err instanceof Error ? err.message : String(err)}`);\n return map;\n }\n\n if (typeof lockData !== 'object' || lockData === null) return map;\n const packages = (lockData as Record<string, unknown>).packages;\n if (typeof packages !== 'object' || packages === null) return map;\n\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip the root project entry\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n\n const name = key.replace(/^.*node_modules\\//, '');\n if (!name || map.has(name)) continue;\n\n const license = normalizeLicenseField(pkg);\n if (license !== undefined) map.set(name, license);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pnpm-lock.yaml (v5, v6, v9 formats).\n *\n * v5/v6: packages section with keys like \"/express/4.18.2\" or \"/express@4.18.2\"\n * v9: snapshots section with keys like \"express@4.18.2\" and\n * packages section with \"express@4.18.2: {version: ...}\" (optional)\n */\nfunction extractFromPnpmLock(content: string, map: ResolvedVersionMap): void {\n // v9 format: package entries under `packages:` or `snapshots:`\n // Key format: \"name@version\" or \"@scope/name@version\"\n // We extract name@version pairs from both sections.\n\n // Match lines like: /express/4.18.2: or /express@4.18.2: or express@4.18.2:\n for (const m of content.matchAll(/^[\\s]*\\/?(@?[a-zA-Z0-9._-]+(?:\\/[a-zA-Z0-9._-]+)?)[@/]([0-9][a-zA-Z0-9._-]*)(?:\\(.*?\\))?:/gm)) {\n const name = m[1].replace(/^\\//, ''); // strip leading slash for scoped packages\n const version = m[2];\n if (name && version && !map.has(name)) {\n map.set(name, version);\n }\n }\n}\n\n/**\n * Extract resolved versions from package-lock.json (supports v2 and v3 format).\n */\nfunction extractFromPackageLock(lockData: unknown, map: ResolvedVersionMap): void {\n if (typeof lockData !== 'object' || lockData === null) return;\n const lock = lockData as Record<string, unknown>;\n\n // lockfileVersion 2/3: uses \"packages\" field with \"\" as root\n const packages = lock.packages;\n if (typeof packages === 'object' && packages !== null) {\n for (const [key, value] of Object.entries(packages as Record<string, unknown>)) {\n if (key === '') continue; // skip root\n if (typeof value !== 'object' || value === null) continue;\n const pkg = value as Record<string, unknown>;\n const version = pkg.version;\n if (typeof version !== 'string') continue;\n\n // Key format: \"node_modules/<name>\" or \"node_modules/@scope/name\"\n const name = key.replace(/^.*node_modules\\//, '');\n if (name && !map.has(name)) {\n map.set(name, version);\n }\n }\n return;\n }\n\n // lockfileVersion 1: uses \"dependencies\" field\n const dependencies = lock.dependencies;\n if (typeof dependencies === 'object' && dependencies !== null) {\n extractFromLockV1Dependencies(dependencies as Record<string, unknown>, map);\n }\n}\n\n/**\n * Recursively extract versions from lockfileVersion 1 dependencies.\n */\nfunction extractFromLockV1Dependencies(\n deps: Record<string, unknown>,\n map: ResolvedVersionMap,\n): void {\n for (const [name, value] of Object.entries(deps)) {\n if (typeof value !== 'object' || value === null) continue;\n const dep = value as Record<string, unknown>;\n const version = dep.version;\n if (typeof version === 'string' && !map.has(name)) {\n map.set(name, version);\n }\n // Recurse into nested dependencies\n const nested = dep.dependencies;\n if (typeof nested === 'object' && nested !== null) {\n extractFromLockV1Dependencies(nested as Record<string, unknown>, map);\n }\n }\n}\n\n/**\n * Extract resolved versions from yarn.lock (v1 format).\n * yarn.lock uses a custom format, not JSON.\n */\nfunction extractFromYarnLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let currentPackageName: string | null = null;\n\n for (const line of lines) {\n // Skip comments and empty lines\n if (line.startsWith('#') || line.trim() === '') {\n currentPackageName = null;\n continue;\n }\n\n // Package header line: \"package-name@version-range:\" or \"@scope/name@range:\"\n // Can also be: \"package-name@range1, package-name@range2:\"\n if (!line.startsWith(' ') && line.endsWith(':')) {\n const header = line.slice(0, -1); // remove trailing ':'\n // Extract package name from the first entry (before @version)\n const name = extractPackageNameFromYarnHeader(header);\n currentPackageName = name;\n continue;\n }\n\n // Version line: \" version \"x.y.z\"\"\n if (currentPackageName && line.trim().startsWith('version ')) {\n const versionMatch = line.match(/version\\s+\"([^\"]+)\"/);\n if (versionMatch && !map.has(currentPackageName)) {\n map.set(currentPackageName, versionMatch[1]);\n }\n currentPackageName = null;\n }\n }\n}\n\n/**\n * Extract the package name from a yarn.lock header line.\n * Handles formats like:\n * - \"express@^4.17.1\"\n * - \"@types/node@^18.0.0\"\n * - \"express@^4.17.1, express@^4.18.0\"\n * - '\"express@^4.17.1\"'\n */\nfunction extractPackageNameFromYarnHeader(header: string): string | null {\n // Take the first entry if comma-separated\n const firstEntry = header.split(',')[0].trim();\n // Remove surrounding quotes if present\n const cleaned = firstEntry.replace(/^[\"']|[\"']$/g, '');\n\n // Find the last @ that separates name from version\n // For scoped packages (@scope/name@version), we need the last @\n const lastAtIndex = cleaned.lastIndexOf('@');\n if (lastAtIndex <= 0) return null; // no @ or starts with @ (scoped without version)\n\n // For scoped packages, check if the first char is @\n if (cleaned.startsWith('@')) {\n // @scope/name@version \u2014 find the @ after the scope\n const afterScope = cleaned.indexOf('/', 1);\n if (afterScope === -1) return null;\n const atAfterName = cleaned.indexOf('@', afterScope);\n if (atAfterName === -1) return cleaned; // no version part\n return cleaned.slice(0, atAfterName);\n }\n\n return cleaned.slice(0, lastAtIndex);\n}\n\n/**\n * Normalize a package's `license`/`licenses` field into a single SPDX-ish\n * string. Handles the string format (`\"MIT\"`), the object format\n * (`{\"type\":\"MIT\"}`), and the legacy `licenses` array format. Used both for\n * package-lock.json's per-package `license` entries and, if ever needed,\n * a raw package.json object.\n */\nfunction normalizeLicenseField(pkg: Record<string, unknown>): string | undefined {\n const raw = pkg['license'];\n if (typeof raw === 'string' && raw.trim() !== '') {\n return raw.trim();\n }\n if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n const licObj = raw as Record<string, unknown>;\n if (typeof licObj['type'] === 'string' && licObj['type'].trim() !== '') {\n return licObj['type'].trim();\n }\n }\n // Handle legacy \"licenses\" array\n const licenses = pkg['licenses'];\n if (Array.isArray(licenses) && licenses.length > 0) {\n const first = licenses[0];\n if (typeof first === 'object' && first !== null) {\n const lic = first as Record<string, unknown>;\n if (typeof lic['type'] === 'string' && lic['type'].trim() !== '') {\n return lic['type'].trim();\n }\n }\n if (typeof first === 'string' && first.trim() !== '') {\n return first.trim();\n }\n }\n return undefined;\n}\n\n/**\n * Validate that a dependency name follows npm naming conventions.\n * Must be non-empty, not start with a dot or underscore (unless scoped),\n * and contain only valid characters.\n */\nfunction isValidDependencyName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 214) return false;\n\n // Scoped packages: @scope/name\n if (name.startsWith('@')) {\n const parts = name.split('/');\n if (parts.length !== 2) return false;\n if (!parts[0] || !parts[1]) return false;\n return true;\n }\n\n // Unscoped: must not start with . or _\n if (name.startsWith('.') || name.startsWith('_')) return false;\n\n return true;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -82,6 +82,19 @@ async function parseNugetManifest(manifestPath, projectRoot) {
82
82
  (0, import_errors.logWarn)(`[sec:nuget] Failed to read ${relativePackagesConfig}: ${err instanceof Error ? err.message : String(err)}`);
83
83
  }
84
84
  }
85
+ const directNames = new Set(dependencies.map((d) => d.name.toLowerCase()));
86
+ const lockRelativePath = path.relative(projectRoot, path.join(manifestDir, "packages.lock.json")).replace(/\\/g, "/");
87
+ for (const [name, version] of resolvedVersions) {
88
+ if (directNames.has(name)) continue;
89
+ dependencies.push({
90
+ name,
91
+ declaredConstraint: version,
92
+ resolvedVersion: version,
93
+ scope: "production",
94
+ ecosystem: "nuget",
95
+ sourceManifest: lockRelativePath
96
+ });
97
+ }
85
98
  return dependencies;
86
99
  }
87
100
  function extractPackageReferences(content, relativeManifest) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/security/manifest/plugins/nuget.ts"],
4
- "sourcesContent": ["/**\n * NuGet Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture csproj parser to extract version constraints,\n * resolved versions (from packages.lock.json), and dependency scopes for security analysis.\n *\n * Reuses `csprojParser` from `src/architecture/manifest/csproj.ts` for discovery and\n * basic parsing, then layers on version/scope extraction.\n *\n * Handles:\n * - Inline attribute format: `<PackageReference Include=\"Name\" Version=\"1.0.0\"/>`\n * - Multi-line child element format: `<PackageReference Include=\"Name\"><Version>1.0.0</Version></PackageReference>`\n * - Legacy packages.config: `<package id=\"Name\" version=\"1.0.0\" targetFramework=\"...\"/>`\n * - Lock file packages.lock.json for resolved versions\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Resolved version map: package name (lower-cased) \u2192 resolved version string.\n * Built from packages.lock.json.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse a .csproj manifest and extract NuGet dependency declarations\n * with version constraints, scopes, and resolved versions from packages.lock.json.\n * Also parses a packages.config in the same directory if present.\n *\n * @param manifestPath - Absolute path to the .csproj file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parseNugetManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the .csproj content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:nuget] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from packages.lock.json\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract project-level license from .csproj\n const license = extractNugetLicense(content);\n\n const dependencies: ParsedDependency[] = [];\n\n // Extract from .csproj PackageReference elements\n const csprojDeps = extractPackageReferences(content, relativeManifest);\n for (const dep of csprojDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name.toLowerCase());\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope: dep.scope,\n ecosystem: 'nuget',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n // Also parse packages.config in the same directory (legacy NuGet)\n const packagesConfigPath = path.join(manifestDir, 'packages.config');\n if (fs.existsSync(packagesConfigPath)) {\n const relativePackagesConfig = path.relative(projectRoot, packagesConfigPath).replace(/\\\\/g, '/');\n try {\n const packagesConfigContent = fs.readFileSync(packagesConfigPath, 'utf8');\n const legacyDeps = extractPackagesConfig(packagesConfigContent, relativePackagesConfig);\n for (const dep of legacyDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name.toLowerCase());\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope: dep.scope,\n ecosystem: 'nuget',\n sourceManifest: relativePackagesConfig,\n ...(license !== undefined ? { license } : {}),\n });\n }\n } catch (err) {\n logWarn(`[sec:nuget] Failed to read ${relativePackagesConfig}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return dependencies;\n}\n\n/**\n * A raw dependency entry extracted from a .csproj or packages.config file.\n */\ninterface RawDependency {\n name: string;\n constraint: string;\n scope: ParsedDependency['scope'];\n}\n\n/**\n * Extract PackageReference entries from .csproj XML content.\n *\n * Handles:\n * - Inline: `<PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.1\" />`\n * - With PrivateAssets=\"all\": `<PackageReference Include=\"MSTest.TestFramework\" Version=\"3.1.1\" PrivateAssets=\"all\" />`\n * - Multi-line child: `<PackageReference Include=\"Name\"><Version>1.0.0</Version></PackageReference>`\n */\nfunction extractPackageReferences(content: string, relativeManifest: string): RawDependency[] {\n const deps: RawDependency[] = [];\n\n // Match single-line <PackageReference ... /> and multi-line <PackageReference ...>...</PackageReference>\n // The regex captures the full element including potential child content\n const elementRegex = /<PackageReference\\b([^>]*?)(?:\\/>|>([\\s\\S]*?)<\\/PackageReference>)/gi;\n let match: RegExpExecArray | null;\n\n while ((match = elementRegex.exec(content)) !== null) {\n const attributes = match[1] ?? '';\n const innerContent = match[2] ?? '';\n\n // Extract the Include attribute (package name)\n const includeMatch = attributes.match(/\\bInclude\\s*=\\s*\"([^\"]+)\"/i);\n if (!includeMatch) {\n continue;\n }\n const name = includeMatch[1].trim();\n if (!name) {\n logWarn(`[sec:nuget] Empty Include attribute in PackageReference at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n // Determine scope: PrivateAssets=\"all\" indicates a development/build-only dependency\n const privateAssetsMatch = attributes.match(/\\bPrivateAssets\\s*=\\s*\"([^\"]*)\"/i);\n const scope: ParsedDependency['scope'] =\n privateAssetsMatch && privateAssetsMatch[1].toLowerCase() === 'all'\n ? 'development'\n : 'production';\n\n // Extract version from Version attribute (inline format)\n let constraint: string | null = null;\n const versionAttrMatch = attributes.match(/\\bVersion\\s*=\\s*\"([^\"]+)\"/i);\n if (versionAttrMatch && versionAttrMatch[1].trim() !== '') {\n constraint = versionAttrMatch[1].trim();\n }\n\n // If no inline version, check for <Version> child element (multi-line format)\n if (!constraint && innerContent) {\n const versionElemMatch = innerContent.match(/<Version\\s*>\\s*([^<]+)\\s*<\\/Version>/i);\n if (versionElemMatch && versionElemMatch[1].trim() !== '') {\n constraint = versionElemMatch[1].trim();\n }\n }\n\n if (!constraint) {\n // Version may be managed centrally (Directory.Packages.props) \u2014 use \"*\"\n constraint = '*';\n }\n\n deps.push({ name, constraint, scope });\n }\n\n return deps;\n}\n\n/**\n * Extract package entries from a legacy packages.config XML file.\n *\n * Format: `<package id=\"Newtonsoft.Json\" version=\"13.0.1\" targetFramework=\"net48\" />`\n * All packages from packages.config are treated as production scope.\n */\nfunction extractPackagesConfig(content: string, relativeManifest: string): RawDependency[] {\n const deps: RawDependency[] = [];\n\n const packageRegex = /<package\\b([^>]*?)\\/>/gi;\n let match: RegExpExecArray | null;\n\n while ((match = packageRegex.exec(content)) !== null) {\n const attributes = match[1] ?? '';\n\n const idMatch = attributes.match(/\\bid\\s*=\\s*\"([^\"]+)\"/i);\n if (!idMatch) continue;\n const name = idMatch[1].trim();\n if (!name) {\n logWarn(`[sec:nuget] Empty id attribute in packages.config at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n const versionMatch = attributes.match(/\\bversion\\s*=\\s*\"([^\"]+)\"/i);\n const constraint = versionMatch && versionMatch[1].trim() !== '' ? versionMatch[1].trim() : '*';\n\n deps.push({ name, constraint, scope: 'production' });\n }\n\n return deps;\n}\n\n/**\n * Attempt to load resolved versions from packages.lock.json in the given directory.\n * Returns an empty map if the lock file is not available.\n *\n * packages.lock.json format:\n * ```json\n * {\n * \"version\": 1,\n * \"dependencies\": {\n * \"net8.0\": {\n * \"Newtonsoft.Json\": {\n * \"type\": \"Direct\",\n * \"resolved\": \"13.0.3\",\n * ...\n * }\n * }\n * }\n * }\n * ```\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n const lockPath = path.join(manifestDir, 'packages.lock.json');\n if (!fs.existsSync(lockPath)) {\n return map;\n }\n\n try {\n const raw = fs.readFileSync(lockPath, 'utf8');\n const parsed = JSON.parse(raw);\n extractFromPackagesLock(parsed, map);\n } catch (err) {\n logWarn(`[sec:nuget] Failed to parse packages.lock.json: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from a parsed packages.lock.json object.\n *\n * Iterates over all target frameworks and their packages, storing\n * the `resolved` version for each package (keyed by lower-cased name).\n */\nfunction extractFromPackagesLock(\n parsed: unknown,\n map: ResolvedVersionMap,\n): void {\n if (typeof parsed !== 'object' || parsed === null) return;\n\n const root = parsed as Record<string, unknown>;\n const dependencies = root['dependencies'];\n if (typeof dependencies !== 'object' || dependencies === null) return;\n\n // Iterate over target frameworks (e.g. \"net8.0\", \"net48\")\n for (const framework of Object.values(dependencies as Record<string, unknown>)) {\n if (typeof framework !== 'object' || framework === null) continue;\n\n // Iterate over packages within the framework\n for (const [packageName, packageInfo] of Object.entries(framework as Record<string, unknown>)) {\n if (map.has(packageName.toLowerCase())) continue; // first occurrence wins\n if (typeof packageInfo !== 'object' || packageInfo === null) continue;\n\n const info = packageInfo as Record<string, unknown>;\n const resolved = info['resolved'];\n if (typeof resolved === 'string' && resolved.trim() !== '') {\n map.set(packageName.toLowerCase(), resolved.trim());\n }\n }\n }\n}\n\n/**\n * Extract the PackageLicenseExpression from a .csproj file.\n * Handles: <PackageLicenseExpression>MIT</PackageLicenseExpression>\n */\nfunction extractNugetLicense(content: string): string | undefined {\n const match = content.match(/<PackageLicenseExpression\\s*>\\s*([^<]+)\\s*<\\/PackageLicenseExpression>/i);\n if (match && match[1].trim() !== '') {\n return match[1].trim();\n }\n return undefined;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * NuGet Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture csproj parser to extract version constraints,\n * resolved versions (from packages.lock.json), and dependency scopes for security analysis.\n *\n * Reuses `csprojParser` from `src/architecture/manifest/csproj.ts` for discovery and\n * basic parsing, then layers on version/scope extraction.\n *\n * Handles:\n * - Inline attribute format: `<PackageReference Include=\"Name\" Version=\"1.0.0\"/>`\n * - Multi-line child element format: `<PackageReference Include=\"Name\"><Version>1.0.0</Version></PackageReference>`\n * - Legacy packages.config: `<package id=\"Name\" version=\"1.0.0\" targetFramework=\"...\"/>`\n * - Lock file packages.lock.json for resolved versions\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Resolved version map: package name (lower-cased) \u2192 resolved version string.\n * Built from packages.lock.json.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse a .csproj manifest and extract NuGet dependency declarations\n * with version constraints, scopes, and resolved versions from packages.lock.json.\n * Also parses a packages.config in the same directory if present.\n *\n * @param manifestPath - Absolute path to the .csproj file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parseNugetManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the .csproj content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:nuget] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from packages.lock.json\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract project-level license from .csproj\n const license = extractNugetLicense(content);\n\n const dependencies: ParsedDependency[] = [];\n\n // Extract from .csproj PackageReference elements\n const csprojDeps = extractPackageReferences(content, relativeManifest);\n for (const dep of csprojDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name.toLowerCase());\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope: dep.scope,\n ecosystem: 'nuget',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n // Also parse packages.config in the same directory (legacy NuGet)\n const packagesConfigPath = path.join(manifestDir, 'packages.config');\n if (fs.existsSync(packagesConfigPath)) {\n const relativePackagesConfig = path.relative(projectRoot, packagesConfigPath).replace(/\\\\/g, '/');\n try {\n const packagesConfigContent = fs.readFileSync(packagesConfigPath, 'utf8');\n const legacyDeps = extractPackagesConfig(packagesConfigContent, relativePackagesConfig);\n for (const dep of legacyDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name.toLowerCase());\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope: dep.scope,\n ecosystem: 'nuget',\n sourceManifest: relativePackagesConfig,\n ...(license !== undefined ? { license } : {}),\n });\n }\n } catch (err) {\n logWarn(`[sec:nuget] Failed to read ${relativePackagesConfig}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Transitive-only packages (present in packages.lock.json but never\n // declared in the .csproj) would otherwise never become a Dependency_Node\n // and would silently skip vulnerability scanning. resolvedVersions already\n // has every package packages.lock.json resolved, direct or transitive\n // (lower-cased \u2014 no original casing is retained by the lock parser).\n const directNames = new Set(dependencies.map(d => d.name.toLowerCase()));\n const lockRelativePath = path.relative(projectRoot, path.join(manifestDir, 'packages.lock.json')).replace(/\\\\/g, '/');\n for (const [name, version] of resolvedVersions) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n declaredConstraint: version,\n resolvedVersion: version,\n scope: 'production',\n ecosystem: 'nuget',\n sourceManifest: lockRelativePath,\n });\n }\n\n return dependencies;\n}\n\n/**\n * A raw dependency entry extracted from a .csproj or packages.config file.\n */\ninterface RawDependency {\n name: string;\n constraint: string;\n scope: ParsedDependency['scope'];\n}\n\n/**\n * Extract PackageReference entries from .csproj XML content.\n *\n * Handles:\n * - Inline: `<PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.1\" />`\n * - With PrivateAssets=\"all\": `<PackageReference Include=\"MSTest.TestFramework\" Version=\"3.1.1\" PrivateAssets=\"all\" />`\n * - Multi-line child: `<PackageReference Include=\"Name\"><Version>1.0.0</Version></PackageReference>`\n */\nfunction extractPackageReferences(content: string, relativeManifest: string): RawDependency[] {\n const deps: RawDependency[] = [];\n\n // Match single-line <PackageReference ... /> and multi-line <PackageReference ...>...</PackageReference>\n // The regex captures the full element including potential child content\n const elementRegex = /<PackageReference\\b([^>]*?)(?:\\/>|>([\\s\\S]*?)<\\/PackageReference>)/gi;\n let match: RegExpExecArray | null;\n\n while ((match = elementRegex.exec(content)) !== null) {\n const attributes = match[1] ?? '';\n const innerContent = match[2] ?? '';\n\n // Extract the Include attribute (package name)\n const includeMatch = attributes.match(/\\bInclude\\s*=\\s*\"([^\"]+)\"/i);\n if (!includeMatch) {\n continue;\n }\n const name = includeMatch[1].trim();\n if (!name) {\n logWarn(`[sec:nuget] Empty Include attribute in PackageReference at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n // Determine scope: PrivateAssets=\"all\" indicates a development/build-only dependency\n const privateAssetsMatch = attributes.match(/\\bPrivateAssets\\s*=\\s*\"([^\"]*)\"/i);\n const scope: ParsedDependency['scope'] =\n privateAssetsMatch && privateAssetsMatch[1].toLowerCase() === 'all'\n ? 'development'\n : 'production';\n\n // Extract version from Version attribute (inline format)\n let constraint: string | null = null;\n const versionAttrMatch = attributes.match(/\\bVersion\\s*=\\s*\"([^\"]+)\"/i);\n if (versionAttrMatch && versionAttrMatch[1].trim() !== '') {\n constraint = versionAttrMatch[1].trim();\n }\n\n // If no inline version, check for <Version> child element (multi-line format)\n if (!constraint && innerContent) {\n const versionElemMatch = innerContent.match(/<Version\\s*>\\s*([^<]+)\\s*<\\/Version>/i);\n if (versionElemMatch && versionElemMatch[1].trim() !== '') {\n constraint = versionElemMatch[1].trim();\n }\n }\n\n if (!constraint) {\n // Version may be managed centrally (Directory.Packages.props) \u2014 use \"*\"\n constraint = '*';\n }\n\n deps.push({ name, constraint, scope });\n }\n\n return deps;\n}\n\n/**\n * Extract package entries from a legacy packages.config XML file.\n *\n * Format: `<package id=\"Newtonsoft.Json\" version=\"13.0.1\" targetFramework=\"net48\" />`\n * All packages from packages.config are treated as production scope.\n */\nfunction extractPackagesConfig(content: string, relativeManifest: string): RawDependency[] {\n const deps: RawDependency[] = [];\n\n const packageRegex = /<package\\b([^>]*?)\\/>/gi;\n let match: RegExpExecArray | null;\n\n while ((match = packageRegex.exec(content)) !== null) {\n const attributes = match[1] ?? '';\n\n const idMatch = attributes.match(/\\bid\\s*=\\s*\"([^\"]+)\"/i);\n if (!idMatch) continue;\n const name = idMatch[1].trim();\n if (!name) {\n logWarn(`[sec:nuget] Empty id attribute in packages.config at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n const versionMatch = attributes.match(/\\bversion\\s*=\\s*\"([^\"]+)\"/i);\n const constraint = versionMatch && versionMatch[1].trim() !== '' ? versionMatch[1].trim() : '*';\n\n deps.push({ name, constraint, scope: 'production' });\n }\n\n return deps;\n}\n\n/**\n * Attempt to load resolved versions from packages.lock.json in the given directory.\n * Returns an empty map if the lock file is not available.\n *\n * packages.lock.json format:\n * ```json\n * {\n * \"version\": 1,\n * \"dependencies\": {\n * \"net8.0\": {\n * \"Newtonsoft.Json\": {\n * \"type\": \"Direct\",\n * \"resolved\": \"13.0.3\",\n * ...\n * }\n * }\n * }\n * }\n * ```\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n const lockPath = path.join(manifestDir, 'packages.lock.json');\n if (!fs.existsSync(lockPath)) {\n return map;\n }\n\n try {\n const raw = fs.readFileSync(lockPath, 'utf8');\n const parsed = JSON.parse(raw);\n extractFromPackagesLock(parsed, map);\n } catch (err) {\n logWarn(`[sec:nuget] Failed to parse packages.lock.json: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from a parsed packages.lock.json object.\n *\n * Iterates over all target frameworks and their packages, storing\n * the `resolved` version for each package (keyed by lower-cased name).\n */\nfunction extractFromPackagesLock(\n parsed: unknown,\n map: ResolvedVersionMap,\n): void {\n if (typeof parsed !== 'object' || parsed === null) return;\n\n const root = parsed as Record<string, unknown>;\n const dependencies = root['dependencies'];\n if (typeof dependencies !== 'object' || dependencies === null) return;\n\n // Iterate over target frameworks (e.g. \"net8.0\", \"net48\")\n for (const framework of Object.values(dependencies as Record<string, unknown>)) {\n if (typeof framework !== 'object' || framework === null) continue;\n\n // Iterate over packages within the framework\n for (const [packageName, packageInfo] of Object.entries(framework as Record<string, unknown>)) {\n if (map.has(packageName.toLowerCase())) continue; // first occurrence wins\n if (typeof packageInfo !== 'object' || packageInfo === null) continue;\n\n const info = packageInfo as Record<string, unknown>;\n const resolved = info['resolved'];\n if (typeof resolved === 'string' && resolved.trim() !== '') {\n map.set(packageName.toLowerCase(), resolved.trim());\n }\n }\n }\n}\n\n/**\n * Extract the PackageLicenseExpression from a .csproj file.\n * Handles: <PackageLicenseExpression>MIT</PackageLicenseExpression>\n */\nfunction extractNugetLicense(content: string): string | undefined {\n const match = content.match(/<PackageLicenseExpression\\s*>\\s*([^<]+)\\s*<\\/PackageLicenseExpression>/i);\n if (match && match[1].trim() !== '') {\n return match[1].trim();\n }\n return undefined;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,SAAoB;AACpB,WAAsB;AAEtB,oBAAwB;AAiBxB,eAAsB,mBACpB,cACA,aAC6B;AAC7B,QAAM,cAAc,KAAK,QAAQ,YAAY;AAC7C,QAAM,mBAAmB,KAAK,SAAS,aAAa,YAAY,EAAE,QAAQ,OAAO,GAAG;AAGpF,MAAI;AACJ,MAAI;AACF,cAAU,GAAG,aAAa,cAAc,MAAM;AAAA,EAChD,SAAS,KAAK;AACZ,+BAAQ,8BAA8B,gBAAgB,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC7G,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,mBAAmB,qBAAqB,WAAW;AAGzD,QAAM,UAAU,oBAAoB,OAAO;AAE3C,QAAM,eAAmC,CAAC;AAG1C,QAAM,aAAa,yBAAyB,SAAS,gBAAgB;AACrE,aAAW,OAAO,YAAY;AAC5B,UAAM,kBAAkB,iBAAiB,IAAI,IAAI,KAAK,YAAY,CAAC;AACnE,iBAAa,KAAK;AAAA,MAChB,MAAM,IAAI;AAAA,MACV,oBAAoB,IAAI;AAAA,MACxB;AAAA,MACA,OAAO,IAAI;AAAA,MACX,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AAGA,QAAM,qBAAqB,KAAK,KAAK,aAAa,iBAAiB;AACnE,MAAI,GAAG,WAAW,kBAAkB,GAAG;AACrC,UAAM,yBAAyB,KAAK,SAAS,aAAa,kBAAkB,EAAE,QAAQ,OAAO,GAAG;AAChG,QAAI;AACF,YAAM,wBAAwB,GAAG,aAAa,oBAAoB,MAAM;AACxE,YAAM,aAAa,sBAAsB,uBAAuB,sBAAsB;AACtF,iBAAW,OAAO,YAAY;AAC5B,cAAM,kBAAkB,iBAAiB,IAAI,IAAI,KAAK,YAAY,CAAC;AACnE,qBAAa,KAAK;AAAA,UAChB,MAAM,IAAI;AAAA,UACV,oBAAoB,IAAI;AAAA,UACxB;AAAA,UACA,OAAO,IAAI;AAAA,UACX,WAAW;AAAA,UACX,gBAAgB;AAAA,UAChB,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,QAC7C,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,iCAAQ,8BAA8B,sBAAsB,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IACrH;AAAA,EACF;AAOA,QAAM,cAAc,IAAI,IAAI,aAAa,IAAI,OAAK,EAAE,KAAK,YAAY,CAAC,CAAC;AACvE,QAAM,mBAAmB,KAAK,SAAS,aAAa,KAAK,KAAK,aAAa,oBAAoB,CAAC,EAAE,QAAQ,OAAO,GAAG;AACpH,aAAW,CAAC,MAAM,OAAO,KAAK,kBAAkB;AAC9C,QAAI,YAAY,IAAI,IAAI,EAAG;AAC3B,iBAAa,KAAK;AAAA,MAChB;AAAA,MACA,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,OAAO;AAAA,MACP,WAAW;AAAA,MACX,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAmBA,SAAS,yBAAyB,SAAiB,kBAA2C;AAC5F,QAAM,OAAwB,CAAC;AAI/B,QAAM,eAAe;AACrB,MAAI;AAEJ,UAAQ,QAAQ,aAAa,KAAK,OAAO,OAAO,MAAM;AACpD,UAAM,aAAa,MAAM,CAAC,KAAK;AAC/B,UAAM,eAAe,MAAM,CAAC,KAAK;AAGjC,UAAM,eAAe,WAAW,MAAM,4BAA4B;AAClE,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AACA,UAAM,OAAO,aAAa,CAAC,EAAE,KAAK;AAClC,QAAI,CAAC,MAAM;AACT,iCAAQ,8DAA8D,gBAAgB,kBAAa;AACnG;AAAA,IACF;AAGA,UAAM,qBAAqB,WAAW,MAAM,kCAAkC;AAC9E,UAAM,QACJ,sBAAsB,mBAAmB,CAAC,EAAE,YAAY,MAAM,QAC1D,gBACA;AAGN,QAAI,aAA4B;AAChC,UAAM,mBAAmB,WAAW,MAAM,4BAA4B;AACtE,QAAI,oBAAoB,iBAAiB,CAAC,EAAE,KAAK,MAAM,IAAI;AACzD,mBAAa,iBAAiB,CAAC,EAAE,KAAK;AAAA,IACxC;AAGA,QAAI,CAAC,cAAc,cAAc;AAC/B,YAAM,mBAAmB,aAAa,MAAM,uCAAuC;AACnF,UAAI,oBAAoB,iBAAiB,CAAC,EAAE,KAAK,MAAM,IAAI;AACzD,qBAAa,iBAAiB,CAAC,EAAE,KAAK;AAAA,MACxC;AAAA,IACF;AAEA,QAAI,CAAC,YAAY;AAEf,mBAAa;AAAA,IACf;AAEA,SAAK,KAAK,EAAE,MAAM,YAAY,MAAM,CAAC;AAAA,EACvC;AAEA,SAAO;AACT;AAQA,SAAS,sBAAsB,SAAiB,kBAA2C;AACzF,QAAM,OAAwB,CAAC;AAE/B,QAAM,eAAe;AACrB,MAAI;AAEJ,UAAQ,QAAQ,aAAa,KAAK,OAAO,OAAO,MAAM;AACpD,UAAM,aAAa,MAAM,CAAC,KAAK;AAE/B,UAAM,UAAU,WAAW,MAAM,uBAAuB;AACxD,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,QAAQ,CAAC,EAAE,KAAK;AAC7B,QAAI,CAAC,MAAM;AACT,iCAAQ,wDAAwD,gBAAgB,kBAAa;AAC7F;AAAA,IACF;AAEA,UAAM,eAAe,WAAW,MAAM,4BAA4B;AAClE,UAAM,aAAa,gBAAgB,aAAa,CAAC,EAAE,KAAK,MAAM,KAAK,aAAa,CAAC,EAAE,KAAK,IAAI;AAE5F,SAAK,KAAK,EAAE,MAAM,YAAY,OAAO,aAAa,CAAC;AAAA,EACrD;AAEA,SAAO;AACT;AAsBA,SAAS,qBAAqB,aAAyC;AACrE,QAAM,MAA0B,oBAAI,IAAI;AAExC,QAAM,WAAW,KAAK,KAAK,aAAa,oBAAoB;AAC5D,MAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,UAAU,MAAM;AAC5C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,4BAAwB,QAAQ,GAAG;AAAA,EACrC,SAAS,KAAK;AACZ,+BAAQ,mDAAmD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EAC/G;AAEA,SAAO;AACT;AAQA,SAAS,wBACP,QACA,KACM;AACN,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM;AAEnD,QAAM,OAAO;AACb,QAAM,eAAe,KAAK,cAAc;AACxC,MAAI,OAAO,iBAAiB,YAAY,iBAAiB,KAAM;AAG/D,aAAW,aAAa,OAAO,OAAO,YAAuC,GAAG;AAC9E,QAAI,OAAO,cAAc,YAAY,cAAc,KAAM;AAGzD,eAAW,CAAC,aAAa,WAAW,KAAK,OAAO,QAAQ,SAAoC,GAAG;AAC7F,UAAI,IAAI,IAAI,YAAY,YAAY,CAAC,EAAG;AACxC,UAAI,OAAO,gBAAgB,YAAY,gBAAgB,KAAM;AAE7D,YAAM,OAAO;AACb,YAAM,WAAW,KAAK,UAAU;AAChC,UAAI,OAAO,aAAa,YAAY,SAAS,KAAK,MAAM,IAAI;AAC1D,YAAI,IAAI,YAAY,YAAY,GAAG,SAAS,KAAK,CAAC;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,oBAAoB,SAAqC;AAChE,QAAM,QAAQ,QAAQ,MAAM,yEAAyE;AACrG,MAAI,SAAS,MAAM,CAAC,EAAE,KAAK,MAAM,IAAI;AACnC,WAAO,MAAM,CAAC,EAAE,KAAK;AAAA,EACvB;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -66,6 +66,18 @@ async function parsePubspecManifest(manifestPath, projectRoot) {
66
66
  });
67
67
  }
68
68
  }
69
+ const directNames = new Set(dependencies.map((d) => d.name));
70
+ for (const [name, version] of resolvedVersions) {
71
+ if (directNames.has(name)) continue;
72
+ dependencies.push({
73
+ name,
74
+ declaredConstraint: version,
75
+ resolvedVersion: version,
76
+ scope: "production",
77
+ ecosystem: "pub",
78
+ sourceManifest: relativeManifest
79
+ });
80
+ }
69
81
  return dependencies;
70
82
  }
71
83
  function extractSectionDependencies(content, sectionKey, relativeManifest) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/security/manifest/plugins/pubspec.ts"],
4
- "sourcesContent": ["/**\n * Dart/Flutter pub Version Extraction Plugin for KiroGraph-Sec\n *\n * Parses pubspec.yaml manifests to extract Dart package declarations with\n * version constraints, scopes, and resolved versions from pubspec.lock.\n *\n * Uses lightweight regex-based YAML parsing \u2014 no external YAML library required.\n *\n * Handles:\n * - `dependencies:` section \u2192 'production' scope\n * - `dev_dependencies:` section \u2192 'development' scope\n * - Version formats: `^1.0.0`, `\">=1.0.0 <2.0.0\"`, `any`\n * - Skips SDK entries (`sdk:`), `flutter:`, path deps, and git deps\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from pubspec.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse a pubspec.yaml manifest and extract package declarations with\n * version constraints, scopes, and resolved versions from pubspec.lock.\n *\n * @param manifestPath - Absolute path to the pubspec.yaml file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parsePubspecManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the pubspec.yaml content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:pub] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from pubspec.lock\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract the license field if present\n const license = extractPubspecLicense(content);\n\n const dependencies: ParsedDependency[] = [];\n\n // Process `dependencies` (production) and `dev_dependencies` (development)\n const sections: Array<{ key: string; scope: ParsedDependency['scope'] }> = [\n { key: 'dependencies', scope: 'production' },\n { key: 'dev_dependencies', scope: 'development' },\n ];\n\n for (const { key, scope } of sections) {\n const sectionDeps = extractSectionDependencies(content, key, relativeManifest);\n for (const dep of sectionDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name);\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope,\n ecosystem: 'pub',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n return dependencies;\n}\n\n/**\n * A raw dependency entry extracted from a pubspec.yaml section.\n */\ninterface RawDependency {\n name: string;\n constraint: string;\n}\n\n/**\n * Extract dependencies from a specific top-level section of a pubspec.yaml file.\n * Uses indentation-based parsing to determine section boundaries.\n *\n * Section entries take one of three forms:\n * - ` package_name: ^1.0.0` (inline scalar version)\n * - ` package_name: \">=1.0.0 <2.0.0\"` (quoted version)\n * - ` package_name: any` (any version)\n * - ` package_name:` (block mapping \u2014 path/git/sdk dep)\n * ` path: ../local`\n *\n * Entries with sub-keys (block mappings) are skipped as non-registry deps.\n */\nfunction extractSectionDependencies(\n content: string,\n sectionKey: string,\n relativeManifest: string,\n): RawDependency[] {\n const deps: RawDependency[] = [];\n const lines = content.split('\\n');\n\n // Find the section header line (at column 0, no indent)\n const headerPattern = new RegExp(`^${escapeRegex(sectionKey)}\\\\s*:\\\\s*$`);\n let inSection = false;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n const trimmed = line.trimEnd();\n\n // Detect section header\n if (!inSection) {\n if (headerPattern.test(trimmed)) {\n inSection = true;\n }\n i++;\n continue;\n }\n\n // Exit section when we encounter another top-level key (no leading whitespace)\n if (trimmed !== '' && !/^\\s/.test(trimmed)) {\n break;\n }\n\n // Skip blank lines inside section\n if (trimmed === '') {\n i++;\n continue;\n }\n\n // Match a 2-space-indented package entry: ` package_name: ...`\n const entryMatch = trimmed.match(/^ ([a-zA-Z0-9_][a-zA-Z0-9_.-]*):\\s*(.*)$/);\n if (!entryMatch) {\n i++;\n continue;\n }\n\n const pkgName = entryMatch[1];\n const valueOnSameLine = entryMatch[2].trim();\n\n // Check if the next line has deeper indentation (block mapping \u2192 skip)\n const nextLine = i + 1 < lines.length ? lines[i + 1] : '';\n const isBlockMapping = nextLine !== '' && /^ \\S/.test(nextLine);\n\n if (isBlockMapping) {\n // Skip \u2014 path/git/sdk dependency; advance past the block\n i++;\n while (i < lines.length) {\n const subLine = lines[i];\n if (subLine.trimEnd() === '' || /^ /.test(subLine)) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Skip SDK and flutter pseudo-packages\n if (shouldSkipEntry(pkgName)) {\n i++;\n continue;\n }\n\n if (!isValidPackageName(pkgName)) {\n logWarn(`[sec:pub] Invalid package name \"${pkgName}\" in ${relativeManifest} \u2014 skipping`);\n i++;\n continue;\n }\n\n const constraint = parseConstraintValue(valueOnSameLine);\n deps.push({ name: pkgName, constraint });\n i++;\n }\n\n return deps;\n}\n\n/**\n * Parse a pubspec.yaml dependency version value string.\n *\n * Handles:\n * - `^1.0.0` \u2192 `^1.0.0`\n * - `\">=1.0.0 <2.0.0\"` \u2192 `>=1.0.0 <2.0.0` (quotes stripped)\n * - `'>=1.0.0 <2.0.0'` \u2192 `>=1.0.0 <2.0.0`\n * - `any` \u2192 `*`\n * - empty \u2192 `*`\n */\nfunction parseConstraintValue(value: string): string {\n if (!value || value === '' || value.toLowerCase() === 'any') return '*';\n\n // Strip surrounding quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1).trim();\n }\n\n return value || '*';\n}\n\n/**\n * Determine whether a pubspec.yaml dependency key should be skipped.\n */\nfunction shouldSkipEntry(name: string): boolean {\n if (name === 'sdk') return true;\n if (name === 'flutter') return true;\n return false;\n}\n\n/**\n * Validate that a pub package name follows Dart naming conventions.\n * Package names must be lowercase, with underscores, not hyphens.\n */\nfunction isValidPackageName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 128) return false;\n return /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(name);\n}\n\n/**\n * Attempt to load resolved versions from pubspec.lock in the given directory.\n * Returns an empty map if pubspec.lock is not available.\n *\n * pubspec.lock YAML format:\n * ```yaml\n * packages:\n * http:\n * dependency: \"direct main\"\n * description:\n * name: http\n * url: \"https://pub.dartlang.org\"\n * version: \"0.13.5\"\n * test:\n * dependency: \"direct dev\"\n * version: \"1.21.4\"\n * ```\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n const lockPath = path.join(manifestDir, 'pubspec.lock');\n if (!fs.existsSync(lockPath)) {\n return map;\n }\n\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n extractFromPubspecLock(lockContent, map);\n } catch (err) {\n logWarn(`[sec:pub] Failed to parse pubspec.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pubspec.lock content.\n *\n * The lock file is a YAML document. We use regex-based parsing to avoid\n * introducing a YAML library dependency. For each package block under\n * `packages:`, we find its `version:` entry.\n */\nfunction extractFromPubspecLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let inPackagesSection = false;\n let currentPackage: string | null = null;\n\n for (const line of lines) {\n const trimmed = line.trimEnd();\n\n // Detect top-level `packages:` section\n if (trimmed === 'packages:') {\n inPackagesSection = true;\n currentPackage = null;\n continue;\n }\n\n // Exit packages section if we hit another top-level key\n if (inPackagesSection && trimmed !== '' && !/^\\s/.test(trimmed)) {\n inPackagesSection = false;\n currentPackage = null;\n continue;\n }\n\n if (!inPackagesSection) continue;\n\n // Package name entry: ` package_name:` (2-space indent)\n const pkgNameMatch = trimmed.match(/^ ([a-zA-Z0-9_][a-zA-Z0-9_.-]*):\\s*$/);\n if (pkgNameMatch) {\n currentPackage = pkgNameMatch[1];\n continue;\n }\n\n // Version entry: ` version: \"1.2.3\"` (4-space indent)\n if (currentPackage) {\n const versionMatch = trimmed.match(/^ version:\\s*[\"']?([^\"'\\s]+)[\"']?\\s*$/);\n if (versionMatch) {\n if (!map.has(currentPackage)) {\n map.set(currentPackage, versionMatch[1]);\n }\n currentPackage = null;\n }\n }\n }\n}\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract the `license:` field from pubspec.yaml content.\n * Handles: `license: MIT` (top-level scalar field)\n */\nfunction extractPubspecLicense(content: string): string | undefined {\n const match = content.match(/^license:\\s*(.+)$/m);\n if (match && match[1].trim() !== '') {\n // Strip surrounding quotes if present\n let value = match[1].trim();\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1).trim();\n }\n return value || undefined;\n }\n return undefined;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Dart/Flutter pub Version Extraction Plugin for KiroGraph-Sec\n *\n * Parses pubspec.yaml manifests to extract Dart package declarations with\n * version constraints, scopes, and resolved versions from pubspec.lock.\n *\n * Uses lightweight regex-based YAML parsing \u2014 no external YAML library required.\n *\n * Handles:\n * - `dependencies:` section \u2192 'production' scope\n * - `dev_dependencies:` section \u2192 'development' scope\n * - Version formats: `^1.0.0`, `\">=1.0.0 <2.0.0\"`, `any`\n * - Skips SDK entries (`sdk:`), `flutter:`, path deps, and git deps\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from pubspec.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse a pubspec.yaml manifest and extract package declarations with\n * version constraints, scopes, and resolved versions from pubspec.lock.\n *\n * @param manifestPath - Absolute path to the pubspec.yaml file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parsePubspecManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the pubspec.yaml content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:pub] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from pubspec.lock\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract the license field if present\n const license = extractPubspecLicense(content);\n\n const dependencies: ParsedDependency[] = [];\n\n // Process `dependencies` (production) and `dev_dependencies` (development)\n const sections: Array<{ key: string; scope: ParsedDependency['scope'] }> = [\n { key: 'dependencies', scope: 'production' },\n { key: 'dev_dependencies', scope: 'development' },\n ];\n\n for (const { key, scope } of sections) {\n const sectionDeps = extractSectionDependencies(content, key, relativeManifest);\n for (const dep of sectionDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name);\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope,\n ecosystem: 'pub',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Transitive-only packages (present in pubspec.lock but never declared in\n // pubspec.yaml) would otherwise never become a Dependency_Node and would\n // silently skip vulnerability scanning. resolvedVersions already has every\n // package pubspec.lock resolved, direct or transitive.\n const directNames = new Set(dependencies.map(d => d.name));\n for (const [name, version] of resolvedVersions) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n declaredConstraint: version,\n resolvedVersion: version,\n scope: 'production',\n ecosystem: 'pub',\n sourceManifest: relativeManifest,\n });\n }\n\n return dependencies;\n}\n\n/**\n * A raw dependency entry extracted from a pubspec.yaml section.\n */\ninterface RawDependency {\n name: string;\n constraint: string;\n}\n\n/**\n * Extract dependencies from a specific top-level section of a pubspec.yaml file.\n * Uses indentation-based parsing to determine section boundaries.\n *\n * Section entries take one of three forms:\n * - ` package_name: ^1.0.0` (inline scalar version)\n * - ` package_name: \">=1.0.0 <2.0.0\"` (quoted version)\n * - ` package_name: any` (any version)\n * - ` package_name:` (block mapping \u2014 path/git/sdk dep)\n * ` path: ../local`\n *\n * Entries with sub-keys (block mappings) are skipped as non-registry deps.\n */\nfunction extractSectionDependencies(\n content: string,\n sectionKey: string,\n relativeManifest: string,\n): RawDependency[] {\n const deps: RawDependency[] = [];\n const lines = content.split('\\n');\n\n // Find the section header line (at column 0, no indent)\n const headerPattern = new RegExp(`^${escapeRegex(sectionKey)}\\\\s*:\\\\s*$`);\n let inSection = false;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n const trimmed = line.trimEnd();\n\n // Detect section header\n if (!inSection) {\n if (headerPattern.test(trimmed)) {\n inSection = true;\n }\n i++;\n continue;\n }\n\n // Exit section when we encounter another top-level key (no leading whitespace)\n if (trimmed !== '' && !/^\\s/.test(trimmed)) {\n break;\n }\n\n // Skip blank lines inside section\n if (trimmed === '') {\n i++;\n continue;\n }\n\n // Match a 2-space-indented package entry: ` package_name: ...`\n const entryMatch = trimmed.match(/^ ([a-zA-Z0-9_][a-zA-Z0-9_.-]*):\\s*(.*)$/);\n if (!entryMatch) {\n i++;\n continue;\n }\n\n const pkgName = entryMatch[1];\n const valueOnSameLine = entryMatch[2].trim();\n\n // Check if the next line has deeper indentation (block mapping \u2192 skip)\n const nextLine = i + 1 < lines.length ? lines[i + 1] : '';\n const isBlockMapping = nextLine !== '' && /^ \\S/.test(nextLine);\n\n if (isBlockMapping) {\n // Skip \u2014 path/git/sdk dependency; advance past the block\n i++;\n while (i < lines.length) {\n const subLine = lines[i];\n if (subLine.trimEnd() === '' || /^ /.test(subLine)) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Skip SDK and flutter pseudo-packages\n if (shouldSkipEntry(pkgName)) {\n i++;\n continue;\n }\n\n if (!isValidPackageName(pkgName)) {\n logWarn(`[sec:pub] Invalid package name \"${pkgName}\" in ${relativeManifest} \u2014 skipping`);\n i++;\n continue;\n }\n\n const constraint = parseConstraintValue(valueOnSameLine);\n deps.push({ name: pkgName, constraint });\n i++;\n }\n\n return deps;\n}\n\n/**\n * Parse a pubspec.yaml dependency version value string.\n *\n * Handles:\n * - `^1.0.0` \u2192 `^1.0.0`\n * - `\">=1.0.0 <2.0.0\"` \u2192 `>=1.0.0 <2.0.0` (quotes stripped)\n * - `'>=1.0.0 <2.0.0'` \u2192 `>=1.0.0 <2.0.0`\n * - `any` \u2192 `*`\n * - empty \u2192 `*`\n */\nfunction parseConstraintValue(value: string): string {\n if (!value || value === '' || value.toLowerCase() === 'any') return '*';\n\n // Strip surrounding quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1).trim();\n }\n\n return value || '*';\n}\n\n/**\n * Determine whether a pubspec.yaml dependency key should be skipped.\n */\nfunction shouldSkipEntry(name: string): boolean {\n if (name === 'sdk') return true;\n if (name === 'flutter') return true;\n return false;\n}\n\n/**\n * Validate that a pub package name follows Dart naming conventions.\n * Package names must be lowercase, with underscores, not hyphens.\n */\nfunction isValidPackageName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 128) return false;\n return /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(name);\n}\n\n/**\n * Attempt to load resolved versions from pubspec.lock in the given directory.\n * Returns an empty map if pubspec.lock is not available.\n *\n * pubspec.lock YAML format:\n * ```yaml\n * packages:\n * http:\n * dependency: \"direct main\"\n * description:\n * name: http\n * url: \"https://pub.dartlang.org\"\n * version: \"0.13.5\"\n * test:\n * dependency: \"direct dev\"\n * version: \"1.21.4\"\n * ```\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n const lockPath = path.join(manifestDir, 'pubspec.lock');\n if (!fs.existsSync(lockPath)) {\n return map;\n }\n\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n extractFromPubspecLock(lockContent, map);\n } catch (err) {\n logWarn(`[sec:pub] Failed to parse pubspec.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pubspec.lock content.\n *\n * The lock file is a YAML document. We use regex-based parsing to avoid\n * introducing a YAML library dependency. For each package block under\n * `packages:`, we find its `version:` entry.\n */\nfunction extractFromPubspecLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let inPackagesSection = false;\n let currentPackage: string | null = null;\n\n for (const line of lines) {\n const trimmed = line.trimEnd();\n\n // Detect top-level `packages:` section\n if (trimmed === 'packages:') {\n inPackagesSection = true;\n currentPackage = null;\n continue;\n }\n\n // Exit packages section if we hit another top-level key\n if (inPackagesSection && trimmed !== '' && !/^\\s/.test(trimmed)) {\n inPackagesSection = false;\n currentPackage = null;\n continue;\n }\n\n if (!inPackagesSection) continue;\n\n // Package name entry: ` package_name:` (2-space indent)\n const pkgNameMatch = trimmed.match(/^ ([a-zA-Z0-9_][a-zA-Z0-9_.-]*):\\s*$/);\n if (pkgNameMatch) {\n currentPackage = pkgNameMatch[1];\n continue;\n }\n\n // Version entry: ` version: \"1.2.3\"` (4-space indent)\n if (currentPackage) {\n const versionMatch = trimmed.match(/^ version:\\s*[\"']?([^\"'\\s]+)[\"']?\\s*$/);\n if (versionMatch) {\n if (!map.has(currentPackage)) {\n map.set(currentPackage, versionMatch[1]);\n }\n currentPackage = null;\n }\n }\n }\n}\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract the `license:` field from pubspec.yaml content.\n * Handles: `license: MIT` (top-level scalar field)\n */\nfunction extractPubspecLicense(content: string): string | undefined {\n const match = content.match(/^license:\\s*(.+)$/m);\n if (match && match[1].trim() !== '') {\n // Strip surrounding quotes if present\n let value = match[1].trim();\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1).trim();\n }\n return value || undefined;\n }\n return undefined;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -89,6 +89,18 @@ async function parsePyprojectManifest(manifestPath, projectRoot) {
89
89
  });
90
90
  }
91
91
  }
92
+ const directNames = new Set(dependencies.map((d) => d.name.toLowerCase()));
93
+ for (const [name, version] of resolvedVersions) {
94
+ if (directNames.has(name)) continue;
95
+ dependencies.push({
96
+ name,
97
+ declaredConstraint: version,
98
+ resolvedVersion: version,
99
+ scope: "production",
100
+ ecosystem: "pypi",
101
+ sourceManifest: relativeManifest
102
+ });
103
+ }
92
104
  return dependencies;
93
105
  }
94
106
  function extractPep621Dependencies(content, relativeManifest) {