kirograph 0.28.1 → 1.1.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.
- package/README.md +3 -2
- package/dist/architecture/index.js +4 -1
- package/dist/architecture/index.js.map +2 -2
- package/dist/architecture/manifest/index.js +13 -2
- package/dist/architecture/manifest/index.js.map +2 -2
- package/dist/architecture/manifest/python.js +2 -2
- package/dist/architecture/manifest/python.js.map +2 -2
- package/dist/bin/commands/help.js +3 -1
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/install.js +2 -1
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/memory.js +18 -8
- package/dist/bin/commands/memory.js.map +2 -2
- package/dist/bin/commands/reachability.js +7 -2
- package/dist/bin/commands/reachability.js.map +2 -2
- package/dist/bin/commands/status.js +2 -1
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/uninit.js +2 -1
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vulns.js +26 -6
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/commands/wiki.js +56 -0
- package/dist/bin/commands/wiki.js.map +2 -2
- package/dist/bin/installer/common.js +7 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/detect.js +10 -0
- package/dist/bin/installer/detect.js.map +2 -2
- package/dist/bin/installer/hooks.js +13 -8
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +1 -1
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +1 -1
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +1 -1
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/commandcode.js +84 -0
- package/dist/bin/installer/targets/commandcode.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +1 -1
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +2 -2
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +1 -1
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +1 -1
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/index.js +10 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +1 -1
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +1 -1
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/bin/kirograph.js.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +18 -6
- package/dist/core/pipeline.js.map +2 -2
- package/dist/extraction/extractor.js +88 -26
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +38 -3
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.js.map +2 -2
- package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
- package/dist/frameworks/index.js +68 -7
- package/dist/frameworks/index.js.map +2 -2
- package/dist/jev/client.js +99 -0
- package/dist/jev/client.js.map +7 -0
- package/dist/jev/types.js +17 -0
- package/dist/jev/types.js.map +7 -0
- package/dist/mcp/handlers/memory.js +14 -8
- package/dist/mcp/handlers/memory.js.map +2 -2
- package/dist/mcp/handlers/security.js +5 -3
- package/dist/mcp/handlers/security.js.map +2 -2
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/memory/index.js +13 -1
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/relation-jev.js +59 -0
- package/dist/memory/relation-jev.js.map +7 -0
- package/dist/memory/schema.js +57 -0
- package/dist/memory/schema.js.map +7 -0
- package/dist/security/auth-detection-jev.js +47 -0
- package/dist/security/auth-detection-jev.js.map +7 -0
- package/dist/security/manifest/adapter.js +34 -5
- package/dist/security/manifest/adapter.js.map +3 -3
- package/dist/security/manifest/parser.js +2 -2
- package/dist/security/manifest/parser.js.map +2 -2
- package/dist/security/manifest/plugins/cargo.js +23 -0
- package/dist/security/manifest/plugins/cargo.js.map +2 -2
- package/dist/security/manifest/plugins/composer.js +12 -0
- package/dist/security/manifest/plugins/composer.js.map +2 -2
- package/dist/security/manifest/plugins/gradle.js +12 -0
- package/dist/security/manifest/plugins/gradle.js.map +2 -2
- package/dist/security/manifest/plugins/hex.js +12 -0
- package/dist/security/manifest/plugins/hex.js.map +2 -2
- package/dist/security/manifest/plugins/maven.js +68 -1
- package/dist/security/manifest/plugins/maven.js.map +2 -2
- package/dist/security/manifest/plugins/npm.js +86 -0
- package/dist/security/manifest/plugins/npm.js.map +2 -2
- package/dist/security/manifest/plugins/nuget.js +13 -0
- package/dist/security/manifest/plugins/nuget.js.map +2 -2
- package/dist/security/manifest/plugins/pubspec.js +12 -0
- package/dist/security/manifest/plugins/pubspec.js.map +2 -2
- package/dist/security/manifest/plugins/pyproject.js +12 -0
- package/dist/security/manifest/plugins/pyproject.js.map +2 -2
- package/dist/security/manifest/plugins/rubygems.js +12 -0
- package/dist/security/manifest/plugins/rubygems.js.map +2 -2
- package/dist/security/pipeline.js +5 -2
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +115 -50
- package/dist/security/reachability.js.map +2 -2
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/client.js +45 -1
- package/dist/security/vuln/client.js.map +2 -2
- package/dist/security/vuln/epss-client.js +1 -1
- package/dist/security/vuln/epss-client.js.map +1 -1
- package/dist/security/vuln/osv-adapter.js +144 -32
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/sync/index.js +9 -2
- package/dist/sync/index.js.map +2 -2
- package/dist/vectors/libsql-index.js +287 -0
- package/dist/vectors/libsql-index.js.map +7 -0
- package/dist/vectors/turso-index.js +307 -0
- package/dist/vectors/turso-index.js.map +7 -0
- package/dist/wiki/contradiction-jev.js +49 -0
- package/dist/wiki/contradiction-jev.js.map +7 -0
- package/dist/wiki/index.js +13 -0
- package/dist/wiki/index.js.map +2 -2
- package/dist/wiki/ingest.js +2 -0
- package/dist/wiki/ingest.js.map +2 -2
- package/dist/wiki/links.js +53 -0
- package/dist/wiki/links.js.map +7 -0
- package/dist/wiki/lint.js +3 -6
- package/dist/wiki/lint.js.map +2 -2
- package/dist/wiki/rename.js +103 -0
- package/dist/wiki/rename.js.map +7 -0
- package/package.json +3 -2
|
@@ -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 maven_exports = {};
|
|
30
30
|
__export(maven_exports, {
|
|
31
|
+
parseMavenDependencyTree: () => parseMavenDependencyTree,
|
|
31
32
|
parseMavenManifest: () => parseMavenManifest
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(maven_exports);
|
|
@@ -72,7 +73,8 @@ async function parseMavenManifest(manifestPath, projectRoot) {
|
|
|
72
73
|
}
|
|
73
74
|
const license = extractMavenLicense(content);
|
|
74
75
|
const dependencies = [];
|
|
75
|
-
const
|
|
76
|
+
const dependenciesOnlyContent = content.replace(/<dependencyManagement>[\s\S]*?<\/dependencyManagement>/g, "");
|
|
77
|
+
const dependencyBlocks = dependenciesOnlyContent.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g);
|
|
76
78
|
for (const block of dependencyBlocks) {
|
|
77
79
|
const depXml = block[1];
|
|
78
80
|
const groupId = extractXmlElement(depXml, "groupId");
|
|
@@ -109,8 +111,72 @@ async function parseMavenManifest(manifestPath, projectRoot) {
|
|
|
109
111
|
...license !== void 0 ? { license } : {}
|
|
110
112
|
});
|
|
111
113
|
}
|
|
114
|
+
const manifestDir = path.dirname(manifestPath);
|
|
115
|
+
const treeFile = findMavenDependencyTreeFile(manifestDir);
|
|
116
|
+
if (treeFile) {
|
|
117
|
+
const treeRelativePath = path.relative(projectRoot, treeFile.path).replace(/\\/g, "/");
|
|
118
|
+
let treeContent;
|
|
119
|
+
try {
|
|
120
|
+
treeContent = fs.readFileSync(treeFile.path, "utf8");
|
|
121
|
+
} catch (err) {
|
|
122
|
+
(0, import_errors.logWarn)(`[sec:maven] Failed to read ${treeRelativePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
+
return dependencies;
|
|
124
|
+
}
|
|
125
|
+
const treeEntries = parseMavenDependencyTree(treeContent);
|
|
126
|
+
for (const dep of dependencies) {
|
|
127
|
+
if (dep.resolvedVersion) continue;
|
|
128
|
+
const treeEntry = treeEntries.get(dep.name);
|
|
129
|
+
if (treeEntry) {
|
|
130
|
+
dep.resolvedVersion = treeEntry.version;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const directNames = new Set(dependencies.map((d) => d.name));
|
|
134
|
+
for (const [name, entry] of treeEntries) {
|
|
135
|
+
if (directNames.has(name)) continue;
|
|
136
|
+
dependencies.push({
|
|
137
|
+
name,
|
|
138
|
+
// No declared range exists at the project level for a transitive
|
|
139
|
+
// package — the resolved version from the tree is the only constraint we have.
|
|
140
|
+
declaredConstraint: entry.version,
|
|
141
|
+
resolvedVersion: entry.version,
|
|
142
|
+
scope: mapMavenScope(entry.scope),
|
|
143
|
+
ecosystem: "maven",
|
|
144
|
+
sourceManifest: treeRelativePath
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
112
148
|
return dependencies;
|
|
113
149
|
}
|
|
150
|
+
function findMavenDependencyTreeFile(manifestDir) {
|
|
151
|
+
const candidates = ["dependency-tree.txt", path.join("target", "dependency-tree.txt")];
|
|
152
|
+
for (const candidate of candidates) {
|
|
153
|
+
const candidatePath = path.join(manifestDir, candidate);
|
|
154
|
+
if (fs.existsSync(candidatePath)) {
|
|
155
|
+
return { path: candidatePath };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
function parseMavenDependencyTree(content) {
|
|
161
|
+
const result = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
163
|
+
if (!rawLine.trim()) continue;
|
|
164
|
+
const stripped = rawLine.replace(/^[\s|+\\-]+/, "").trim();
|
|
165
|
+
if (!stripped || stripped.startsWith("(")) continue;
|
|
166
|
+
const parts = stripped.split(":");
|
|
167
|
+
if (parts.length < 5) continue;
|
|
168
|
+
const groupId = parts[0];
|
|
169
|
+
const artifactId = parts[1];
|
|
170
|
+
const version = parts[parts.length - 2];
|
|
171
|
+
const scope = parts[parts.length - 1];
|
|
172
|
+
if (!groupId || !artifactId || !version) continue;
|
|
173
|
+
const name = `${groupId}:${artifactId}`;
|
|
174
|
+
if (!result.has(name)) {
|
|
175
|
+
result.set(name, { version, scope });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
114
180
|
function extractMavenLicense(content) {
|
|
115
181
|
const licensesMatch = content.match(/<licenses\b[^>]*>([\s\S]*?)<\/licenses>/i);
|
|
116
182
|
if (!licensesMatch) return void 0;
|
|
@@ -123,6 +189,7 @@ function extractMavenLicense(content) {
|
|
|
123
189
|
}
|
|
124
190
|
// Annotate the CommonJS export names for ESM import in node:
|
|
125
191
|
0 && (module.exports = {
|
|
192
|
+
parseMavenDependencyTree,
|
|
126
193
|
parseMavenManifest
|
|
127
194
|
});
|
|
128
195
|
//# sourceMappingURL=maven.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/security/manifest/plugins/maven.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Maven Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture maven parser to extract version constraints,\n * dependency scopes, and groupId/artifactId for security analysis.\n *\n * Reuses `mavenParser` from `src/architecture/manifest/maven.ts` for discovery and\n * basic parsing, then layers on version/scope extraction from pom.xml.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Maven scope to ParsedDependency scope mapping.\n *\n * - compile (default), runtime \u2192 production\n * - test \u2192 development\n * - provided, system \u2192 optional\n */\nfunction mapMavenScope(mavenScope: string | undefined): ParsedDependency['scope'] {\n if (!mavenScope || mavenScope.trim() === '') {\n return 'production';\n }\n\n const normalized = mavenScope.trim().toLowerCase();\n switch (normalized) {\n case 'compile':\n case 'runtime':\n return 'production';\n case 'test':\n return 'development';\n case 'provided':\n case 'system':\n return 'optional';\n default:\n // Unknown scope defaults to production\n return 'production';\n }\n}\n\n/**\n * Extract text content from an XML element by tag name within a given XML fragment.\n * Returns undefined if the element is not found.\n */\nfunction extractXmlElement(xml: string, tagName: string): string | undefined {\n const regex = new RegExp(`<${tagName}>([^<]*)</${tagName}>`);\n const match = xml.match(regex);\n return match ? match[1].trim() : undefined;\n}\n\n/**\n * Parse a Maven pom.xml manifest and extract dependency declarations\n * with groupId, artifactId, version constraints, and scopes.\n *\n * @param manifestPath - Absolute path to the pom.xml 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 parseMavenManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the pom.xml content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:maven] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Validate basic XML structure\n if (!content.includes('<project')) {\n logWarn(`[sec:maven] Invalid pom.xml structure at ${relativeManifest} \u2014 missing <project> element`);\n return [];\n }\n\n // Extract the project-level license\n const license = extractMavenLicense(content);\n\n // Extract all <dependency> blocks from the <dependencies> sections\n const dependencies: ParsedDependency[] = [];\n\n // Match all <dependency>...</dependency> blocks\n const dependencyBlocks = content.matchAll(/<dependency>([\\s\\S]*?)<\\/dependency>/g);\n\n for (const block of dependencyBlocks) {\n const depXml = block[1];\n\n const groupId = extractXmlElement(depXml, 'groupId');\n const artifactId = extractXmlElement(depXml, 'artifactId');\n const version = extractXmlElement(depXml, 'version');\n const scope = extractXmlElement(depXml, 'scope');\n\n // Validate required fields\n if (!groupId || groupId.trim() === '') {\n logWarn(`[sec:maven] Missing groupId in dependency at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n if (!artifactId || artifactId.trim() === '') {\n logWarn(`[sec:maven] Missing artifactId in dependency at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n // Skip dependencies with unresolved Maven properties (e.g. ${project.version})\n if (groupId.includes('${') || artifactId.includes('${')) {\n logWarn(`[sec:maven] Unresolved property in dependency ${groupId}:${artifactId} at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n // Build the dependency name as groupId:artifactId (Maven convention)\n const name = `${groupId}:${artifactId}`;\n\n // Version constraint \u2014 may be absent (managed by parent POM or BOM)\n let declaredConstraint: string;\n if (version && version.trim() !== '' && !version.includes('${')) {\n declaredConstraint = version.trim();\n } else {\n // No version or unresolved property \u2014 use empty string to indicate managed externally\n declaredConstraint = version && !version.includes('${') ? version.trim() : '';\n }\n\n const mappedScope = mapMavenScope(scope);\n\n dependencies.push({\n name,\n declaredConstraint,\n resolvedVersion: declaredConstraint || undefined,\n scope: mappedScope,\n ecosystem: 'maven',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n return dependencies;\n}\n\n/**\n * Extract the first license name from a pom.xml <licenses> block.\n * Looks for: <licenses><license><name>...</name></license></licenses>\n */\nfunction extractMavenLicense(content: string): string | undefined {\n const licensesMatch = content.match(/<licenses\\b[^>]*>([\\s\\S]*?)<\\/licenses>/i);\n if (!licensesMatch) return undefined;\n const licenseBlock = licensesMatch[1];\n const nameMatch = licenseBlock.match(/<name>([^<]+)<\\/name>/i);\n if (nameMatch && nameMatch[1].trim() !== '') {\n return nameMatch[1].trim();\n }\n return undefined;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,SAAoB;AACpB,WAAsB;AAEtB,oBAAwB;AASxB,SAAS,cAAc,YAA2D;AAChF,MAAI,CAAC,cAAc,WAAW,KAAK,MAAM,IAAI;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,WAAW,KAAK,EAAE,YAAY;AACjD,UAAQ,YAAY;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AAEE,aAAO;AAAA,EACX;AACF;AAMA,SAAS,kBAAkB,KAAa,SAAqC;AAC3E,QAAM,QAAQ,IAAI,OAAO,IAAI,OAAO,aAAa,OAAO,GAAG;AAC3D,QAAM,QAAQ,IAAI,MAAM,KAAK;AAC7B,SAAO,QAAQ,MAAM,CAAC,EAAE,KAAK,IAAI;AACnC;AAUA,eAAsB,mBACpB,cACA,aAC6B;AAC7B,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,MAAI,CAAC,QAAQ,SAAS,UAAU,GAAG;AACjC,+BAAQ,4CAA4C,gBAAgB,mCAA8B;AAClG,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,UAAU,oBAAoB,OAAO;AAG3C,QAAM,eAAmC,CAAC;
|
|
4
|
+
"sourcesContent": ["/**\n * Maven Version Extraction Plugin for KiroGraph-Sec\n *\n * Extends the existing architecture maven parser to extract version constraints,\n * dependency scopes, and groupId/artifactId for security analysis.\n *\n * Reuses `mavenParser` from `src/architecture/manifest/maven.ts` for discovery and\n * basic parsing, then layers on version/scope extraction from pom.xml.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Maven scope to ParsedDependency scope mapping.\n *\n * - compile (default), runtime \u2192 production\n * - test \u2192 development\n * - provided, system \u2192 optional\n */\nfunction mapMavenScope(mavenScope: string | undefined): ParsedDependency['scope'] {\n if (!mavenScope || mavenScope.trim() === '') {\n return 'production';\n }\n\n const normalized = mavenScope.trim().toLowerCase();\n switch (normalized) {\n case 'compile':\n case 'runtime':\n return 'production';\n case 'test':\n return 'development';\n case 'provided':\n case 'system':\n return 'optional';\n default:\n // Unknown scope defaults to production\n return 'production';\n }\n}\n\n/**\n * Extract text content from an XML element by tag name within a given XML fragment.\n * Returns undefined if the element is not found.\n */\nfunction extractXmlElement(xml: string, tagName: string): string | undefined {\n const regex = new RegExp(`<${tagName}>([^<]*)</${tagName}>`);\n const match = xml.match(regex);\n return match ? match[1].trim() : undefined;\n}\n\n/**\n * Parse a Maven pom.xml manifest and extract dependency declarations\n * with groupId, artifactId, version constraints, and scopes.\n *\n * @param manifestPath - Absolute path to the pom.xml 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 parseMavenManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the pom.xml content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:maven] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Validate basic XML structure\n if (!content.includes('<project')) {\n logWarn(`[sec:maven] Invalid pom.xml structure at ${relativeManifest} \u2014 missing <project> element`);\n return [];\n }\n\n // Extract the project-level license\n const license = extractMavenLicense(content);\n\n // Extract all <dependency> blocks from the <dependencies> sections\n const dependencies: ParsedDependency[] = [];\n\n // <dependencyManagement> entries (BOM imports, version overrides for child\n // modules) are not themselves project dependencies \u2014 strip that block before\n // scanning so they don't get misread as declared dependencies.\n const dependenciesOnlyContent = content.replace(/<dependencyManagement>[\\s\\S]*?<\\/dependencyManagement>/g, '');\n\n // Match all <dependency>...</dependency> blocks\n const dependencyBlocks = dependenciesOnlyContent.matchAll(/<dependency>([\\s\\S]*?)<\\/dependency>/g);\n\n for (const block of dependencyBlocks) {\n const depXml = block[1];\n\n const groupId = extractXmlElement(depXml, 'groupId');\n const artifactId = extractXmlElement(depXml, 'artifactId');\n const version = extractXmlElement(depXml, 'version');\n const scope = extractXmlElement(depXml, 'scope');\n\n // Validate required fields\n if (!groupId || groupId.trim() === '') {\n logWarn(`[sec:maven] Missing groupId in dependency at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n if (!artifactId || artifactId.trim() === '') {\n logWarn(`[sec:maven] Missing artifactId in dependency at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n // Skip dependencies with unresolved Maven properties (e.g. ${project.version})\n if (groupId.includes('${') || artifactId.includes('${')) {\n logWarn(`[sec:maven] Unresolved property in dependency ${groupId}:${artifactId} at ${relativeManifest} \u2014 skipping`);\n continue;\n }\n\n // Build the dependency name as groupId:artifactId (Maven convention)\n const name = `${groupId}:${artifactId}`;\n\n // Version constraint \u2014 may be absent (managed by parent POM or BOM)\n let declaredConstraint: string;\n if (version && version.trim() !== '' && !version.includes('${')) {\n declaredConstraint = version.trim();\n } else {\n // No version or unresolved property \u2014 use empty string to indicate managed externally\n declaredConstraint = version && !version.includes('${') ? version.trim() : '';\n }\n\n const mappedScope = mapMavenScope(scope);\n\n dependencies.push({\n name,\n declaredConstraint,\n resolvedVersion: declaredConstraint || undefined,\n scope: mappedScope,\n ecosystem: 'maven',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n // Maven has no lock file, so a <dependency> with no explicit <version> (the\n // common case under a BOM parent like spring-boot-starter-parent) never gets\n // a resolvedVersion above, and packages pulled in only transitively (e.g.\n // tomcat-embed-core via spring-boot-starter-web) never appear in pom.xml at\n // all. If the project has a pre-generated `mvn dependency:tree` text file\n // (the closest Maven equivalent of a lock file), use it to fill in resolved\n // versions and capture transitive-only packages, mirroring how the npm\n // plugin layers package-lock.json on top of package.json.\n const manifestDir = path.dirname(manifestPath);\n const treeFile = findMavenDependencyTreeFile(manifestDir);\n\n if (treeFile) {\n const treeRelativePath = path.relative(projectRoot, treeFile.path).replace(/\\\\/g, '/');\n let treeContent: string;\n try {\n treeContent = fs.readFileSync(treeFile.path, 'utf8');\n } catch (err) {\n logWarn(`[sec:maven] Failed to read ${treeRelativePath}: ${err instanceof Error ? err.message : String(err)}`);\n return dependencies;\n }\n\n const treeEntries = parseMavenDependencyTree(treeContent);\n\n for (const dep of dependencies) {\n if (dep.resolvedVersion) continue;\n const treeEntry = treeEntries.get(dep.name);\n if (treeEntry) {\n dep.resolvedVersion = treeEntry.version;\n }\n }\n\n const directNames = new Set(dependencies.map(d => d.name));\n for (const [name, entry] of treeEntries) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n // No declared range exists at the project level for a transitive\n // package \u2014 the resolved version from the tree is the only constraint we have.\n declaredConstraint: entry.version,\n resolvedVersion: entry.version,\n scope: mapMavenScope(entry.scope),\n ecosystem: 'maven',\n sourceManifest: treeRelativePath,\n });\n }\n }\n\n return dependencies;\n}\n\n/**\n * Look for a pre-generated Maven dependency-tree text file next to pom.xml,\n * e.g. produced with:\n * mvn dependency:tree -DoutputFile=dependency-tree.txt\n * Checked locations, in order: `dependency-tree.txt` next to pom.xml, then\n * `target/dependency-tree.txt` (Maven's default build output directory).\n */\nfunction findMavenDependencyTreeFile(manifestDir: string): { path: string } | undefined {\n const candidates = ['dependency-tree.txt', path.join('target', 'dependency-tree.txt')];\n for (const candidate of candidates) {\n const candidatePath = path.join(manifestDir, candidate);\n if (fs.existsSync(candidatePath)) {\n return { path: candidatePath };\n }\n }\n return undefined;\n}\n\n/**\n * Parse `mvn dependency:tree` text output into a map of `groupId:artifactId`\n * to its resolved version and scope.\n *\n * Handles both:\n * groupId:artifactId:type:version:scope\n * groupId:artifactId:type:classifier:version:scope\n *\n * Skips the root project line (no scope segment) and parenthesized entries\n * like `(commons-logging:commons-logging:jar:1.1.1:compile - omitted for conflict with 1.2)`\n * \u2014 Maven's conflict resolution means only the winning version ends up on the\n * classpath, so omitted entries aren't actually present in the build.\n */\nexport function parseMavenDependencyTree(content: string): Map<string, { version: string; scope: string }> {\n const result = new Map<string, { version: string; scope: string }>();\n\n for (const rawLine of content.split(/\\r?\\n/)) {\n if (!rawLine.trim()) continue;\n\n // Strip Maven's ASCII tree-drawing prefix (e.g. \"| +- \", \" \\- \")\n const stripped = rawLine.replace(/^[\\s|+\\\\-]+/, '').trim();\n if (!stripped || stripped.startsWith('(')) continue;\n\n const parts = stripped.split(':');\n if (parts.length < 5) continue; // root project line (\"groupId:artifactId:version\") or malformed\n\n const groupId = parts[0];\n const artifactId = parts[1];\n const version = parts[parts.length - 2];\n const scope = parts[parts.length - 1];\n if (!groupId || !artifactId || !version) continue;\n\n const name = `${groupId}:${artifactId}`;\n if (!result.has(name)) {\n result.set(name, { version, scope });\n }\n }\n\n return result;\n}\n\n/**\n * Extract the first license name from a pom.xml <licenses> block.\n * Looks for: <licenses><license><name>...</name></license></licenses>\n */\nfunction extractMavenLicense(content: string): string | undefined {\n const licensesMatch = content.match(/<licenses\\b[^>]*>([\\s\\S]*?)<\\/licenses>/i);\n if (!licensesMatch) return undefined;\n const licenseBlock = licensesMatch[1];\n const nameMatch = licenseBlock.match(/<name>([^<]+)<\\/name>/i);\n if (nameMatch && nameMatch[1].trim() !== '') {\n return nameMatch[1].trim();\n }\n return undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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);
|
|
@@ -119,8 +120,92 @@ async function parseNpmManifest(manifestPath, projectRoot) {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
}
|
|
123
|
+
const directNames = new Set(dependencies.map((d) => d.name));
|
|
124
|
+
const lockRelativePath = relativeManifest.replace(/package\.json$/, "package-lock.json");
|
|
125
|
+
const npmLockPath = path.join(manifestDir, "package-lock.json");
|
|
126
|
+
if (fs.existsSync(npmLockPath)) {
|
|
127
|
+
dependencies.push(...extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames));
|
|
128
|
+
} else {
|
|
129
|
+
for (const [name, version] of resolvedVersions) {
|
|
130
|
+
if (directNames.has(name)) continue;
|
|
131
|
+
dependencies.push({
|
|
132
|
+
name,
|
|
133
|
+
declaredConstraint: version,
|
|
134
|
+
resolvedVersion: version,
|
|
135
|
+
scope: "production",
|
|
136
|
+
ecosystem: "npm",
|
|
137
|
+
sourceManifest: relativeManifest
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
122
141
|
return dependencies;
|
|
123
142
|
}
|
|
143
|
+
function findNpmDuplicateVersions(projectRoot) {
|
|
144
|
+
const versionsByName = /* @__PURE__ */ new Map();
|
|
145
|
+
const lockPath = path.join(projectRoot, "package-lock.json");
|
|
146
|
+
if (!fs.existsSync(lockPath)) return versionsByName;
|
|
147
|
+
let lockData;
|
|
148
|
+
try {
|
|
149
|
+
lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
150
|
+
} catch {
|
|
151
|
+
return versionsByName;
|
|
152
|
+
}
|
|
153
|
+
if (typeof lockData !== "object" || lockData === null) return versionsByName;
|
|
154
|
+
const packages = lockData.packages;
|
|
155
|
+
if (typeof packages !== "object" || packages === null) return versionsByName;
|
|
156
|
+
for (const [key, value] of Object.entries(packages)) {
|
|
157
|
+
if (key === "") continue;
|
|
158
|
+
if (typeof value !== "object" || value === null) continue;
|
|
159
|
+
const pkg = value;
|
|
160
|
+
const version = pkg.version;
|
|
161
|
+
if (typeof version !== "string") continue;
|
|
162
|
+
const name = key.replace(/^.*node_modules\//, "");
|
|
163
|
+
if (!name) continue;
|
|
164
|
+
if (!versionsByName.has(name)) versionsByName.set(name, /* @__PURE__ */ new Set());
|
|
165
|
+
versionsByName.get(name).add(version);
|
|
166
|
+
}
|
|
167
|
+
for (const [name, versions] of versionsByName) {
|
|
168
|
+
if (versions.size <= 1) versionsByName.delete(name);
|
|
169
|
+
}
|
|
170
|
+
return versionsByName;
|
|
171
|
+
}
|
|
172
|
+
function extractTransitiveNpmDependencies(manifestDir, lockRelativePath, directNames) {
|
|
173
|
+
const lockPath = path.join(manifestDir, "package-lock.json");
|
|
174
|
+
if (!fs.existsSync(lockPath)) return [];
|
|
175
|
+
let lockData;
|
|
176
|
+
try {
|
|
177
|
+
lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
178
|
+
} catch (err) {
|
|
179
|
+
(0, import_errors.logWarn)(`[sec:npm] Failed to parse ${lockRelativePath} for transitive dependencies: ${err instanceof Error ? err.message : String(err)}`);
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
if (typeof lockData !== "object" || lockData === null) return [];
|
|
183
|
+
const packages = lockData.packages;
|
|
184
|
+
if (typeof packages !== "object" || packages === null) return [];
|
|
185
|
+
const transitive = [];
|
|
186
|
+
const seen = /* @__PURE__ */ new Set();
|
|
187
|
+
for (const [key, value] of Object.entries(packages)) {
|
|
188
|
+
if (key === "") continue;
|
|
189
|
+
if (typeof value !== "object" || value === null) continue;
|
|
190
|
+
const pkg = value;
|
|
191
|
+
const name = key.replace(/^.*node_modules\//, "");
|
|
192
|
+
if (!name || directNames.has(name) || seen.has(name)) continue;
|
|
193
|
+
const version = pkg.version;
|
|
194
|
+
if (typeof version !== "string") continue;
|
|
195
|
+
seen.add(name);
|
|
196
|
+
transitive.push({
|
|
197
|
+
name,
|
|
198
|
+
// No real semver range exists at the project level for a transitive
|
|
199
|
+
// package — the resolved version is the only constraint we have.
|
|
200
|
+
declaredConstraint: version,
|
|
201
|
+
resolvedVersion: version,
|
|
202
|
+
scope: pkg.dev === true ? "development" : "production",
|
|
203
|
+
ecosystem: "npm",
|
|
204
|
+
sourceManifest: lockRelativePath
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return transitive;
|
|
208
|
+
}
|
|
124
209
|
function loadResolvedVersions(manifestDir) {
|
|
125
210
|
const map = /* @__PURE__ */ new Map();
|
|
126
211
|
const lockPath = path.join(manifestDir, "package-lock.json");
|
|
@@ -279,6 +364,7 @@ function isValidDependencyName(name) {
|
|
|
279
364
|
}
|
|
280
365
|
// Annotate the CommonJS export names for ESM import in node:
|
|
281
366
|
0 && (module.exports = {
|
|
367
|
+
findNpmDuplicateVersions,
|
|
282
368
|
parseNpmManifest
|
|
283
369
|
});
|
|
284
370
|
//# 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/**\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 // 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 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 });\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 * 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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",
|
|
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": "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",
|
|
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
|
}
|