kirograph 1.0.0 → 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/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/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/vulns.js +16 -1
- 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/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +2 -2
- package/dist/extraction/extractor.js +46 -41
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.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 +17 -3
- package/dist/security/manifest/adapter.js.map +3 -3
- 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 +1 -1
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +54 -5
- package/dist/security/reachability.js.map +3 -3
- 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/osv-adapter.js +45 -1
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- 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 +1 -1
|
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var npm_exports = {};
|
|
30
30
|
__export(npm_exports, {
|
|
31
|
+
findNpmDuplicateVersions: () => findNpmDuplicateVersions,
|
|
31
32
|
parseNpmManifest: () => parseNpmManifest
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(npm_exports);
|
|
@@ -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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,SAAoB;AACpB,WAAsB;AAEtB,oBAAwB;AAGxB,MAAM,eAA2E;AAAA,EAC/E,EAAE,OAAO,gBAAgB,OAAO,aAAa;AAAA,EAC7C,EAAE,OAAO,mBAAmB,OAAO,cAAc;AAAA,EACjD,EAAE,OAAO,wBAAwB,OAAO,WAAW;AACrD;AAgBA,eAAsB,iBACpB,cACA,aAC6B;AAC7B,QAAM,cAAc,KAAK,QAAQ,YAAY;AAC7C,QAAM,mBAAmB,KAAK,SAAS,aAAa,YAAY,EAAE,QAAQ,OAAO,GAAG;AAGpF,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,cAAc,MAAM;AAChD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,iCAAQ,+CAA+C,gBAAgB,EAAE;AACzE,aAAO,CAAC;AAAA,IACV;AACA,UAAM;AAAA,EACR,SAAS,KAAK;AACZ,+BAAQ,kCAAkC,gBAAgB,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACjH,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,mBAAmB,qBAAqB,WAAW;AAGzD,QAAM,UAAU,kBAAkB,GAAG;AAGrC,QAAM,eAAmC,CAAC;AAC1C,QAAM,WAAoD,CAAC;AAE3D,aAAW,EAAE,OAAO,MAAM,KAAK,cAAc;AAC3C,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,YAAY,UAAa,YAAY,KAAM;AAE/C,QAAI,OAAO,YAAY,YAAY,MAAM,QAAQ,OAAO,GAAG;AACzD,iCAAQ,sBAAsB,KAAK,cAAc,gBAAgB,gCAA2B,OAAO,OAAO,EAAE;AAC5G;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,QAAQ,OAAkC;AACjE,eAAW,CAAC,MAAM,UAAU,KAAK,SAAS;AAExC,UAAI,CAAC,sBAAsB,IAAI,GAAG;AAChC,mCAAQ,sCAAsC,IAAI,QAAQ,gBAAgB,KAAK,KAAK,mBAAc;AAClG,iBAAS,KAAK,EAAE,MAAM,QAAQ,eAAe,CAAC;AAC9C;AAAA,MACF;AAEA,UAAI,OAAO,eAAe,YAAY,WAAW,KAAK,MAAM,IAAI;AAC9D,mCAAQ,6CAA6C,IAAI,QAAQ,gBAAgB,KAAK,KAAK,mBAAc;AACzG,iBAAS,KAAK,EAAE,MAAM,QAAQ,qBAAqB,CAAC;AACpD;AAAA,MACF;AAEA,YAAM,kBAAkB,iBAAiB,IAAI,IAAI;AAEjD,mBAAa,KAAK;AAAA,QAChB;AAAA,QACA,oBAAoB;AAAA,QACpB;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,gBAAgB;AAAA,QAChB,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC7C,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,WAAW,IAAI;AACrB,MAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAAG;AACxE,UAAM,cAAc,OAAO,QAAQ,QAAmC;AACtE,eAAW,CAAC,MAAM,UAAU,KAAK,aAAa;AAC5C,UAAI,CAAC,sBAAsB,IAAI,GAAG;AAChC,mCAAQ,2CAA2C,IAAI,QAAQ,gBAAgB,kBAAa;AAC5F,iBAAS,KAAK,EAAE,MAAM,QAAQ,eAAe,CAAC;AAC9C;AAAA,MACF;AAEA,UAAI,OAAO,eAAe,YAAY,WAAW,KAAK,MAAM,IAAI;AAC9D,mCAAQ,6DAA6D,IAAI,QAAQ,gBAAgB,kBAAa;AAC9G,iBAAS,KAAK,EAAE,MAAM,QAAQ,qBAAqB,CAAC;AACpD;AAAA,MACF;AAGA,YAAM,kBAAkB,aAAa,KAAK,OAAK,EAAE,SAAS,IAAI;AAC9D,UAAI,CAAC,iBAAiB;AACpB,cAAM,kBAAkB,iBAAiB,IAAI,IAAI;AACjD,qBAAa,KAAK;AAAA,UAChB;AAAA,UACA,oBAAoB;AAAA,UACpB;AAAA,UACA,OAAO;AAAA,UACP,WAAW;AAAA,UACX,gBAAgB;AAAA,UAChB,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,QAC7C,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAKA,QAAM,cAAc,IAAI,IAAI,aAAa,IAAI,OAAK,EAAE,IAAI,CAAC;AACzD,QAAM,mBAAmB,iBAAiB,QAAQ,kBAAkB,mBAAmB;AACvF,QAAM,cAAc,KAAK,KAAK,aAAa,mBAAmB;AAC9D,MAAI,GAAG,WAAW,WAAW,GAAG;AAG9B,iBAAa,KAAK,GAAG,iCAAiC,aAAa,kBAAkB,WAAW,CAAC;AAAA,EACnG,OAAO;AAKL,eAAW,CAAC,MAAM,OAAO,KAAK,kBAAkB;AAC9C,UAAI,YAAY,IAAI,IAAI,EAAG;AAC3B,mBAAa,KAAK;AAAA,QAChB;AAAA,QACA,oBAAoB;AAAA,QACpB,iBAAiB;AAAA,QACjB,OAAO;AAAA,QACP,WAAW;AAAA,QACX,gBAAgB;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAkBO,SAAS,yBAAyB,aAA+C;AACtF,QAAM,iBAAiB,oBAAI,IAAyB;AAEpD,QAAM,WAAW,KAAK,KAAK,aAAa,mBAAmB;AAC3D,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO;AAErC,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,GAAG,aAAa,UAAU,MAAM,CAAC;AAAA,EACzD,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,aAAa,YAAY,aAAa,KAAM,QAAO;AAC9D,QAAM,WAAY,SAAqC;AACvD,MAAI,OAAO,aAAa,YAAY,aAAa,KAAM,QAAO;AAE9D,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAmC,GAAG;AAC9E,QAAI,QAAQ,GAAI;AAChB,QAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,UAAM,MAAM;AAEZ,UAAM,UAAU,IAAI;AACpB,QAAI,OAAO,YAAY,SAAU;AAEjC,UAAM,OAAO,IAAI,QAAQ,qBAAqB,EAAE;AAChD,QAAI,CAAC,KAAM;AAEX,QAAI,CAAC,eAAe,IAAI,IAAI,EAAG,gBAAe,IAAI,MAAM,oBAAI,IAAI,CAAC;AACjE,mBAAe,IAAI,IAAI,EAAG,IAAI,OAAO;AAAA,EACvC;AAEA,aAAW,CAAC,MAAM,QAAQ,KAAK,gBAAgB;AAC7C,QAAI,SAAS,QAAQ,EAAG,gBAAe,OAAO,IAAI;AAAA,EACpD;AAEA,SAAO;AACT;AAQA,SAAS,iCACP,aACA,kBACA,aACoB;AACpB,QAAM,WAAW,KAAK,KAAK,aAAa,mBAAmB;AAC3D,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO,CAAC;AAEtC,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,GAAG,aAAa,UAAU,MAAM,CAAC;AAAA,EACzD,SAAS,KAAK;AACZ,+BAAQ,6BAA6B,gBAAgB,iCAAiC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACxI,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,OAAO,aAAa,YAAY,aAAa,KAAM,QAAO,CAAC;AAC/D,QAAM,WAAY,SAAqC;AACvD,MAAI,OAAO,aAAa,YAAY,aAAa,KAAM,QAAO,CAAC;AAE/D,QAAM,aAAiC,CAAC;AACxC,QAAM,OAAO,oBAAI,IAAY;AAE7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAmC,GAAG;AAC9E,QAAI,QAAQ,GAAI;AAChB,QAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,UAAM,MAAM;AAEZ,UAAM,OAAO,IAAI,QAAQ,qBAAqB,EAAE;AAChD,QAAI,CAAC,QAAQ,YAAY,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,EAAG;AAEtD,UAAM,UAAU,IAAI;AACpB,QAAI,OAAO,YAAY,SAAU;AAEjC,SAAK,IAAI,IAAI;AACb,eAAW,KAAK;AAAA,MACd;AAAA;AAAA;AAAA,MAGA,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,OAAO,IAAI,QAAQ,OAAO,gBAAgB;AAAA,MAC1C,WAAW;AAAA,MACX,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAMA,SAAS,qBAAqB,aAAyC;AACrE,QAAM,MAA0B,oBAAI,IAAI;AAGxC,QAAM,WAAW,KAAK,KAAK,aAAa,mBAAmB;AAC3D,MAAI,GAAG,WAAW,QAAQ,GAAG;AAC3B,QAAI;AACF,YAAM,cAAc,GAAG,aAAa,UAAU,MAAM;AACpD,YAAM,WAAW,KAAK,MAAM,WAAW;AACvC,6BAAuB,UAAU,GAAG;AACpC,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,iCAAQ,gDAAgD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IAC5G;AAAA,EACF;AAGA,QAAM,eAAe,KAAK,KAAK,aAAa,gBAAgB;AAC5D,MAAI,GAAG,WAAW,YAAY,GAAG;AAC/B,QAAI;AACF,YAAM,cAAc,GAAG,aAAa,cAAc,MAAM;AACxD,0BAAoB,aAAa,GAAG;AACpC,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,iCAAQ,6CAA6C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IACzG;AAAA,EACF;AAGA,QAAM,eAAe,KAAK,KAAK,aAAa,WAAW;AACvD,MAAI,GAAG,WAAW,YAAY,GAAG;AAC/B,QAAI;AACF,YAAM,cAAc,GAAG,aAAa,cAAc,MAAM;AACxD,0BAAoB,aAAa,GAAG;AACpC,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,iCAAQ,wCAAwC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IACpG;AAAA,EACF;AAEA,SAAO;AACT;AASA,SAAS,oBAAoB,SAAiB,KAA+B;AAM3E,aAAW,KAAK,QAAQ,SAAS,6FAA6F,GAAG;AAC/H,UAAM,OAAO,EAAE,CAAC,EAAE,QAAQ,OAAO,EAAE;AACnC,UAAM,UAAU,EAAE,CAAC;AACnB,QAAI,QAAQ,WAAW,CAAC,IAAI,IAAI,IAAI,GAAG;AACrC,UAAI,IAAI,MAAM,OAAO;AAAA,IACvB;AAAA,EACF;AACF;AAKA,SAAS,uBAAuB,UAAmB,KAA+B;AAChF,MAAI,OAAO,aAAa,YAAY,aAAa,KAAM;AACvD,QAAM,OAAO;AAGb,QAAM,WAAW,KAAK;AACtB,MAAI,OAAO,aAAa,YAAY,aAAa,MAAM;AACrD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAmC,GAAG;AAC9E,UAAI,QAAQ,GAAI;AAChB,UAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,YAAM,MAAM;AACZ,YAAM,UAAU,IAAI;AACpB,UAAI,OAAO,YAAY,SAAU;AAGjC,YAAM,OAAO,IAAI,QAAQ,qBAAqB,EAAE;AAChD,UAAI,QAAQ,CAAC,IAAI,IAAI,IAAI,GAAG;AAC1B,YAAI,IAAI,MAAM,OAAO;AAAA,MACvB;AAAA,IACF;AACA;AAAA,EACF;AAGA,QAAM,eAAe,KAAK;AAC1B,MAAI,OAAO,iBAAiB,YAAY,iBAAiB,MAAM;AAC7D,kCAA8B,cAAyC,GAAG;AAAA,EAC5E;AACF;AAKA,SAAS,8BACP,MACA,KACM;AACN,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAChD,QAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,UAAM,MAAM;AACZ,UAAM,UAAU,IAAI;AACpB,QAAI,OAAO,YAAY,YAAY,CAAC,IAAI,IAAI,IAAI,GAAG;AACjD,UAAI,IAAI,MAAM,OAAO;AAAA,IACvB;AAEA,UAAM,SAAS,IAAI;AACnB,QAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AACjD,oCAA8B,QAAmC,GAAG;AAAA,IACtE;AAAA,EACF;AACF;AAMA,SAAS,oBAAoB,SAAiB,KAA+B;AAC3E,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,MAAI,qBAAoC;AAExC,aAAW,QAAQ,OAAO;AAExB,QAAI,KAAK,WAAW,GAAG,KAAK,KAAK,KAAK,MAAM,IAAI;AAC9C,2BAAqB;AACrB;AAAA,IACF;AAIA,QAAI,CAAC,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,GAAG;AAC/C,YAAM,SAAS,KAAK,MAAM,GAAG,EAAE;AAE/B,YAAM,OAAO,iCAAiC,MAAM;AACpD,2BAAqB;AACrB;AAAA,IACF;AAGA,QAAI,sBAAsB,KAAK,KAAK,EAAE,WAAW,UAAU,GAAG;AAC5D,YAAM,eAAe,KAAK,MAAM,qBAAqB;AACrD,UAAI,gBAAgB,CAAC,IAAI,IAAI,kBAAkB,GAAG;AAChD,YAAI,IAAI,oBAAoB,aAAa,CAAC,CAAC;AAAA,MAC7C;AACA,2BAAqB;AAAA,IACvB;AAAA,EACF;AACF;AAUA,SAAS,iCAAiC,QAA+B;AAEvE,QAAM,aAAa,OAAO,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK;AAE7C,QAAM,UAAU,WAAW,QAAQ,gBAAgB,EAAE;AAIrD,QAAM,cAAc,QAAQ,YAAY,GAAG;AAC3C,MAAI,eAAe,EAAG,QAAO;AAG7B,MAAI,QAAQ,WAAW,GAAG,GAAG;AAE3B,UAAM,aAAa,QAAQ,QAAQ,KAAK,CAAC;AACzC,QAAI,eAAe,GAAI,QAAO;AAC9B,UAAM,cAAc,QAAQ,QAAQ,KAAK,UAAU;AACnD,QAAI,gBAAgB,GAAI,QAAO;AAC/B,WAAO,QAAQ,MAAM,GAAG,WAAW;AAAA,EACrC;AAEA,SAAO,QAAQ,MAAM,GAAG,WAAW;AACrC;AAMA,SAAS,kBAAkB,KAAkD;AAC3E,QAAM,MAAM,IAAI,SAAS;AACzB,MAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,IAAI;AAChD,WAAO,IAAI,KAAK;AAAA,EAClB;AACA,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,GAAG,GAAG;AAClE,UAAM,SAAS;AACf,QAAI,OAAO,OAAO,MAAM,MAAM,YAAY,OAAO,MAAM,EAAE,KAAK,MAAM,IAAI;AACtE,aAAO,OAAO,MAAM,EAAE,KAAK;AAAA,IAC7B;AAAA,EACF;AAEA,QAAM,WAAW,IAAI,UAAU;AAC/B,MAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,GAAG;AAClD,UAAM,QAAQ,SAAS,CAAC;AACxB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,YAAM,MAAM;AACZ,UAAI,OAAO,IAAI,MAAM,MAAM,YAAY,IAAI,MAAM,EAAE,KAAK,MAAM,IAAI;AAChE,eAAO,IAAI,MAAM,EAAE,KAAK;AAAA,MAC1B;AAAA,IACF;AACA,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;AACpD,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,sBAAsB,MAAuB;AACpD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,MAAI,KAAK,SAAS,IAAK,QAAO;AAG9B,MAAI,KAAK,WAAW,GAAG,GAAG;AACxB,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAG,QAAO;AACnC,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,EAAG,QAAO;AAEzD,SAAO;AACT;",
|
|
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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,SAAoB;AACpB,WAAsB;AAEtB,oBAAwB;AAgBxB,eAAsB,qBACpB,cACA,aAC6B;AAC7B,QAAM,cAAc,KAAK,QAAQ,YAAY;AAC7C,QAAM,mBAAmB,KAAK,SAAS,aAAa,YAAY,EAAE,QAAQ,OAAO,GAAG;AAGpF,MAAI;AACJ,MAAI;AACF,cAAU,GAAG,aAAa,cAAc,MAAM;AAAA,EAChD,SAAS,KAAK;AACZ,+BAAQ,4BAA4B,gBAAgB,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC3G,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,mBAAmB,qBAAqB,WAAW;AAGzD,QAAM,UAAU,sBAAsB,OAAO;AAE7C,QAAM,eAAmC,CAAC;AAG1C,QAAM,WAAqE;AAAA,IACzE,EAAE,KAAK,gBAAgB,OAAO,aAAa;AAAA,IAC3C,EAAE,KAAK,oBAAoB,OAAO,cAAc;AAAA,EAClD;AAEA,aAAW,EAAE,KAAK,MAAM,KAAK,UAAU;AACrC,UAAM,cAAc,2BAA2B,SAAS,KAAK,gBAAgB;AAC7E,eAAW,OAAO,aAAa;AAC7B,YAAM,kBAAkB,iBAAiB,IAAI,IAAI,IAAI;AACrD,mBAAa,KAAK;AAAA,QAChB,MAAM,IAAI;AAAA,QACV,oBAAoB,IAAI;AAAA,QACxB;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,gBAAgB;AAAA,QAChB,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC7C,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAuBA,SAAS,2BACP,SACA,YACA,kBACiB;AACjB,QAAM,OAAwB,CAAC;AAC/B,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAGhC,QAAM,gBAAgB,IAAI,OAAO,IAAI,YAAY,UAAU,CAAC,YAAY;AACxE,MAAI,YAAY;AAChB,MAAI,IAAI;AAER,SAAO,IAAI,MAAM,QAAQ;AACvB,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,UAAU,KAAK,QAAQ;AAG7B,QAAI,CAAC,WAAW;AACd,UAAI,cAAc,KAAK,OAAO,GAAG;AAC/B,oBAAY;AAAA,MACd;AACA;AACA;AAAA,IACF;AAGA,QAAI,YAAY,MAAM,CAAC,MAAM,KAAK,OAAO,GAAG;AAC1C;AAAA,IACF;AAGA,QAAI,YAAY,IAAI;AAClB;AACA;AAAA,IACF;AAGA,UAAM,aAAa,QAAQ,MAAM,2CAA2C;AAC5E,QAAI,CAAC,YAAY;AACf;AACA;AAAA,IACF;AAEA,UAAM,UAAU,WAAW,CAAC;AAC5B,UAAM,kBAAkB,WAAW,CAAC,EAAE,KAAK;AAG3C,UAAM,WAAW,IAAI,IAAI,MAAM,SAAS,MAAM,IAAI,CAAC,IAAI;AACvD,UAAM,iBAAiB,aAAa,MAAM,UAAU,KAAK,QAAQ;AAEjE,QAAI,gBAAgB;AAElB;AACA,aAAO,IAAI,MAAM,QAAQ;AACvB,cAAM,UAAU,MAAM,CAAC;AACvB,YAAI,QAAQ,QAAQ,MAAM,MAAM,QAAQ,KAAK,OAAO,GAAG;AACrD;AAAA,QACF,OAAO;AACL;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,gBAAgB,OAAO,GAAG;AAC5B;AACA;AAAA,IACF;AAEA,QAAI,CAAC,mBAAmB,OAAO,GAAG;AAChC,iCAAQ,mCAAmC,OAAO,QAAQ,gBAAgB,kBAAa;AACvF;AACA;AAAA,IACF;AAEA,UAAM,aAAa,qBAAqB,eAAe;AACvD,SAAK,KAAK,EAAE,MAAM,SAAS,WAAW,CAAC;AACvC;AAAA,EACF;AAEA,SAAO;AACT;AAYA,SAAS,qBAAqB,OAAuB;AACnD,MAAI,CAAC,SAAS,UAAU,MAAM,MAAM,YAAY,MAAM,MAAO,QAAO;AAGpE,MAAK,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAAI;AAClD,YAAQ,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK;AAAA,EAClC;AAEA,SAAO,SAAS;AAClB;AAKA,SAAS,gBAAgB,MAAuB;AAC9C,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,SAAS,UAAW,QAAO;AAC/B,SAAO;AACT;AAMA,SAAS,mBAAmB,MAAuB;AACjD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,MAAI,KAAK,SAAS,IAAK,QAAO;AAC9B,SAAO,gCAAgC,KAAK,IAAI;AAClD;AAoBA,SAAS,qBAAqB,aAAyC;AACrE,QAAM,MAA0B,oBAAI,IAAI;AAExC,QAAM,WAAW,KAAK,KAAK,aAAa,cAAc;AACtD,MAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,cAAc,GAAG,aAAa,UAAU,MAAM;AACpD,2BAAuB,aAAa,GAAG;AAAA,EACzC,SAAS,KAAK;AACZ,+BAAQ,2CAA2C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EACvG;AAEA,SAAO;AACT;AASA,SAAS,uBAAuB,SAAiB,KAA+B;AAC9E,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,MAAI,oBAAoB;AACxB,MAAI,iBAAgC;AAEpC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,QAAQ;AAG7B,QAAI,YAAY,aAAa;AAC3B,0BAAoB;AACpB,uBAAiB;AACjB;AAAA,IACF;AAGA,QAAI,qBAAqB,YAAY,MAAM,CAAC,MAAM,KAAK,OAAO,GAAG;AAC/D,0BAAoB;AACpB,uBAAiB;AACjB;AAAA,IACF;AAEA,QAAI,CAAC,kBAAmB;AAGxB,UAAM,eAAe,QAAQ,MAAM,uCAAuC;AAC1E,QAAI,cAAc;AAChB,uBAAiB,aAAa,CAAC;AAC/B;AAAA,IACF;AAGA,QAAI,gBAAgB;AAClB,YAAM,eAAe,QAAQ,MAAM,0CAA0C;AAC7E,UAAI,cAAc;AAChB,YAAI,CAAC,IAAI,IAAI,cAAc,GAAG;AAC5B,cAAI,IAAI,gBAAgB,aAAa,CAAC,CAAC;AAAA,QACzC;AACA,yBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,uBAAuB,MAAM;AAClD;AAMA,SAAS,sBAAsB,SAAqC;AAClE,QAAM,QAAQ,QAAQ,MAAM,oBAAoB;AAChD,MAAI,SAAS,MAAM,CAAC,EAAE,KAAK,MAAM,IAAI;AAEnC,QAAI,QAAQ,MAAM,CAAC,EAAE,KAAK;AAC1B,QAAK,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAAI;AAClD,cAAQ,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK;AAAA,IAClC;AACA,WAAO,SAAS;AAAA,EAClB;AACA,SAAO;AACT;",
|
|
4
|
+
"sourcesContent": ["/**\n * Dart/Flutter pub Version Extraction Plugin for KiroGraph-Sec\n *\n * Parses pubspec.yaml manifests to extract Dart package declarations with\n * version constraints, scopes, and resolved versions from pubspec.lock.\n *\n * Uses lightweight regex-based YAML parsing \u2014 no external YAML library required.\n *\n * Handles:\n * - `dependencies:` section \u2192 'production' scope\n * - `dev_dependencies:` section \u2192 'development' scope\n * - Version formats: `^1.0.0`, `\">=1.0.0 <2.0.0\"`, `any`\n * - Skips SDK entries (`sdk:`), `flutter:`, path deps, and git deps\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\n/**\n * Resolved version map: package name \u2192 resolved version string.\n * Built from pubspec.lock.\n */\ntype ResolvedVersionMap = Map<string, string>;\n\n/**\n * Parse a pubspec.yaml manifest and extract package declarations with\n * version constraints, scopes, and resolved versions from pubspec.lock.\n *\n * @param manifestPath - Absolute path to the pubspec.yaml file\n * @param projectRoot - Absolute path to the project root directory\n * @returns Array of parsed dependencies with version and scope information\n */\nexport async function parsePubspecManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const manifestDir = path.dirname(manifestPath);\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n // Read the pubspec.yaml content\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:pub] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n // Attempt to load resolved versions from pubspec.lock\n const resolvedVersions = loadResolvedVersions(manifestDir);\n\n // Extract the license field if present\n const license = extractPubspecLicense(content);\n\n const dependencies: ParsedDependency[] = [];\n\n // Process `dependencies` (production) and `dev_dependencies` (development)\n const sections: Array<{ key: string; scope: ParsedDependency['scope'] }> = [\n { key: 'dependencies', scope: 'production' },\n { key: 'dev_dependencies', scope: 'development' },\n ];\n\n for (const { key, scope } of sections) {\n const sectionDeps = extractSectionDependencies(content, key, relativeManifest);\n for (const dep of sectionDeps) {\n const resolvedVersion = resolvedVersions.get(dep.name);\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion,\n scope,\n ecosystem: 'pub',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Transitive-only packages (present in pubspec.lock but never declared in\n // pubspec.yaml) would otherwise never become a Dependency_Node and would\n // silently skip vulnerability scanning. resolvedVersions already has every\n // package pubspec.lock resolved, direct or transitive.\n const directNames = new Set(dependencies.map(d => d.name));\n for (const [name, version] of resolvedVersions) {\n if (directNames.has(name)) continue;\n dependencies.push({\n name,\n declaredConstraint: version,\n resolvedVersion: version,\n scope: 'production',\n ecosystem: 'pub',\n sourceManifest: relativeManifest,\n });\n }\n\n return dependencies;\n}\n\n/**\n * A raw dependency entry extracted from a pubspec.yaml section.\n */\ninterface RawDependency {\n name: string;\n constraint: string;\n}\n\n/**\n * Extract dependencies from a specific top-level section of a pubspec.yaml file.\n * Uses indentation-based parsing to determine section boundaries.\n *\n * Section entries take one of three forms:\n * - ` package_name: ^1.0.0` (inline scalar version)\n * - ` package_name: \">=1.0.0 <2.0.0\"` (quoted version)\n * - ` package_name: any` (any version)\n * - ` package_name:` (block mapping \u2014 path/git/sdk dep)\n * ` path: ../local`\n *\n * Entries with sub-keys (block mappings) are skipped as non-registry deps.\n */\nfunction extractSectionDependencies(\n content: string,\n sectionKey: string,\n relativeManifest: string,\n): RawDependency[] {\n const deps: RawDependency[] = [];\n const lines = content.split('\\n');\n\n // Find the section header line (at column 0, no indent)\n const headerPattern = new RegExp(`^${escapeRegex(sectionKey)}\\\\s*:\\\\s*$`);\n let inSection = false;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n const trimmed = line.trimEnd();\n\n // Detect section header\n if (!inSection) {\n if (headerPattern.test(trimmed)) {\n inSection = true;\n }\n i++;\n continue;\n }\n\n // Exit section when we encounter another top-level key (no leading whitespace)\n if (trimmed !== '' && !/^\\s/.test(trimmed)) {\n break;\n }\n\n // Skip blank lines inside section\n if (trimmed === '') {\n i++;\n continue;\n }\n\n // Match a 2-space-indented package entry: ` package_name: ...`\n const entryMatch = trimmed.match(/^ ([a-zA-Z0-9_][a-zA-Z0-9_.-]*):\\s*(.*)$/);\n if (!entryMatch) {\n i++;\n continue;\n }\n\n const pkgName = entryMatch[1];\n const valueOnSameLine = entryMatch[2].trim();\n\n // Check if the next line has deeper indentation (block mapping \u2192 skip)\n const nextLine = i + 1 < lines.length ? lines[i + 1] : '';\n const isBlockMapping = nextLine !== '' && /^ \\S/.test(nextLine);\n\n if (isBlockMapping) {\n // Skip \u2014 path/git/sdk dependency; advance past the block\n i++;\n while (i < lines.length) {\n const subLine = lines[i];\n if (subLine.trimEnd() === '' || /^ /.test(subLine)) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Skip SDK and flutter pseudo-packages\n if (shouldSkipEntry(pkgName)) {\n i++;\n continue;\n }\n\n if (!isValidPackageName(pkgName)) {\n logWarn(`[sec:pub] Invalid package name \"${pkgName}\" in ${relativeManifest} \u2014 skipping`);\n i++;\n continue;\n }\n\n const constraint = parseConstraintValue(valueOnSameLine);\n deps.push({ name: pkgName, constraint });\n i++;\n }\n\n return deps;\n}\n\n/**\n * Parse a pubspec.yaml dependency version value string.\n *\n * Handles:\n * - `^1.0.0` \u2192 `^1.0.0`\n * - `\">=1.0.0 <2.0.0\"` \u2192 `>=1.0.0 <2.0.0` (quotes stripped)\n * - `'>=1.0.0 <2.0.0'` \u2192 `>=1.0.0 <2.0.0`\n * - `any` \u2192 `*`\n * - empty \u2192 `*`\n */\nfunction parseConstraintValue(value: string): string {\n if (!value || value === '' || value.toLowerCase() === 'any') return '*';\n\n // Strip surrounding quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1).trim();\n }\n\n return value || '*';\n}\n\n/**\n * Determine whether a pubspec.yaml dependency key should be skipped.\n */\nfunction shouldSkipEntry(name: string): boolean {\n if (name === 'sdk') return true;\n if (name === 'flutter') return true;\n return false;\n}\n\n/**\n * Validate that a pub package name follows Dart naming conventions.\n * Package names must be lowercase, with underscores, not hyphens.\n */\nfunction isValidPackageName(name: string): boolean {\n if (!name || typeof name !== 'string') return false;\n if (name.length > 128) return false;\n return /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(name);\n}\n\n/**\n * Attempt to load resolved versions from pubspec.lock in the given directory.\n * Returns an empty map if pubspec.lock is not available.\n *\n * pubspec.lock YAML format:\n * ```yaml\n * packages:\n * http:\n * dependency: \"direct main\"\n * description:\n * name: http\n * url: \"https://pub.dartlang.org\"\n * version: \"0.13.5\"\n * test:\n * dependency: \"direct dev\"\n * version: \"1.21.4\"\n * ```\n */\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n const lockPath = path.join(manifestDir, 'pubspec.lock');\n if (!fs.existsSync(lockPath)) {\n return map;\n }\n\n try {\n const lockContent = fs.readFileSync(lockPath, 'utf8');\n extractFromPubspecLock(lockContent, map);\n } catch (err) {\n logWarn(`[sec:pub] Failed to parse pubspec.lock: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return map;\n}\n\n/**\n * Extract resolved versions from pubspec.lock content.\n *\n * The lock file is a YAML document. We use regex-based parsing to avoid\n * introducing a YAML library dependency. For each package block under\n * `packages:`, we find its `version:` entry.\n */\nfunction extractFromPubspecLock(content: string, map: ResolvedVersionMap): void {\n const lines = content.split('\\n');\n let inPackagesSection = false;\n let currentPackage: string | null = null;\n\n for (const line of lines) {\n const trimmed = line.trimEnd();\n\n // Detect top-level `packages:` section\n if (trimmed === 'packages:') {\n inPackagesSection = true;\n currentPackage = null;\n continue;\n }\n\n // Exit packages section if we hit another top-level key\n if (inPackagesSection && trimmed !== '' && !/^\\s/.test(trimmed)) {\n inPackagesSection = false;\n currentPackage = null;\n continue;\n }\n\n if (!inPackagesSection) continue;\n\n // Package name entry: ` package_name:` (2-space indent)\n const pkgNameMatch = trimmed.match(/^ ([a-zA-Z0-9_][a-zA-Z0-9_.-]*):\\s*$/);\n if (pkgNameMatch) {\n currentPackage = pkgNameMatch[1];\n continue;\n }\n\n // Version entry: ` version: \"1.2.3\"` (4-space indent)\n if (currentPackage) {\n const versionMatch = trimmed.match(/^ version:\\s*[\"']?([^\"'\\s]+)[\"']?\\s*$/);\n if (versionMatch) {\n if (!map.has(currentPackage)) {\n map.set(currentPackage, versionMatch[1]);\n }\n currentPackage = null;\n }\n }\n }\n}\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract the `license:` field from pubspec.yaml content.\n * Handles: `license: MIT` (top-level scalar field)\n */\nfunction extractPubspecLicense(content: string): string | undefined {\n const match = content.match(/^license:\\s*(.+)$/m);\n if (match && match[1].trim() !== '') {\n // Strip surrounding quotes if present\n let value = match[1].trim();\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1).trim();\n }\n return value || undefined;\n }\n return undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -89,6 +89,18 @@ async function parsePyprojectManifest(manifestPath, projectRoot) {
|
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
const directNames = new Set(dependencies.map((d) => d.name.toLowerCase()));
|
|
93
|
+
for (const [name, version] of resolvedVersions) {
|
|
94
|
+
if (directNames.has(name)) continue;
|
|
95
|
+
dependencies.push({
|
|
96
|
+
name,
|
|
97
|
+
declaredConstraint: version,
|
|
98
|
+
resolvedVersion: version,
|
|
99
|
+
scope: "production",
|
|
100
|
+
ecosystem: "pypi",
|
|
101
|
+
sourceManifest: relativeManifest
|
|
102
|
+
});
|
|
103
|
+
}
|
|
92
104
|
return dependencies;
|
|
93
105
|
}
|
|
94
106
|
function extractPep621Dependencies(content, relativeManifest) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/security/manifest/plugins/pyproject.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * pyproject.toml Version Extraction Plugin for KiroGraph-Sec\n *\n * Handles modern Python packaging: PEP 621 (standard), Poetry, Hatch, PDM, Flit.\n * pyproject.toml is the canonical manifest for virtually all Python projects\n * created after 2021 \u2014 requirements.txt is only used for pinned deployment deps.\n *\n * Supports:\n * - PEP 621 [project] dependencies = [\"package>=1.0\"]\n * - Poetry [tool.poetry.dependencies] { package = \"^1.0\" }\n * - PDM [tool.pdm.dev-dependencies] { dev = [\"package>=1.0\"] }\n * - Hatch [tool.hatch.envs.default.dependencies] = [\"package>=1.0\"]\n * Lock files: poetry.lock, pdm.lock, uv.lock (all share the same version extraction)\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\ntype ResolvedVersionMap = Map<string, string>;\n\n// \u2500\u2500 Entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function parsePyprojectManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:pyproject] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n const manifestDir = path.dirname(manifestPath);\n const resolvedVersions = loadResolvedVersions(manifestDir);\n const license = extractPyprojectLicense(content);\n const dependencies: ParsedDependency[] = [];\n\n // \u2500\u2500 PEP 621 [project] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const pep621Deps = extractPep621Dependencies(content, relativeManifest);\n for (const dep of pep621Deps) {\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion: resolvedVersions.get(dep.name.toLowerCase()),\n scope: dep.scope,\n ecosystem: 'pypi',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n // \u2500\u2500 Poetry [tool.poetry.dependencies] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (dependencies.length === 0 || hasPoetrySection(content)) {\n const poetryDeps = extractPoetryDependencies(content, relativeManifest);\n for (const dep of poetryDeps) {\n if (!dependencies.find(d => d.name === dep.name)) {\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion: resolvedVersions.get(dep.name.toLowerCase()),\n scope: dep.scope,\n ecosystem: 'pypi',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n }\n\n // \u2500\u2500 PDM [tool.pdm.dev-dependencies] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const pdmDeps = extractPdmDependencies(content, relativeManifest);\n for (const dep of pdmDeps) {\n if (!dependencies.find(d => d.name === dep.name)) {\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion: resolvedVersions.get(dep.name.toLowerCase()),\n scope: dep.scope,\n ecosystem: 'pypi',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n return dependencies;\n}\n\n// \u2500\u2500 PEP 621 extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface RawDep { name: string; constraint: string; scope: ParsedDependency['scope'] }\n\nfunction extractPep621Dependencies(content: string, relativeManifest: string): RawDep[] {\n const deps: RawDep[] = [];\n\n // [project] dependencies = [\"package>=1.0\", ...]\n const depsBlock = extractTomlArray(content, /^\\[project\\]/m, 'dependencies');\n for (const spec of depsBlock) {\n const dep = parsePep508Spec(spec, relativeManifest);\n if (dep) deps.push({ ...dep, scope: 'production' });\n }\n\n // [project.optional-dependencies] group = [\"package>=1.0\", ...]\n const optSection = extractSection(content, /^\\[project\\.optional-dependencies\\]/m);\n if (optSection) {\n for (const m of optSection.matchAll(/^\\s*([a-zA-Z0-9_-]+)\\s*=\\s*\\[([^\\]]*)\\]/gm)) {\n const groupName = m[1].toLowerCase();\n const isDevGroup = /^(dev|test|lint|docs|ci|build|check|debug)/.test(groupName);\n const items = parseTomlStringArray(m[2]);\n for (const spec of items) {\n const dep = parsePep508Spec(spec, relativeManifest);\n if (dep) deps.push({ ...dep, scope: isDevGroup ? 'development' : 'optional' });\n }\n }\n }\n\n return deps;\n}\n\n// \u2500\u2500 Poetry extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction hasPoetrySection(content: string): boolean {\n return /^\\[tool\\.poetry/m.test(content);\n}\n\nfunction extractPoetryDependencies(content: string, relativeManifest: string): RawDep[] {\n const deps: RawDep[] = [];\n\n const sections: Array<{ pattern: RegExp; scope: ParsedDependency['scope'] }> = [\n { pattern: /^\\[tool\\.poetry\\.dependencies\\]/m, scope: 'production' },\n { pattern: /^\\[tool\\.poetry\\.dev-dependencies\\]/m, scope: 'development' },\n ];\n\n // Also handle [tool.poetry.group.*.dependencies]\n for (const m of content.matchAll(/^\\[tool\\.poetry\\.group\\.([a-zA-Z0-9_-]+)\\.dependencies\\]/gm)) {\n const groupName = m[1].toLowerCase();\n const scope: ParsedDependency['scope'] = /^(dev|test|lint|docs|ci)/.test(groupName) ? 'development' : 'optional';\n sections.push({ pattern: new RegExp(`^\\\\[tool\\\\.poetry\\\\.group\\\\.${m[1]}\\\\.dependencies\\\\]`, 'm'), scope });\n }\n\n for (const { pattern, scope } of sections) {\n const section = extractSection(content, pattern);\n if (!section) continue;\n\n for (const line of section.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith('[')) break;\n\n // name = \"^1.0\" or name = { version = \"^1.0\", ... }\n const simpleMatch = trimmed.match(/^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)\\s*=\\s*\"([^\"]+)\"/);\n if (simpleMatch) {\n const name = simpleMatch[1];\n if (name === 'python') continue; // skip python version constraint\n deps.push({ name: normalizePypiName(name), constraint: simpleMatch[3], scope });\n continue;\n }\n\n // name = { version = \"^1.0\", ... }\n const tableMatch = trimmed.match(/^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)\\s*=\\s*\\{/);\n if (tableMatch) {\n const name = tableMatch[1];\n if (name === 'python') continue;\n const versionMatch = trimmed.match(/version\\s*=\\s*\"([^\"]+)\"/);\n if (versionMatch) {\n deps.push({ name: normalizePypiName(name), constraint: versionMatch[1], scope });\n } else {\n // path/git dep without version \u2014 record with wildcard\n deps.push({ name: normalizePypiName(name), constraint: '*', scope });\n }\n }\n }\n }\n\n return deps;\n}\n\n// \u2500\u2500 PDM extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction extractPdmDependencies(content: string, relativeManifest: string): RawDep[] {\n const deps: RawDep[] = [];\n\n // [tool.pdm.dev-dependencies] groups with arrays\n const section = extractSection(content, /^\\[tool\\.pdm\\.dev-dependencies\\]/m);\n if (section) {\n for (const m of section.matchAll(/^\\s*([a-zA-Z0-9_-]+)\\s*=\\s*\\[([^\\]]*)\\]/gm)) {\n const items = parseTomlStringArray(m[2]);\n for (const spec of items) {\n const dep = parsePep508Spec(spec, relativeManifest);\n if (dep) deps.push({ ...dep, scope: 'development' });\n }\n }\n }\n\n return deps;\n}\n\n// \u2500\u2500 PEP 508 spec parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction parsePep508Spec(spec: string, _relativeManifest: string): { name: string; constraint: string } | null {\n // PEP 508: name [extras] [version_spec] [; marker]\n // Strip markers (semicolon and beyond), extras ([...])\n const withoutMarker = spec.split(';')[0].trim();\n const withoutExtras = withoutMarker.replace(/\\[[^\\]]*\\]/, '').trim();\n\n const match = withoutExtras.match(/^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)\\s*(.*)/);\n if (!match) return null;\n\n const name = normalizePypiName(match[1]);\n const constraint = match[3].trim() || '*';\n return { name, constraint };\n}\n\n// \u2500\u2500 Lock file loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n // Try poetry.lock, pdm.lock, uv.lock in order\n const candidates = ['poetry.lock', 'pdm.lock', 'uv.lock'];\n for (const lockFile of candidates) {\n const lockPath = path.join(manifestDir, lockFile);\n if (!fs.existsSync(lockPath)) continue;\n try {\n const content = fs.readFileSync(lockPath, 'utf8');\n if (lockFile === 'poetry.lock') extractFromPoetryLock(content, map);\n else extractFromPdmUvLock(content, map);\n break; // only use the first lock file found\n } catch (err) {\n logWarn(`[sec:pyproject] Failed to parse ${lockFile}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return map;\n}\n\nfunction extractFromPoetryLock(content: string, map: ResolvedVersionMap): void {\n // [[package]] blocks with name and version fields\n for (const m of content.matchAll(/\\[\\[package\\]\\][^[]*?name\\s*=\\s*\"([^\"]+)\"[^[]*?version\\s*=\\s*\"([^\"]+)\"/gs)) {\n map.set(m[1].toLowerCase(), m[2]);\n }\n}\n\nfunction extractFromPdmUvLock(content: string, map: ResolvedVersionMap): void {\n // [[package]] or [[distribution]] blocks (pdm/uv use slightly different headers)\n for (const m of content.matchAll(/\\[\\[(?:package|distribution)\\]\\][^[]*?name\\s*=\\s*\"([^\"]+)\"[^[]*?version\\s*=\\s*\"([^\"]+)\"/gs)) {\n map.set(m[1].toLowerCase(), m[2]);\n }\n}\n\n// \u2500\u2500 TOML helpers (no library dependency) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Extract the content after a section header until the next header. */\nfunction extractSection(content: string, headerPattern: RegExp): string | null {\n const match = headerPattern.exec(content);\n if (!match) return null;\n const start = match.index + match[0].length;\n const remaining = content.slice(start);\n const nextSection = remaining.match(/^\\[/m);\n return nextSection ? remaining.slice(0, nextSection.index) : remaining;\n}\n\n/** Extract a TOML array value by key from a section. */\nfunction extractTomlArray(content: string, sectionPattern: RegExp, key: string): string[] {\n const section = extractSection(content, sectionPattern);\n if (!section) return [];\n\n // Match key = [\"...\", \"...\", ...] \u2014 may span multiple lines\n const keyPattern = new RegExp(`^${key}\\\\s*=\\\\s*\\\\[([^\\\\]]*(?:\\\\][^\\\\[\\\\]]*\\\\[)*[^\\\\]]*)\\\\]`, 'ms');\n const m = keyPattern.exec(section);\n if (!m) return [];\n return parseTomlStringArray(m[1]);\n}\n\n/** Parse a TOML string array body (contents inside [...]) into individual strings. */\nfunction parseTomlStringArray(body: string): string[] {\n const results: string[] = [];\n for (const m of body.matchAll(/\"([^\"]+)\"/g)) {\n results.push(m[1].trim());\n }\n for (const m of body.matchAll(/'([^']+)'/g)) {\n results.push(m[1].trim());\n }\n return results.filter(Boolean);\n}\n\n/** Normalize PyPI package name: lowercase, replace - and _ with - (canonical form). */\nfunction normalizePypiName(name: string): string {\n return name.toLowerCase().replace(/[-_.]+/g, '-');\n}\n\n/**\n * Extract the license from a pyproject.toml file.\n *\n * Handles:\n * - PEP 621 [project] license = {text = \"MIT\"} or license = \"MIT\"\n * - Poetry [tool.poetry] license = \"MIT\"\n */\nfunction extractPyprojectLicense(content: string): string | undefined {\n // PEP 621: license = {text = \"MIT\"} or license = {file = \"LICENSE\"}\n const pep621Section = extractSection(content, /^\\[project\\]/m);\n if (pep621Section) {\n // license = {text = \"MIT\"}\n const tableTextMatch = pep621Section.match(/^license\\s*=\\s*\\{[^}]*text\\s*=\\s*[\"']([^\"']+)[\"'][^}]*\\}/m);\n if (tableTextMatch && tableTextMatch[1].trim() !== '') {\n return tableTextMatch[1].trim();\n }\n // license = \"MIT\"\n const stringMatch = pep621Section.match(/^license\\s*=\\s*[\"']([^\"']+)[\"']/m);\n if (stringMatch && stringMatch[1].trim() !== '') {\n return stringMatch[1].trim();\n }\n }\n\n // Poetry: [tool.poetry] license = \"MIT\"\n const poetrySection = extractSection(content, /^\\[tool\\.poetry\\]/m);\n if (poetrySection) {\n const poetryLicenseMatch = poetrySection.match(/^license\\s*=\\s*[\"']([^\"']+)[\"']/m);\n if (poetryLicenseMatch && poetryLicenseMatch[1].trim() !== '') {\n return poetryLicenseMatch[1].trim();\n }\n }\n\n return undefined;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * pyproject.toml Version Extraction Plugin for KiroGraph-Sec\n *\n * Handles modern Python packaging: PEP 621 (standard), Poetry, Hatch, PDM, Flit.\n * pyproject.toml is the canonical manifest for virtually all Python projects\n * created after 2021 \u2014 requirements.txt is only used for pinned deployment deps.\n *\n * Supports:\n * - PEP 621 [project] dependencies = [\"package>=1.0\"]\n * - Poetry [tool.poetry.dependencies] { package = \"^1.0\" }\n * - PDM [tool.pdm.dev-dependencies] { dev = [\"package>=1.0\"] }\n * - Hatch [tool.hatch.envs.default.dependencies] = [\"package>=1.0\"]\n * Lock files: poetry.lock, pdm.lock, uv.lock (all share the same version extraction)\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ParsedDependency } from '../../types';\nimport { logWarn } from '../../../errors';\n\ntype ResolvedVersionMap = Map<string, string>;\n\n// \u2500\u2500 Entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function parsePyprojectManifest(\n manifestPath: string,\n projectRoot: string,\n): Promise<ParsedDependency[]> {\n const relativeManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch (err) {\n logWarn(`[sec:pyproject] Failed to read ${relativeManifest}: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n const manifestDir = path.dirname(manifestPath);\n const resolvedVersions = loadResolvedVersions(manifestDir);\n const license = extractPyprojectLicense(content);\n const dependencies: ParsedDependency[] = [];\n\n // \u2500\u2500 PEP 621 [project] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const pep621Deps = extractPep621Dependencies(content, relativeManifest);\n for (const dep of pep621Deps) {\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion: resolvedVersions.get(dep.name.toLowerCase()),\n scope: dep.scope,\n ecosystem: 'pypi',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n\n // \u2500\u2500 Poetry [tool.poetry.dependencies] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (dependencies.length === 0 || hasPoetrySection(content)) {\n const poetryDeps = extractPoetryDependencies(content, relativeManifest);\n for (const dep of poetryDeps) {\n if (!dependencies.find(d => d.name === dep.name)) {\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion: resolvedVersions.get(dep.name.toLowerCase()),\n scope: dep.scope,\n ecosystem: 'pypi',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n }\n\n // \u2500\u2500 PDM [tool.pdm.dev-dependencies] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const pdmDeps = extractPdmDependencies(content, relativeManifest);\n for (const dep of pdmDeps) {\n if (!dependencies.find(d => d.name === dep.name)) {\n dependencies.push({\n name: dep.name,\n declaredConstraint: dep.constraint,\n resolvedVersion: resolvedVersions.get(dep.name.toLowerCase()),\n scope: dep.scope,\n ecosystem: 'pypi',\n sourceManifest: relativeManifest,\n ...(license !== undefined ? { license } : {}),\n });\n }\n }\n\n // Transitive-only packages (present in poetry.lock/pdm.lock/uv.lock but\n // never declared in pyproject.toml) would otherwise never become a\n // Dependency_Node and would silently skip vulnerability scanning.\n // resolvedVersions already has every package the lock file resolved,\n // direct or transitive (lower-cased \u2014 original casing isn't retained).\n const directNames = new Set(dependencies.map(d => d.name.toLowerCase()));\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: 'pypi',\n sourceManifest: relativeManifest,\n });\n }\n\n return dependencies;\n}\n\n// \u2500\u2500 PEP 621 extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface RawDep { name: string; constraint: string; scope: ParsedDependency['scope'] }\n\nfunction extractPep621Dependencies(content: string, relativeManifest: string): RawDep[] {\n const deps: RawDep[] = [];\n\n // [project] dependencies = [\"package>=1.0\", ...]\n const depsBlock = extractTomlArray(content, /^\\[project\\]/m, 'dependencies');\n for (const spec of depsBlock) {\n const dep = parsePep508Spec(spec, relativeManifest);\n if (dep) deps.push({ ...dep, scope: 'production' });\n }\n\n // [project.optional-dependencies] group = [\"package>=1.0\", ...]\n const optSection = extractSection(content, /^\\[project\\.optional-dependencies\\]/m);\n if (optSection) {\n for (const m of optSection.matchAll(/^\\s*([a-zA-Z0-9_-]+)\\s*=\\s*\\[([^\\]]*)\\]/gm)) {\n const groupName = m[1].toLowerCase();\n const isDevGroup = /^(dev|test|lint|docs|ci|build|check|debug)/.test(groupName);\n const items = parseTomlStringArray(m[2]);\n for (const spec of items) {\n const dep = parsePep508Spec(spec, relativeManifest);\n if (dep) deps.push({ ...dep, scope: isDevGroup ? 'development' : 'optional' });\n }\n }\n }\n\n return deps;\n}\n\n// \u2500\u2500 Poetry extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction hasPoetrySection(content: string): boolean {\n return /^\\[tool\\.poetry/m.test(content);\n}\n\nfunction extractPoetryDependencies(content: string, relativeManifest: string): RawDep[] {\n const deps: RawDep[] = [];\n\n const sections: Array<{ pattern: RegExp; scope: ParsedDependency['scope'] }> = [\n { pattern: /^\\[tool\\.poetry\\.dependencies\\]/m, scope: 'production' },\n { pattern: /^\\[tool\\.poetry\\.dev-dependencies\\]/m, scope: 'development' },\n ];\n\n // Also handle [tool.poetry.group.*.dependencies]\n for (const m of content.matchAll(/^\\[tool\\.poetry\\.group\\.([a-zA-Z0-9_-]+)\\.dependencies\\]/gm)) {\n const groupName = m[1].toLowerCase();\n const scope: ParsedDependency['scope'] = /^(dev|test|lint|docs|ci)/.test(groupName) ? 'development' : 'optional';\n sections.push({ pattern: new RegExp(`^\\\\[tool\\\\.poetry\\\\.group\\\\.${m[1]}\\\\.dependencies\\\\]`, 'm'), scope });\n }\n\n for (const { pattern, scope } of sections) {\n const section = extractSection(content, pattern);\n if (!section) continue;\n\n for (const line of section.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith('[')) break;\n\n // name = \"^1.0\" or name = { version = \"^1.0\", ... }\n const simpleMatch = trimmed.match(/^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)\\s*=\\s*\"([^\"]+)\"/);\n if (simpleMatch) {\n const name = simpleMatch[1];\n if (name === 'python') continue; // skip python version constraint\n deps.push({ name: normalizePypiName(name), constraint: simpleMatch[3], scope });\n continue;\n }\n\n // name = { version = \"^1.0\", ... }\n const tableMatch = trimmed.match(/^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)\\s*=\\s*\\{/);\n if (tableMatch) {\n const name = tableMatch[1];\n if (name === 'python') continue;\n const versionMatch = trimmed.match(/version\\s*=\\s*\"([^\"]+)\"/);\n if (versionMatch) {\n deps.push({ name: normalizePypiName(name), constraint: versionMatch[1], scope });\n } else {\n // path/git dep without version \u2014 record with wildcard\n deps.push({ name: normalizePypiName(name), constraint: '*', scope });\n }\n }\n }\n }\n\n return deps;\n}\n\n// \u2500\u2500 PDM extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction extractPdmDependencies(content: string, relativeManifest: string): RawDep[] {\n const deps: RawDep[] = [];\n\n // [tool.pdm.dev-dependencies] groups with arrays\n const section = extractSection(content, /^\\[tool\\.pdm\\.dev-dependencies\\]/m);\n if (section) {\n for (const m of section.matchAll(/^\\s*([a-zA-Z0-9_-]+)\\s*=\\s*\\[([^\\]]*)\\]/gm)) {\n const items = parseTomlStringArray(m[2]);\n for (const spec of items) {\n const dep = parsePep508Spec(spec, relativeManifest);\n if (dep) deps.push({ ...dep, scope: 'development' });\n }\n }\n }\n\n return deps;\n}\n\n// \u2500\u2500 PEP 508 spec parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction parsePep508Spec(spec: string, _relativeManifest: string): { name: string; constraint: string } | null {\n // PEP 508: name [extras] [version_spec] [; marker]\n // Strip markers (semicolon and beyond), extras ([...])\n const withoutMarker = spec.split(';')[0].trim();\n const withoutExtras = withoutMarker.replace(/\\[[^\\]]*\\]/, '').trim();\n\n const match = withoutExtras.match(/^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)\\s*(.*)/);\n if (!match) return null;\n\n const name = normalizePypiName(match[1]);\n const constraint = match[3].trim() || '*';\n return { name, constraint };\n}\n\n// \u2500\u2500 Lock file loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction loadResolvedVersions(manifestDir: string): ResolvedVersionMap {\n const map: ResolvedVersionMap = new Map();\n\n // Try poetry.lock, pdm.lock, uv.lock in order\n const candidates = ['poetry.lock', 'pdm.lock', 'uv.lock'];\n for (const lockFile of candidates) {\n const lockPath = path.join(manifestDir, lockFile);\n if (!fs.existsSync(lockPath)) continue;\n try {\n const content = fs.readFileSync(lockPath, 'utf8');\n if (lockFile === 'poetry.lock') extractFromPoetryLock(content, map);\n else extractFromPdmUvLock(content, map);\n break; // only use the first lock file found\n } catch (err) {\n logWarn(`[sec:pyproject] Failed to parse ${lockFile}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return map;\n}\n\nfunction extractFromPoetryLock(content: string, map: ResolvedVersionMap): void {\n // [[package]] blocks with name and version fields\n for (const m of content.matchAll(/\\[\\[package\\]\\][^[]*?name\\s*=\\s*\"([^\"]+)\"[^[]*?version\\s*=\\s*\"([^\"]+)\"/gs)) {\n map.set(m[1].toLowerCase(), m[2]);\n }\n}\n\nfunction extractFromPdmUvLock(content: string, map: ResolvedVersionMap): void {\n // [[package]] or [[distribution]] blocks (pdm/uv use slightly different headers)\n for (const m of content.matchAll(/\\[\\[(?:package|distribution)\\]\\][^[]*?name\\s*=\\s*\"([^\"]+)\"[^[]*?version\\s*=\\s*\"([^\"]+)\"/gs)) {\n map.set(m[1].toLowerCase(), m[2]);\n }\n}\n\n// \u2500\u2500 TOML helpers (no library dependency) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Extract the content after a section header until the next header. */\nfunction extractSection(content: string, headerPattern: RegExp): string | null {\n const match = headerPattern.exec(content);\n if (!match) return null;\n const start = match.index + match[0].length;\n const remaining = content.slice(start);\n const nextSection = remaining.match(/^\\[/m);\n return nextSection ? remaining.slice(0, nextSection.index) : remaining;\n}\n\n/** Extract a TOML array value by key from a section. */\nfunction extractTomlArray(content: string, sectionPattern: RegExp, key: string): string[] {\n const section = extractSection(content, sectionPattern);\n if (!section) return [];\n\n // Match key = [\"...\", \"...\", ...] \u2014 may span multiple lines\n const keyPattern = new RegExp(`^${key}\\\\s*=\\\\s*\\\\[([^\\\\]]*(?:\\\\][^\\\\[\\\\]]*\\\\[)*[^\\\\]]*)\\\\]`, 'ms');\n const m = keyPattern.exec(section);\n if (!m) return [];\n return parseTomlStringArray(m[1]);\n}\n\n/** Parse a TOML string array body (contents inside [...]) into individual strings. */\nfunction parseTomlStringArray(body: string): string[] {\n const results: string[] = [];\n for (const m of body.matchAll(/\"([^\"]+)\"/g)) {\n results.push(m[1].trim());\n }\n for (const m of body.matchAll(/'([^']+)'/g)) {\n results.push(m[1].trim());\n }\n return results.filter(Boolean);\n}\n\n/** Normalize PyPI package name: lowercase, replace - and _ with - (canonical form). */\nfunction normalizePypiName(name: string): string {\n return name.toLowerCase().replace(/[-_.]+/g, '-');\n}\n\n/**\n * Extract the license from a pyproject.toml file.\n *\n * Handles:\n * - PEP 621 [project] license = {text = \"MIT\"} or license = \"MIT\"\n * - Poetry [tool.poetry] license = \"MIT\"\n */\nfunction extractPyprojectLicense(content: string): string | undefined {\n // PEP 621: license = {text = \"MIT\"} or license = {file = \"LICENSE\"}\n const pep621Section = extractSection(content, /^\\[project\\]/m);\n if (pep621Section) {\n // license = {text = \"MIT\"}\n const tableTextMatch = pep621Section.match(/^license\\s*=\\s*\\{[^}]*text\\s*=\\s*[\"']([^\"']+)[\"'][^}]*\\}/m);\n if (tableTextMatch && tableTextMatch[1].trim() !== '') {\n return tableTextMatch[1].trim();\n }\n // license = \"MIT\"\n const stringMatch = pep621Section.match(/^license\\s*=\\s*[\"']([^\"']+)[\"']/m);\n if (stringMatch && stringMatch[1].trim() !== '') {\n return stringMatch[1].trim();\n }\n }\n\n // Poetry: [tool.poetry] license = \"MIT\"\n const poetrySection = extractSection(content, /^\\[tool\\.poetry\\]/m);\n if (poetrySection) {\n const poetryLicenseMatch = poetrySection.match(/^license\\s*=\\s*[\"']([^\"']+)[\"']/m);\n if (poetryLicenseMatch && poetryLicenseMatch[1].trim() !== '') {\n return poetryLicenseMatch[1].trim();\n }\n }\n\n return undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -84,6 +84,18 @@ async function parseRubygemsManifest(manifestPath, projectRoot) {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
const directNames = new Set(dependencies.map((d) => d.name));
|
|
88
|
+
for (const [name, version] of resolvedVersions) {
|
|
89
|
+
if (directNames.has(name)) continue;
|
|
90
|
+
dependencies.push({
|
|
91
|
+
name,
|
|
92
|
+
declaredConstraint: version,
|
|
93
|
+
resolvedVersion: version,
|
|
94
|
+
scope: "production",
|
|
95
|
+
ecosystem: "rubygems",
|
|
96
|
+
sourceManifest: relativeManifest
|
|
97
|
+
});
|
|
98
|
+
}
|
|
87
99
|
return dependencies;
|
|
88
100
|
}
|
|
89
101
|
function parseGemLine(line, relativeManifest) {
|