@intentsolutionsio/penetration-tester 2.0.0 → 3.0.4
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/.claude-plugin/plugin.json +8 -3
- package/README.md +8 -0
- package/commands/pentest.md +5 -0
- package/package.json +8 -3
- package/skills/analyzing-tls-config/SKILL.md +221 -0
- package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
- package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
- package/skills/analyzing-tls-config/references/THEORY.md +128 -0
- package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
- package/skills/auditing-cors-policy/SKILL.md +186 -0
- package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
- package/skills/auditing-cors-policy/references/THEORY.md +142 -0
- package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
- package/skills/auditing-npm-dependencies/SKILL.md +254 -0
- package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
- package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
- package/skills/auditing-python-dependencies/SKILL.md +251 -0
- package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
- package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
- package/skills/checking-http-security-headers/SKILL.md +176 -0
- package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
- package/skills/checking-http-security-headers/references/THEORY.md +137 -0
- package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
- package/skills/checking-license-compliance/SKILL.md +225 -0
- package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
- package/skills/checking-license-compliance/references/THEORY.md +152 -0
- package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
- package/skills/composing-vulnerability-report/SKILL.md +212 -0
- package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
- package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
- package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
- package/skills/confirming-pentest-authorization/SKILL.md +247 -0
- package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
- package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
- package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
- package/skills/defining-pentest-scope/SKILL.md +227 -0
- package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
- package/skills/defining-pentest-scope/references/THEORY.md +170 -0
- package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
- package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
- package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
- package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
- package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
- package/skills/detecting-debug-endpoints/SKILL.md +207 -0
- package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
- package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
- package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
- package/skills/detecting-directory-listing/SKILL.md +206 -0
- package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
- package/skills/detecting-directory-listing/references/THEORY.md +203 -0
- package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
- package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
- package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
- package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
- package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
- package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
- package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
- package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
- package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
- package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
- package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
- package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
- package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
- package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
- package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
- package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
- package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
- package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
- package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
- package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
- package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
- package/skills/detecting-weak-cryptography/SKILL.md +147 -0
- package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
- package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
- package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
- package/skills/fingerprinting-server-software/SKILL.md +191 -0
- package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
- package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
- package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
- package/skills/generating-executive-summary/SKILL.md +261 -0
- package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
- package/skills/generating-executive-summary/references/THEORY.md +195 -0
- package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
- package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
- package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
- package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
- package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
- package/skills/performing-penetration-testing/SKILL.md +282 -190
- package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
- package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
- package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
- package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
- package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
- package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
- package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
- package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
- package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
- package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
- package/skills/recording-pentest-engagement/SKILL.md +253 -0
- package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
- package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
- package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
- package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
- package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
- package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
- package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
- package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
- package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
- package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
- package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
|
@@ -70,6 +70,7 @@ def log_error(message: str) -> None:
|
|
|
70
70
|
# Project detection
|
|
71
71
|
# ---------------------------------------------------------------------------
|
|
72
72
|
|
|
73
|
+
|
|
73
74
|
def detect_project_type(directory: Path) -> list[str]:
|
|
74
75
|
"""Auto-detect project ecosystems by scanning for manifest files.
|
|
75
76
|
|
|
@@ -99,8 +100,7 @@ def detect_project_type(directory: Path) -> list[str]:
|
|
|
99
100
|
for ecosystem, manifest in FUTURE_SCANNERS.items():
|
|
100
101
|
if (directory / manifest).exists():
|
|
101
102
|
log_warn(
|
|
102
|
-
f"Detected {ecosystem} project ({manifest}), but {ecosystem} "
|
|
103
|
-
f"scanning is not yet implemented. Skipping."
|
|
103
|
+
f"Detected {ecosystem} project ({manifest}), but {ecosystem} scanning is not yet implemented. Skipping."
|
|
104
104
|
)
|
|
105
105
|
|
|
106
106
|
return detected
|
|
@@ -110,6 +110,7 @@ def detect_project_type(directory: Path) -> list[str]:
|
|
|
110
110
|
# npm audit
|
|
111
111
|
# ---------------------------------------------------------------------------
|
|
112
112
|
|
|
113
|
+
|
|
113
114
|
def run_npm_audit(directory: Path) -> list[dict[str, Any]]:
|
|
114
115
|
"""Run npm audit and parse vulnerability findings.
|
|
115
116
|
|
|
@@ -128,7 +129,9 @@ def run_npm_audit(directory: Path) -> list[dict[str, Any]]:
|
|
|
128
129
|
# Check npm is available
|
|
129
130
|
npm_check = subprocess.run(
|
|
130
131
|
["npm", "--version"],
|
|
131
|
-
capture_output=True,
|
|
132
|
+
capture_output=True,
|
|
133
|
+
text=True,
|
|
134
|
+
timeout=SUBPROCESS_TIMEOUT,
|
|
132
135
|
)
|
|
133
136
|
if npm_check.returncode != 0:
|
|
134
137
|
log_warn("npm is not installed or not in PATH. Skipping npm audit.")
|
|
@@ -141,15 +144,13 @@ def run_npm_audit(directory: Path) -> list[dict[str, Any]]:
|
|
|
141
144
|
|
|
142
145
|
# Warn if node_modules is missing
|
|
143
146
|
if not (directory / "node_modules").is_dir():
|
|
144
|
-
log_warn(
|
|
145
|
-
f"No node_modules directory in {directory}. "
|
|
146
|
-
f"Run 'npm install' first for accurate audit results."
|
|
147
|
-
)
|
|
147
|
+
log_warn(f"No node_modules directory in {directory}. Run 'npm install' first for accurate audit results.")
|
|
148
148
|
|
|
149
149
|
try:
|
|
150
150
|
result = subprocess.run(
|
|
151
151
|
["npm", "audit", "--json"],
|
|
152
|
-
capture_output=True,
|
|
152
|
+
capture_output=True,
|
|
153
|
+
text=True,
|
|
153
154
|
cwd=str(directory),
|
|
154
155
|
timeout=SUBPROCESS_TIMEOUT,
|
|
155
156
|
)
|
|
@@ -198,43 +199,49 @@ def _parse_npm_audit_v2(data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
|
198
199
|
# Each vulnerability entry may reference multiple advisories via "via"
|
|
199
200
|
via_entries = vuln_info.get("via", [])
|
|
200
201
|
if not via_entries:
|
|
201
|
-
findings.append(
|
|
202
|
-
|
|
203
|
-
"package": pkg_name,
|
|
204
|
-
"severity": _normalize_severity(severity),
|
|
205
|
-
"title": f"Vulnerability in {pkg_name}",
|
|
206
|
-
"detail": f"Severity: {severity}. Check npm audit for details.",
|
|
207
|
-
"cve": None,
|
|
208
|
-
"installed_version": vuln_info.get("range"),
|
|
209
|
-
"fixed_version": fixed_version,
|
|
210
|
-
})
|
|
211
|
-
continue
|
|
212
|
-
|
|
213
|
-
for via in via_entries:
|
|
214
|
-
if isinstance(via, str):
|
|
215
|
-
# Indirect vulnerability reference (transitive dependency name)
|
|
216
|
-
findings.append({
|
|
202
|
+
findings.append(
|
|
203
|
+
{
|
|
217
204
|
"scanner": "npm",
|
|
218
205
|
"package": pkg_name,
|
|
219
206
|
"severity": _normalize_severity(severity),
|
|
220
|
-
"title": f"
|
|
221
|
-
"detail": f"{
|
|
207
|
+
"title": f"Vulnerability in {pkg_name}",
|
|
208
|
+
"detail": f"Severity: {severity}. Check npm audit for details.",
|
|
222
209
|
"cve": None,
|
|
223
210
|
"installed_version": vuln_info.get("range"),
|
|
224
211
|
"fixed_version": fixed_version,
|
|
225
|
-
}
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
for via in via_entries:
|
|
217
|
+
if isinstance(via, str):
|
|
218
|
+
# Indirect vulnerability reference (transitive dependency name)
|
|
219
|
+
findings.append(
|
|
220
|
+
{
|
|
221
|
+
"scanner": "npm",
|
|
222
|
+
"package": pkg_name,
|
|
223
|
+
"severity": _normalize_severity(severity),
|
|
224
|
+
"title": f"Depends on vulnerable {via}",
|
|
225
|
+
"detail": f"{pkg_name} is affected through dependency on {via}.",
|
|
226
|
+
"cve": None,
|
|
227
|
+
"installed_version": vuln_info.get("range"),
|
|
228
|
+
"fixed_version": fixed_version,
|
|
229
|
+
}
|
|
230
|
+
)
|
|
226
231
|
elif isinstance(via, dict):
|
|
227
232
|
cve = _extract_cve(via.get("url", ""))
|
|
228
|
-
findings.append(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
233
|
+
findings.append(
|
|
234
|
+
{
|
|
235
|
+
"scanner": "npm",
|
|
236
|
+
"package": pkg_name,
|
|
237
|
+
"severity": _normalize_severity(via.get("severity", severity)),
|
|
238
|
+
"title": via.get("title", f"Vulnerability in {pkg_name}"),
|
|
239
|
+
"detail": via.get("title", "No description available."),
|
|
240
|
+
"cve": cve,
|
|
241
|
+
"installed_version": via.get("range") or vuln_info.get("range"),
|
|
242
|
+
"fixed_version": fixed_version,
|
|
243
|
+
}
|
|
244
|
+
)
|
|
238
245
|
|
|
239
246
|
return findings
|
|
240
247
|
|
|
@@ -250,16 +257,18 @@ def _parse_npm_audit_v1(data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
|
250
257
|
for _adv_id, advisory in advisories.items():
|
|
251
258
|
cve_list = advisory.get("cves", [])
|
|
252
259
|
cve = cve_list[0] if cve_list else None
|
|
253
|
-
findings.append(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
findings.append(
|
|
261
|
+
{
|
|
262
|
+
"scanner": "npm",
|
|
263
|
+
"package": advisory.get("module_name", "unknown"),
|
|
264
|
+
"severity": _normalize_severity(advisory.get("severity", "moderate")),
|
|
265
|
+
"title": advisory.get("title", "Unknown vulnerability"),
|
|
266
|
+
"detail": advisory.get("overview", "No description available."),
|
|
267
|
+
"cve": cve,
|
|
268
|
+
"installed_version": advisory.get("findings", [{}])[0].get("version"),
|
|
269
|
+
"fixed_version": advisory.get("patched_versions"),
|
|
270
|
+
}
|
|
271
|
+
)
|
|
263
272
|
|
|
264
273
|
return findings
|
|
265
274
|
|
|
@@ -268,6 +277,7 @@ def _parse_npm_audit_v1(data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
|
268
277
|
# pip-audit
|
|
269
278
|
# ---------------------------------------------------------------------------
|
|
270
279
|
|
|
280
|
+
|
|
271
281
|
def run_pip_audit(directory: Path) -> list[dict[str, Any]]:
|
|
272
282
|
"""Run pip-audit and parse vulnerability findings.
|
|
273
283
|
|
|
@@ -291,7 +301,8 @@ def run_pip_audit(directory: Path) -> list[dict[str, Any]]:
|
|
|
291
301
|
try:
|
|
292
302
|
result = subprocess.run(
|
|
293
303
|
pip_audit_cmd,
|
|
294
|
-
capture_output=True,
|
|
304
|
+
capture_output=True,
|
|
305
|
+
text=True,
|
|
295
306
|
cwd=str(directory),
|
|
296
307
|
timeout=SUBPROCESS_TIMEOUT,
|
|
297
308
|
)
|
|
@@ -305,14 +316,16 @@ def run_pip_audit(directory: Path) -> list[dict[str, Any]]:
|
|
|
305
316
|
# Try installing pip-audit
|
|
306
317
|
install_result = subprocess.run(
|
|
307
318
|
[sys.executable, "-m", "pip", "install", "pip-audit", "--quiet"],
|
|
308
|
-
capture_output=True,
|
|
319
|
+
capture_output=True,
|
|
320
|
+
text=True,
|
|
309
321
|
timeout=SUBPROCESS_TIMEOUT,
|
|
310
322
|
)
|
|
311
323
|
if install_result.returncode == 0:
|
|
312
324
|
try:
|
|
313
325
|
result = subprocess.run(
|
|
314
326
|
pip_audit_cmd,
|
|
315
|
-
capture_output=True,
|
|
327
|
+
capture_output=True,
|
|
328
|
+
text=True,
|
|
316
329
|
cwd=str(directory),
|
|
317
330
|
timeout=SUBPROCESS_TIMEOUT,
|
|
318
331
|
)
|
|
@@ -370,16 +383,18 @@ def _parse_pip_audit_output(raw_output: str) -> list[dict[str, Any]]:
|
|
|
370
383
|
if isinstance(fix, list):
|
|
371
384
|
fix = fix[0] if fix else None
|
|
372
385
|
|
|
373
|
-
findings.append(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
386
|
+
findings.append(
|
|
387
|
+
{
|
|
388
|
+
"scanner": "pip-audit",
|
|
389
|
+
"package": pkg_name,
|
|
390
|
+
"severity": _severity_from_vuln_id(vuln_id),
|
|
391
|
+
"title": f"{vuln_id}: {pkg_name}" if vuln_id else f"Vulnerability in {pkg_name}",
|
|
392
|
+
"detail": description or "No description provided by pip-audit.",
|
|
393
|
+
"cve": cve,
|
|
394
|
+
"installed_version": installed,
|
|
395
|
+
"fixed_version": fix,
|
|
396
|
+
}
|
|
397
|
+
)
|
|
383
398
|
|
|
384
399
|
return findings
|
|
385
400
|
|
|
@@ -390,7 +405,8 @@ def _run_pip_outdated_fallback() -> list[dict[str, Any]]:
|
|
|
390
405
|
try:
|
|
391
406
|
result = subprocess.run(
|
|
392
407
|
[sys.executable, "-m", "pip", "list", "--outdated", "--format=json"],
|
|
393
|
-
capture_output=True,
|
|
408
|
+
capture_output=True,
|
|
409
|
+
text=True,
|
|
394
410
|
timeout=SUBPROCESS_TIMEOUT,
|
|
395
411
|
)
|
|
396
412
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
@@ -406,20 +422,22 @@ def _run_pip_outdated_fallback() -> list[dict[str, Any]]:
|
|
|
406
422
|
return findings
|
|
407
423
|
|
|
408
424
|
for entry in data:
|
|
409
|
-
findings.append(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
425
|
+
findings.append(
|
|
426
|
+
{
|
|
427
|
+
"scanner": "pip-audit",
|
|
428
|
+
"package": entry.get("name", "unknown"),
|
|
429
|
+
"severity": "info",
|
|
430
|
+
"title": f"Outdated package: {entry.get('name', 'unknown')}",
|
|
431
|
+
"detail": (
|
|
432
|
+
f"Installed {entry.get('version', '?')}, "
|
|
433
|
+
f"latest {entry.get('latest_version', '?')}. "
|
|
434
|
+
f"Outdated packages may contain known vulnerabilities."
|
|
435
|
+
),
|
|
436
|
+
"cve": None,
|
|
437
|
+
"installed_version": entry.get("version"),
|
|
438
|
+
"fixed_version": entry.get("latest_version"),
|
|
439
|
+
}
|
|
440
|
+
)
|
|
423
441
|
|
|
424
442
|
return findings
|
|
425
443
|
|
|
@@ -428,6 +446,7 @@ def _run_pip_outdated_fallback() -> list[dict[str, Any]]:
|
|
|
428
446
|
# pip check
|
|
429
447
|
# ---------------------------------------------------------------------------
|
|
430
448
|
|
|
449
|
+
|
|
431
450
|
def run_pip_check(directory: Path) -> list[dict[str, Any]]:
|
|
432
451
|
"""Run pip check to detect broken dependencies and version conflicts.
|
|
433
452
|
|
|
@@ -446,7 +465,8 @@ def run_pip_check(directory: Path) -> list[dict[str, Any]]:
|
|
|
446
465
|
try:
|
|
447
466
|
result = subprocess.run(
|
|
448
467
|
[sys.executable, "-m", "pip", "check"],
|
|
449
|
-
capture_output=True,
|
|
468
|
+
capture_output=True,
|
|
469
|
+
text=True,
|
|
450
470
|
cwd=str(directory),
|
|
451
471
|
timeout=SUBPROCESS_TIMEOUT,
|
|
452
472
|
)
|
|
@@ -470,16 +490,18 @@ def run_pip_check(directory: Path) -> list[dict[str, Any]]:
|
|
|
470
490
|
pkg_name = parts[0] if parts else "unknown"
|
|
471
491
|
installed_version = parts[1] if len(parts) > 1 else None
|
|
472
492
|
|
|
473
|
-
findings.append(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
493
|
+
findings.append(
|
|
494
|
+
{
|
|
495
|
+
"scanner": "pip-check",
|
|
496
|
+
"package": pkg_name,
|
|
497
|
+
"severity": "moderate",
|
|
498
|
+
"title": f"Dependency conflict: {pkg_name}",
|
|
499
|
+
"detail": line,
|
|
500
|
+
"cve": None,
|
|
501
|
+
"installed_version": installed_version,
|
|
502
|
+
"fixed_version": None,
|
|
503
|
+
}
|
|
504
|
+
)
|
|
483
505
|
|
|
484
506
|
return findings
|
|
485
507
|
|
|
@@ -488,6 +510,7 @@ def run_pip_check(directory: Path) -> list[dict[str, Any]]:
|
|
|
488
510
|
# Unification and deduplication
|
|
489
511
|
# ---------------------------------------------------------------------------
|
|
490
512
|
|
|
513
|
+
|
|
491
514
|
def unify_results(findings: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
492
515
|
"""Normalize, deduplicate, and sort findings by severity.
|
|
493
516
|
|
|
@@ -528,6 +551,7 @@ def unify_results(findings: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
528
551
|
# Reporting
|
|
529
552
|
# ---------------------------------------------------------------------------
|
|
530
553
|
|
|
554
|
+
|
|
531
555
|
def generate_report(
|
|
532
556
|
directory: Path,
|
|
533
557
|
findings: list[dict[str, Any]],
|
|
@@ -618,6 +642,7 @@ def generate_report(
|
|
|
618
642
|
# Helpers
|
|
619
643
|
# ---------------------------------------------------------------------------
|
|
620
644
|
|
|
645
|
+
|
|
621
646
|
def _normalize_severity(severity: str) -> str:
|
|
622
647
|
"""Normalize a severity string to one of the canonical levels."""
|
|
623
648
|
s = severity.strip().lower()
|
|
@@ -637,6 +662,7 @@ def _normalize_severity(severity: str) -> str:
|
|
|
637
662
|
def _extract_cve(text: str) -> Optional[str]:
|
|
638
663
|
"""Extract a CVE identifier from a string, if present."""
|
|
639
664
|
import re
|
|
665
|
+
|
|
640
666
|
match = re.search(r"CVE-\d{4}-\d{4,}", text)
|
|
641
667
|
return match.group(0) if match else None
|
|
642
668
|
|
|
@@ -668,6 +694,7 @@ def _filter_by_severity(
|
|
|
668
694
|
# CLI
|
|
669
695
|
# ---------------------------------------------------------------------------
|
|
670
696
|
|
|
697
|
+
|
|
671
698
|
def main() -> None:
|
|
672
699
|
"""Entry point for the dependency auditor CLI."""
|
|
673
700
|
parser = argparse.ArgumentParser(
|
|
@@ -726,8 +753,7 @@ def main() -> None:
|
|
|
726
753
|
|
|
727
754
|
if not ecosystems:
|
|
728
755
|
log_warn(
|
|
729
|
-
f"No supported project types detected in {directory}. "
|
|
730
|
-
f"Supported: {', '.join(sorted(SUPPORTED_SCANNERS))}"
|
|
756
|
+
f"No supported project types detected in {directory}. Supported: {', '.join(sorted(SUPPORTED_SCANNERS))}"
|
|
731
757
|
)
|
|
732
758
|
sys.exit(0)
|
|
733
759
|
|
|
@@ -758,8 +784,7 @@ def main() -> None:
|
|
|
758
784
|
filtered = _filter_by_severity(unified, args.min_severity)
|
|
759
785
|
|
|
760
786
|
log(
|
|
761
|
-
f"Unified: {len(unified)} total, {len(filtered)} at or above "
|
|
762
|
-
f"{args.min_severity} severity.",
|
|
787
|
+
f"Unified: {len(unified)} total, {len(filtered)} at or above {args.min_severity} severity.",
|
|
763
788
|
verbose,
|
|
764
789
|
)
|
|
765
790
|
|
|
@@ -767,9 +792,7 @@ def main() -> None:
|
|
|
767
792
|
generate_report(directory, filtered, args.output)
|
|
768
793
|
|
|
769
794
|
# Exit code: 1 if any critical or high findings remain after filtering
|
|
770
|
-
has_critical_or_high = any(
|
|
771
|
-
f["severity"] in ("critical", "high") for f in filtered
|
|
772
|
-
)
|
|
795
|
+
has_critical_or_high = any(f["severity"] in ("critical", "high") for f in filtered)
|
|
773
796
|
sys.exit(1 if has_critical_or_high else 0)
|
|
774
797
|
|
|
775
798
|
|