llm-security-scanner 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. llm_scanner/__init__.py +1 -0
  2. llm_scanner/baselines/__init__.py +90 -0
  3. llm_scanner/cli.py +643 -0
  4. llm_scanner/judge/__init__.py +3 -0
  5. llm_scanner/judge/ollama_judge.py +200 -0
  6. llm_scanner/models.py +118 -0
  7. llm_scanner/payload_library/extended/llm10_extended.yaml +41 -0
  8. llm_scanner/payload_library/llm01_prompt_injection.yaml +44 -0
  9. llm_scanner/payload_library/llm02_sensitive_info_disclosure.yaml +39 -0
  10. llm_scanner/payload_library/llm03_supply_chain.yaml +37 -0
  11. llm_scanner/payload_library/llm04_data_model_poisoning.yaml +39 -0
  12. llm_scanner/payload_library/llm05_improper_output_handling.yaml +40 -0
  13. llm_scanner/payload_library/llm06_excessive_agency.yaml +38 -0
  14. llm_scanner/payload_library/llm07_system_prompt_leakage.yaml +39 -0
  15. llm_scanner/payload_library/llm08_vector_embedding_weaknesses.yaml +40 -0
  16. llm_scanner/payload_library/llm09_misinformation.yaml +41 -0
  17. llm_scanner/payload_library/llm10_unbounded_consumption.yaml +41 -0
  18. llm_scanner/payloads/__init__.py +1 -0
  19. llm_scanner/payloads/loader.py +74 -0
  20. llm_scanner/preflight.py +118 -0
  21. llm_scanner/reporters/__init__.py +38 -0
  22. llm_scanner/reporters/html.py +33 -0
  23. llm_scanner/reporters/json_reporter.py +15 -0
  24. llm_scanner/reporters/markdown.py +47 -0
  25. llm_scanner/reporters/sarif.py +106 -0
  26. llm_scanner/reporters/text.py +45 -0
  27. llm_scanner/reporters/trend.py +71 -0
  28. llm_scanner/scanner.py +121 -0
  29. llm_scanner/suppressions/__init__.py +104 -0
  30. llm_scanner/targets/__init__.py +44 -0
  31. llm_scanner/targets/base.py +29 -0
  32. llm_scanner/targets/http.py +53 -0
  33. llm_scanner/targets/ollama_target.py +46 -0
  34. llm_scanner/templates/index.html.j2 +92 -0
  35. llm_scanner/templates/report.html.j2 +57 -0
  36. llm_security_scanner-0.1.0.dist-info/METADATA +540 -0
  37. llm_security_scanner-0.1.0.dist-info/RECORD +40 -0
  38. llm_security_scanner-0.1.0.dist-info/WHEEL +4 -0
  39. llm_security_scanner-0.1.0.dist-info/entry_points.txt +2 -0
  40. llm_security_scanner-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ # Empty file — marks directory as a Python package
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from llm_scanner.models import AttackResult, ScanReport
7
+
8
+ __all__ = ["BaselineManager"]
9
+
10
+
11
+ class BaselineManager:
12
+ """Save, load, and diff named scan baselines.
13
+
14
+ Baselines are stored as JSON files under ``<output_dir>/baselines/<name>.json``.
15
+ The name must contain only letters, digits, hyphens, and underscores to prevent
16
+ path traversal attacks (T-05-10).
17
+ """
18
+
19
+ def __init__(self, output_dir: Path) -> None:
20
+ self._output_dir = output_dir
21
+
22
+ def _validate_name(self, name: str) -> None:
23
+ """Raise ValueError if *name* contains characters outside [a-zA-Z0-9_-]."""
24
+ if not re.match(r"^[a-zA-Z0-9_-]+$", name):
25
+ raise ValueError(
26
+ f"Invalid baseline name {name!r}. "
27
+ "Use only letters, digits, hyphens, and underscores."
28
+ )
29
+
30
+ def save(self, name: str) -> Path:
31
+ """Copy the most recent report.json under output_dir to baselines/{name}.json.
32
+
33
+ Raises:
34
+ ValueError: if *name* contains invalid characters.
35
+ FileNotFoundError: if no report.json exists under output_dir.
36
+ """
37
+ self._validate_name(name)
38
+
39
+ baselines_dir = self._output_dir / "baselines"
40
+
41
+ # Find all report.json files, excluding those already inside baselines/
42
+ candidates = [
43
+ p
44
+ for p in self._output_dir.rglob("report.json")
45
+ if not p.is_relative_to(baselines_dir)
46
+ ]
47
+
48
+ if not candidates:
49
+ raise FileNotFoundError(
50
+ f"No report.json found in {self._output_dir}"
51
+ )
52
+
53
+ # Pick the most recently modified report
54
+ most_recent = max(candidates, key=lambda p: p.stat().st_mtime)
55
+ content = most_recent.read_text(encoding="utf-8")
56
+
57
+ baselines_dir.mkdir(parents=True, exist_ok=True)
58
+ dest = baselines_dir / f"{name}.json"
59
+ dest.write_text(content, encoding="utf-8")
60
+ return dest
61
+
62
+ def load(self, name: str) -> ScanReport:
63
+ """Deserialize baseline *name* and return a ScanReport.
64
+
65
+ Raises:
66
+ ValueError: if *name* contains invalid characters.
67
+ FileNotFoundError: if the baseline file does not exist.
68
+ """
69
+ self._validate_name(name)
70
+ path = self._output_dir / "baselines" / f"{name}.json"
71
+ if not path.exists():
72
+ raise FileNotFoundError(f"Baseline '{name}' not found at {path}")
73
+ return ScanReport.model_validate_json(path.read_text(encoding="utf-8"))
74
+
75
+ @staticmethod
76
+ def diff_findings(
77
+ baseline: ScanReport,
78
+ current: ScanReport,
79
+ ) -> list[AttackResult]:
80
+ """Return findings that are new vulnerabilities compared to *baseline*.
81
+
82
+ A finding is "new" when it is ``success=True`` in *current* and its
83
+ ``attack_id`` was **not** ``success=True`` in *baseline*.
84
+ """
85
+ baseline_ids = {f.attack_id for f in baseline.findings if f.success}
86
+ return [
87
+ f
88
+ for f in current.findings
89
+ if f.success and f.attack_id not in baseline_ids
90
+ ]