devsecops-radar 0.3.3__tar.gz → 0.3.5__tar.gz

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 (60) hide show
  1. {devsecops_radar-0.3.3/devsecops_radar.egg-info → devsecops_radar-0.3.5}/PKG-INFO +1 -1
  2. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/cli/scanner.py +3 -5
  3. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/analyzer.py +2 -4
  4. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/models.py +2 -8
  5. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/parser.py +2 -4
  6. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/reporting.py +0 -6
  7. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/rule_fusion.py +1 -23
  8. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/sbom.py +1 -2
  9. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/app.py +1 -1
  10. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/sentry/routes.py +0 -2
  11. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5/devsecops_radar.egg-info}/PKG-INFO +1 -1
  12. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/pyproject.toml +1 -1
  13. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/tests/test_analyzer.py +1 -3
  14. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/tests/test_api.py +0 -1
  15. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/tests/test_scanners.py +0 -1
  16. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/LICENSE +0 -0
  17. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/MANIFEST.in +0 -0
  18. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/README.md +0 -0
  19. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/__init__.py +0 -0
  20. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/cli/__init__.py +0 -0
  21. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/__init__.py +0 -0
  22. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/attack_simulation.py +0 -0
  23. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/auth.py +0 -0
  24. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/database.py +0 -0
  25. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/rag.py +0 -0
  26. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/remediation.py +0 -0
  27. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/settings.py +0 -0
  28. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/core/valuation.py +0 -0
  29. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/plugins/__init__.py +0 -0
  30. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/adapter.py +0 -0
  31. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/base.py +0 -0
  32. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/gitleaks.py +0 -0
  33. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/poutine.py +0 -0
  34. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/semgrep.py +0 -0
  35. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/trivy.py +0 -0
  36. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/scanners/zizmor.py +0 -0
  37. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/__init__.py +0 -0
  38. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/attack_paths/__init__.py +0 -0
  39. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/attack_paths/routes.py +0 -0
  40. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/dashboard/__init__.py +0 -0
  41. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/dashboard/routes.py +0 -0
  42. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/static/css/bootstrap.min.css +0 -0
  43. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/static/css/style.css +0 -0
  44. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/static/js/bootstrap.bundle.min.js +0 -0
  45. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/static/js/chart.umd.min.js +0 -0
  46. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/static/js/dashboard.js +0 -0
  47. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/summary/__init__.py +0 -0
  48. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/summary/routes.py +0 -0
  49. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/templates/index.html +0 -0
  50. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/topology/__init__.py +0 -0
  51. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar/web/topology/routes.py +0 -0
  52. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar.egg-info/SOURCES.txt +0 -0
  53. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar.egg-info/dependency_links.txt +0 -0
  54. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar.egg-info/entry_points.txt +0 -0
  55. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar.egg-info/requires.txt +0 -0
  56. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/devsecops_radar.egg-info/top_level.txt +0 -0
  57. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/setup.cfg +0 -0
  58. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/tests/test_cli.py +0 -0
  59. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/tests/test_database.py +0 -0
  60. {devsecops_radar-0.3.3 → devsecops_radar-0.3.5}/tests/test_rule_fusion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devsecops-radar
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: Unified CI/CD Security Dashboard — Pipeline Sentinel
5
5
  Author-email: Mehrdoost <70381337+Mehrdoost@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -123,17 +123,16 @@ def wizard():
123
123
  print("🛡️ Welcome to Pipeline Sentinel – Quick Setup Wizard")
124
124
  print("This will install necessary components.\n")
125
125
  import subprocess
126
- # 1. Ollama check
127
126
  try:
128
127
  subprocess.run(['ollama', '--version'], capture_output=True, check=True)
129
128
  print("[✔] Ollama found.")
130
- except:
129
+ except FileNotFoundError:
131
130
  print("[!] Ollama not found. Installing...")
132
131
  subprocess.run('curl -fsSL https://ollama.com/install.sh | sh', shell=True)
133
- # 2. Pull AI model
132
+ except Exception:
133
+ print("[!] Could not verify Ollama. Please install manually.")
134
134
  print("📥 Pulling AI model (llama3.2)...")
135
135
  subprocess.run(['ollama', 'pull', 'llama3.2:latest'])
136
- # 3. Suggestions for optional tools
137
136
  if subprocess.run(['which', 'semgrep'], capture_output=True).returncode == 0:
138
137
  print("[✔] Semgrep available.")
139
138
  else:
@@ -142,7 +141,6 @@ def wizard():
142
141
  print("[✔] Docker available.")
143
142
  else:
144
143
  print("[ ] Docker not found (optional).")
145
- # 4. Final instructions
146
144
  print("\n✅ Setup complete! You can now run:")
147
145
  print(" devsecops-radar --trivy sample_trivy.json --semgrep sample_semgrep.json")
148
146
  print(" devsecops-radar-web")
@@ -4,9 +4,8 @@ import re
4
4
  import requests
5
5
  from requests.adapters import HTTPAdapter
6
6
  from urllib3.util.retry import Retry
7
- from typing import List, Dict, Any, Optional
7
+ from typing import List, Dict, Any
8
8
 
9
- # --- Retry logic for LLM calls ---
10
9
  def _session_with_retries(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504]):
11
10
  session = requests.Session()
12
11
  retries = Retry(
@@ -20,7 +19,6 @@ def _session_with_retries(total=3, backoff_factor=0.5, status_forcelist=[429, 50
20
19
  session.mount('https://', adapter)
21
20
  return session
22
21
 
23
- # Default maximum findings sent to LLM (configurable via env)
24
22
  MAX_ANALYZER_FINDINGS = int(os.environ.get("ANALYZER_MAX_FINDINGS", "100"))
25
23
 
26
24
  FEW_SHOT_EXAMPLE = {
@@ -60,7 +58,7 @@ def extract_json(text: str) -> Dict[str, Any]:
60
58
  if match:
61
59
  try:
62
60
  return json.loads(match.group(0))
63
- except:
61
+ except json.JSONDecodeError:
64
62
  pass
65
63
  return {"executive_summary": text, "attack_paths": [], "top_remediations": []}
66
64
 
@@ -1,7 +1,7 @@
1
1
  from sqlalchemy import create_engine, Column, Integer, String, DateTime, JSON, ForeignKey
2
2
  from sqlalchemy.orm import declarative_base, sessionmaker, relationship
3
- from pydantic import BaseModel, Field, validator
4
- from typing import List, Optional
3
+ from pydantic import BaseModel, validator
4
+ from typing import Optional
5
5
  import datetime
6
6
  import os
7
7
 
@@ -20,11 +20,6 @@ class FindingSchema(BaseModel):
20
20
  def severity_upper(cls, v):
21
21
  return v.upper()
22
22
 
23
- class ScanMetadata(BaseModel):
24
- findings: List[FindingSchema]
25
- scan_id: Optional[int] = None
26
- timestamp: Optional[str] = None
27
-
28
23
  class Scan(Base):
29
24
  __tablename__ = 'scans'
30
25
  id = Column(Integer, primary_key=True)
@@ -52,7 +47,6 @@ def init_db():
52
47
  Base.metadata.create_all(engine)
53
48
 
54
49
  def save_scan_to_db(findings: list):
55
- # Validate with Pydantic before storing
56
50
  validated = [FindingSchema(**f) for f in findings]
57
51
  init_db()
58
52
  session = SessionLocal()
@@ -1,4 +1,6 @@
1
+ import json
1
2
  import warnings
3
+ from typing import List, Dict, Any
2
4
 
3
5
  warnings.warn(
4
6
  "devsecops_radar.core.parser is deprecated and will be removed in v0.3.0. "
@@ -7,10 +9,6 @@ warnings.warn(
7
9
  stacklevel=2,
8
10
  )
9
11
 
10
- import json
11
- from typing import List, Dict, Any
12
-
13
-
14
12
  def parse_trivy_json(file_path: str) -> List[Dict[str, Any]]:
15
13
  with open(file_path) as f:
16
14
  data = json.load(f)
@@ -18,12 +18,6 @@ def redact_sensitive(text: str, patterns: List[str] = None) -> str:
18
18
  return text
19
19
 
20
20
  def generate_pdf_report(findings: List[Dict[str, Any]], ai_summary: Dict[str, Any], output_file: str = "report.pdf", redact: bool = True):
21
- try:
22
- from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
23
- except ImportError:
24
- print("[ERROR] reportlab not installed.")
25
- return
26
-
27
21
  doc = SimpleDocTemplate(output_file, pagesize=A4)
28
22
  elements = []
29
23
  styles = getSampleStyleSheet()
@@ -2,7 +2,7 @@ import json
2
2
  import os
3
3
  import subprocess
4
4
  from pathlib import Path
5
- from typing import List, Dict, Any, Optional, Tuple
5
+ from typing import List, Dict, Any, Tuple
6
6
 
7
7
 
8
8
  class RuleFusion:
@@ -26,10 +26,7 @@ class RuleFusion:
26
26
  )
27
27
  self.findings: List[Dict[str, Any]] = []
28
28
 
29
- # ── public API ──────────────────────────────────────────────
30
-
31
29
  def load_all_rules(self) -> List[Dict[str, Any]]:
32
- """Load rules from both local and community sources."""
33
30
  if self.local_rules_path and self.local_rules_path.exists():
34
31
  self._load_from_directory(self.local_rules_path)
35
32
 
@@ -40,7 +37,6 @@ class RuleFusion:
40
37
  return self.findings
41
38
 
42
39
  def update_community_rules(self) -> None:
43
- """Clone or pull the latest community rules repository."""
44
40
  target_dir = Path.home() / ".devsecops-radar" / "community-rules"
45
41
  target_dir.parent.mkdir(parents=True, exist_ok=True)
46
42
 
@@ -63,7 +59,6 @@ class RuleFusion:
63
59
  )
64
60
 
65
61
  def generate_template(self, scanner_name: str) -> str:
66
- """Generate a sample rule file for the user to start with."""
67
62
  template = {
68
63
  "findings": [
69
64
  {
@@ -82,18 +77,10 @@ class RuleFusion:
82
77
  }
83
78
  return json.dumps(template, indent=2)
84
79
 
85
- # ── policy engine ─────────────────────────────────────────
86
-
87
80
  @staticmethod
88
81
  def evaluate_policy(
89
82
  findings: List[Dict[str, Any]], policy_file: str
90
83
  ) -> Tuple[bool, str]:
91
- """
92
- Evaluate a policy file against the findings.
93
- Returns (pass, message).
94
- The policy file is a JSON object with conditions.
95
- Example: {"max_critical": 5, "on_violation": "fail"}
96
- """
97
84
  if not os.path.exists(policy_file):
98
85
  return True, (
99
86
  f"Policy file '{policy_file}' not found. "
@@ -120,10 +107,7 @@ class RuleFusion:
120
107
 
121
108
  return True, "Policy checks passed."
122
109
 
123
- # ── internal helpers ────────────────────────────────────────
124
-
125
110
  def _load_from_directory(self, directory: Path) -> None:
126
- """Recursively load all JSON files from a directory."""
127
111
  for json_file in sorted(directory.rglob("*.json")):
128
112
  try:
129
113
  with open(json_file, "r", encoding="utf-8") as f:
@@ -143,7 +127,6 @@ class RuleFusion:
143
127
  print(f"📄 Loaded {len(parsed)} findings from {json_file.name}")
144
128
 
145
129
  def _validate_json(self, data: Any, filename: str) -> bool:
146
- """Structural validation with better list handling."""
147
130
  if isinstance(data, list):
148
131
  if len(data) == 0:
149
132
  print(f"[WARNING] {filename}: empty list, skipping")
@@ -171,10 +154,8 @@ class RuleFusion:
171
154
  def _parse_scanner_output(
172
155
  self, data: Any, filename: str
173
156
  ) -> List[Dict[str, Any]]:
174
- """Parse any known scanner format."""
175
157
  findings: List[Dict[str, Any]] = []
176
158
 
177
- # Already a plain list of findings
178
159
  if isinstance(data, list):
179
160
  for item in data:
180
161
  if isinstance(item, dict) and self._is_finding(item):
@@ -184,7 +165,6 @@ class RuleFusion:
184
165
  if not isinstance(data, dict):
185
166
  return findings
186
167
 
187
- # Trivy format
188
168
  for result in data.get("Results", []):
189
169
  for vuln in result.get("Vulnerabilities", []):
190
170
  findings.append(
@@ -203,7 +183,6 @@ class RuleFusion:
203
183
  }
204
184
  )
205
185
 
206
- # Semgrep format
207
186
  for result in data.get("results", []):
208
187
  findings.append(
209
188
  {
@@ -222,7 +201,6 @@ class RuleFusion:
222
201
  }
223
202
  )
224
203
 
225
- # Poutine / Zizmor / Generic format
226
204
  for item in data.get("findings", []):
227
205
  if isinstance(item, dict):
228
206
  findings.append(self._normalize(item, filename))
@@ -1,7 +1,6 @@
1
1
  import subprocess
2
2
  import json
3
- import os
4
- from typing import List, Dict, Any, Optional
3
+ from typing import List, Dict, Optional
5
4
 
6
5
  def generate_sbom(target_dir: str, output_file: str = "sbom.json") -> Optional[Dict]:
7
6
  try:
@@ -4,7 +4,7 @@ from devsecops_radar.web.attack_paths.routes import attack_paths_bp
4
4
  from devsecops_radar.web.topology.routes import topology_bp
5
5
  from devsecops_radar.web.summary.routes import summary_bp
6
6
  from devsecops_radar.web.sentry.routes import sentry_bp
7
- from devsecops_radar.core.auth import login_required, create_token
7
+ from devsecops_radar.core.auth import create_token
8
8
  from devsecops_radar.core.settings import settings
9
9
 
10
10
  def create_app():
@@ -1,6 +1,4 @@
1
1
  from flask import Blueprint, request, jsonify
2
- import json
3
- import os
4
2
 
5
3
  sentry_bp = Blueprint('sentry', __name__)
6
4
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devsecops-radar
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: Unified CI/CD Security Dashboard — Pipeline Sentinel
5
5
  Author-email: Mehrdoost <70381337+Mehrdoost@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devsecops-radar"
7
- version = "0.3.3"
7
+ version = "0.3.5"
8
8
  description = "Unified CI/CD Security Dashboard — Pipeline Sentinel"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,4 +1,3 @@
1
- import pytest
2
1
  from unittest.mock import patch, MagicMock
3
2
  from devsecops_radar.core.analyzer import OllamaAnalyzer, extract_json, select_findings_for_llm
4
3
 
@@ -9,13 +8,12 @@ def test_extract_json_plain():
9
8
 
10
9
  def test_extract_json_malformed():
11
10
  result = extract_json("some text {invalid")
12
- assert "executive_summary" in result # falls back to wrapping the text
11
+ assert "executive_summary" in result
13
12
 
14
13
  def test_select_findings_for_llm():
15
14
  findings = [{"severity": "CRITICAL"}] * 120 + [{"severity": "LOW"}] * 50
16
15
  selected = select_findings_for_llm(findings, max_items=100)
17
16
  assert len(selected) == 100
18
- # All criticals should be included
19
17
  criticals = [f for f in selected if f["severity"] == "CRITICAL"]
20
18
  assert len(criticals) == 100
21
19
 
@@ -1,6 +1,5 @@
1
1
  import pytest
2
2
  from devsecops_radar.web.app import create_app
3
- import os
4
3
 
5
4
  @pytest.fixture
6
5
  def app():
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import tempfile
3
3
  import os
4
- import pytest
5
4
  from devsecops_radar.scanners.trivy import TrivyScanner
6
5
  from devsecops_radar.scanners.semgrep import SemgrepScanner
7
6
  from devsecops_radar.scanners.poutine import PoutineScanner
File without changes