devsecops-radar 0.3.2__tar.gz → 0.3.3__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.
- {devsecops_radar-0.3.2/devsecops_radar.egg-info → devsecops_radar-0.3.3}/PKG-INFO +1 -1
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/dashboard/routes.py +1 -1
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3/devsecops_radar.egg-info}/PKG-INFO +1 -1
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar.egg-info/SOURCES.txt +3 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/pyproject.toml +1 -1
- devsecops_radar-0.3.3/tests/test_analyzer.py +39 -0
- devsecops_radar-0.3.3/tests/test_api.py +29 -0
- devsecops_radar-0.3.3/tests/test_cli.py +30 -0
- devsecops_radar-0.3.3/tests/test_database.py +43 -0
- devsecops_radar-0.3.3/tests/test_rule_fusion.py +30 -0
- devsecops_radar-0.3.3/tests/test_scanners.py +81 -0
- devsecops_radar-0.3.2/tests/test_api.py +0 -16
- devsecops_radar-0.3.2/tests/test_cli.py +0 -0
- devsecops_radar-0.3.2/tests/test_scanners.py +0 -26
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/LICENSE +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/MANIFEST.in +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/README.md +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/cli/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/cli/scanner.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/analyzer.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/attack_simulation.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/auth.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/database.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/models.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/parser.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/rag.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/remediation.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/reporting.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/rule_fusion.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/sbom.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/settings.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/core/valuation.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/plugins/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/adapter.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/base.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/gitleaks.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/poutine.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/semgrep.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/trivy.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/scanners/zizmor.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/app.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/attack_paths/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/attack_paths/routes.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/dashboard/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/sentry/routes.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/css/bootstrap.min.css +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/css/style.css +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/js/bootstrap.bundle.min.js +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/js/chart.umd.min.js +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/js/dashboard.js +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/summary/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/summary/routes.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/templates/index.html +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/topology/__init__.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/topology/routes.py +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar.egg-info/dependency_links.txt +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar.egg-info/entry_points.txt +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar.egg-info/requires.txt +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar.egg-info/top_level.txt +0 -0
- {devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/setup.cfg +0 -0
|
@@ -39,7 +39,7 @@ DASHBOARD_HTML = r"""
|
|
|
39
39
|
<nav class="navbar navbar-dark border-bottom border-secondary mb-4" style="background:#1e293b;">
|
|
40
40
|
<div class="container-fluid">
|
|
41
41
|
<span class="navbar-brand mb-0 h1">🛡️ Pipeline Sentinel</span>
|
|
42
|
-
<span class="text-muted">v0.3.
|
|
42
|
+
<span class="text-muted">v0.3.3</span>
|
|
43
43
|
</div>
|
|
44
44
|
</nav>
|
|
45
45
|
|
|
@@ -50,6 +50,9 @@ devsecops_radar/web/summary/routes.py
|
|
|
50
50
|
devsecops_radar/web/templates/index.html
|
|
51
51
|
devsecops_radar/web/topology/__init__.py
|
|
52
52
|
devsecops_radar/web/topology/routes.py
|
|
53
|
+
tests/test_analyzer.py
|
|
53
54
|
tests/test_api.py
|
|
54
55
|
tests/test_cli.py
|
|
56
|
+
tests/test_database.py
|
|
57
|
+
tests/test_rule_fusion.py
|
|
55
58
|
tests/test_scanners.py
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
from devsecops_radar.core.analyzer import OllamaAnalyzer, extract_json, select_findings_for_llm
|
|
4
|
+
|
|
5
|
+
def test_extract_json_plain():
|
|
6
|
+
text = '{"executive_summary": "test", "attack_paths": [], "top_remediations": []}'
|
|
7
|
+
result = extract_json(text)
|
|
8
|
+
assert result["executive_summary"] == "test"
|
|
9
|
+
|
|
10
|
+
def test_extract_json_malformed():
|
|
11
|
+
result = extract_json("some text {invalid")
|
|
12
|
+
assert "executive_summary" in result # falls back to wrapping the text
|
|
13
|
+
|
|
14
|
+
def test_select_findings_for_llm():
|
|
15
|
+
findings = [{"severity": "CRITICAL"}] * 120 + [{"severity": "LOW"}] * 50
|
|
16
|
+
selected = select_findings_for_llm(findings, max_items=100)
|
|
17
|
+
assert len(selected) == 100
|
|
18
|
+
# All criticals should be included
|
|
19
|
+
criticals = [f for f in selected if f["severity"] == "CRITICAL"]
|
|
20
|
+
assert len(criticals) == 100
|
|
21
|
+
|
|
22
|
+
@patch('requests.Session.post')
|
|
23
|
+
def test_ollama_analyzer_success(mock_post):
|
|
24
|
+
mock_response = MagicMock()
|
|
25
|
+
mock_response.json.return_value = {"response": '{"executive_summary": "ok", "attack_paths": [], "top_remediations": []}'}
|
|
26
|
+
mock_response.raise_for_status.return_value = None
|
|
27
|
+
mock_post.return_value = mock_response
|
|
28
|
+
analyzer = OllamaAnalyzer()
|
|
29
|
+
findings = [{"severity": "CRITICAL", "id": "1", "tool": "test"}]
|
|
30
|
+
analysis = analyzer.analyze(findings)
|
|
31
|
+
assert analysis["executive_summary"] == "ok"
|
|
32
|
+
|
|
33
|
+
@patch('requests.Session.post')
|
|
34
|
+
def test_ollama_analyzer_network_error(mock_post):
|
|
35
|
+
mock_post.side_effect = Exception("Network down")
|
|
36
|
+
analyzer = OllamaAnalyzer()
|
|
37
|
+
findings = [{"severity": "CRITICAL", "id": "1", "tool": "test"}]
|
|
38
|
+
analysis = analyzer.analyze(findings)
|
|
39
|
+
assert "AI failed" in analysis["executive_summary"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from devsecops_radar.web.app import create_app
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
@pytest.fixture
|
|
6
|
+
def app():
|
|
7
|
+
app = create_app()
|
|
8
|
+
app.config['TESTING'] = True
|
|
9
|
+
return app
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def client(app):
|
|
13
|
+
return app.test_client()
|
|
14
|
+
|
|
15
|
+
def test_dashboard_page(client):
|
|
16
|
+
resp = client.get('/')
|
|
17
|
+
assert resp.status_code == 200
|
|
18
|
+
|
|
19
|
+
def test_findings_api_requires_key_when_set(monkeypatch, client):
|
|
20
|
+
monkeypatch.setenv("PIPELINE_API_KEY", "secret")
|
|
21
|
+
resp = client.get('/api/findings')
|
|
22
|
+
assert resp.status_code == 401
|
|
23
|
+
resp = client.get('/api/findings', headers={"X-API-Key": "secret"})
|
|
24
|
+
assert resp.status_code == 200
|
|
25
|
+
|
|
26
|
+
def test_findings_api_open_when_disabled(monkeypatch, client):
|
|
27
|
+
monkeypatch.setenv("PIPELINE_API_KEY", "disabled")
|
|
28
|
+
resp = client.get('/api/findings')
|
|
29
|
+
assert resp.status_code == 200
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
def test_cli_help():
|
|
7
|
+
result = subprocess.run(['devsecops-radar', '--help'], capture_output=True, text=True)
|
|
8
|
+
assert result.returncode == 0
|
|
9
|
+
assert '--trivy' in result.stdout
|
|
10
|
+
|
|
11
|
+
def test_cli_wizard_flag():
|
|
12
|
+
result = subprocess.run(['devsecops-radar', '--wizard'], capture_output=True, text=True)
|
|
13
|
+
assert result.returncode == 0
|
|
14
|
+
assert 'Quick Setup Wizard' in result.stdout or 'Welcome' in result.stdout
|
|
15
|
+
|
|
16
|
+
def test_cli_merge_sample_files():
|
|
17
|
+
sample_dir = os.path.join(os.path.dirname(__file__), '..')
|
|
18
|
+
trivy_sample = os.path.join(sample_dir, 'sample_trivy.json')
|
|
19
|
+
semgrep_sample = os.path.join(sample_dir, 'sample_semgrep.json')
|
|
20
|
+
if not os.path.exists(trivy_sample) or not os.path.exists(semgrep_sample):
|
|
21
|
+
pytest.skip("Sample files not found")
|
|
22
|
+
with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as outfile:
|
|
23
|
+
outpath = outfile.name
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
['devsecops-radar', '--trivy', trivy_sample, '--semgrep', semgrep_sample, '--output', outpath],
|
|
26
|
+
capture_output=True, text=True
|
|
27
|
+
)
|
|
28
|
+
assert result.returncode == 0
|
|
29
|
+
assert 'Merged' in result.stdout
|
|
30
|
+
os.unlink(outpath)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
# Set a temporary database BEFORE importing any devsecops_radar modules
|
|
6
|
+
@pytest.fixture(scope="module")
|
|
7
|
+
def temp_db():
|
|
8
|
+
# Create a unique temp file path for the database
|
|
9
|
+
fd, tmpfile = tempfile.mkstemp(suffix='.db')
|
|
10
|
+
os.close(fd)
|
|
11
|
+
old_val = os.environ.get("DATABASE_URL")
|
|
12
|
+
os.environ["DATABASE_URL"] = f"sqlite:///{tmpfile}"
|
|
13
|
+
|
|
14
|
+
# Now import the database functions — they will use the temp DB
|
|
15
|
+
from devsecops_radar.core.database import save_scan, get_all_scans, get_findings_paginated
|
|
16
|
+
|
|
17
|
+
yield tmpfile, save_scan, get_all_scans, get_findings_paginated
|
|
18
|
+
|
|
19
|
+
# Cleanup
|
|
20
|
+
os.unlink(tmpfile)
|
|
21
|
+
if old_val is None:
|
|
22
|
+
del os.environ["DATABASE_URL"]
|
|
23
|
+
else:
|
|
24
|
+
os.environ["DATABASE_URL"] = old_val
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_save_and_retrieve(temp_db):
|
|
28
|
+
tmpfile, save_scan, get_all_scans, get_findings_paginated = temp_db
|
|
29
|
+
findings = [
|
|
30
|
+
{
|
|
31
|
+
"tool": "test",
|
|
32
|
+
"severity": "HIGH",
|
|
33
|
+
"id": "1",
|
|
34
|
+
"target": "t",
|
|
35
|
+
"title": "t",
|
|
36
|
+
"description": "d"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
save_scan(findings)
|
|
40
|
+
scans = get_all_scans()
|
|
41
|
+
assert len(scans) > 0
|
|
42
|
+
paginated = get_findings_paginated(1, 10)
|
|
43
|
+
assert paginated["total"] >= 1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import tempfile
|
|
3
|
+
import os
|
|
4
|
+
from devsecops_radar.core.rule_fusion import RuleFusion
|
|
5
|
+
|
|
6
|
+
class TestPolicyEvaluation:
|
|
7
|
+
def test_policy_pass(self):
|
|
8
|
+
findings = [{"severity": "CRITICAL"}] * 3
|
|
9
|
+
policy = {"max_critical": 5, "on_violation": "fail"}
|
|
10
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
11
|
+
json.dump(policy, f)
|
|
12
|
+
f.flush()
|
|
13
|
+
passed, msg = RuleFusion.evaluate_policy(findings, f.name)
|
|
14
|
+
os.unlink(f.name)
|
|
15
|
+
assert passed
|
|
16
|
+
assert "passed" in msg
|
|
17
|
+
|
|
18
|
+
def test_policy_fail(self):
|
|
19
|
+
findings = [{"severity": "CRITICAL"}] * 6
|
|
20
|
+
policy = {"max_critical": 5, "on_violation": "fail"}
|
|
21
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
22
|
+
json.dump(policy, f)
|
|
23
|
+
f.flush()
|
|
24
|
+
passed, msg = RuleFusion.evaluate_policy(findings, f.name)
|
|
25
|
+
os.unlink(f.name)
|
|
26
|
+
assert not passed
|
|
27
|
+
|
|
28
|
+
def test_policy_file_not_found(self):
|
|
29
|
+
passed, msg = RuleFusion.evaluate_policy([], "/nonexistent/policy.json")
|
|
30
|
+
assert passed
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import tempfile
|
|
3
|
+
import os
|
|
4
|
+
import pytest
|
|
5
|
+
from devsecops_radar.scanners.trivy import TrivyScanner
|
|
6
|
+
from devsecops_radar.scanners.semgrep import SemgrepScanner
|
|
7
|
+
from devsecops_radar.scanners.poutine import PoutineScanner
|
|
8
|
+
from devsecops_radar.scanners.zizmor import ZizmorScanner
|
|
9
|
+
from devsecops_radar.scanners.gitleaks import GitleaksScanner
|
|
10
|
+
|
|
11
|
+
def write_temp_json(data):
|
|
12
|
+
tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
|
|
13
|
+
json.dump(data, tmp)
|
|
14
|
+
tmp.close()
|
|
15
|
+
return tmp.name
|
|
16
|
+
|
|
17
|
+
class TestTrivyScanner:
|
|
18
|
+
def test_valid_json(self):
|
|
19
|
+
data = {"Results": [{"Target": "img", "Vulnerabilities": [{"VulnerabilityID": "CVE-1", "Severity": "HIGH"}]}]}
|
|
20
|
+
path = write_temp_json(data)
|
|
21
|
+
findings = TrivyScanner().parse(path)
|
|
22
|
+
os.unlink(path)
|
|
23
|
+
assert len(findings) == 1
|
|
24
|
+
assert findings[0]["severity"] == "HIGH"
|
|
25
|
+
|
|
26
|
+
def test_invalid_json(self):
|
|
27
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
28
|
+
f.write("not json")
|
|
29
|
+
path = f.name
|
|
30
|
+
findings = TrivyScanner().parse(path)
|
|
31
|
+
os.unlink(path)
|
|
32
|
+
assert findings == []
|
|
33
|
+
|
|
34
|
+
def test_missing_results(self):
|
|
35
|
+
data = {"not_results": []}
|
|
36
|
+
path = write_temp_json(data)
|
|
37
|
+
findings = TrivyScanner().parse(path)
|
|
38
|
+
os.unlink(path)
|
|
39
|
+
assert findings == []
|
|
40
|
+
|
|
41
|
+
class TestSemgrepScanner:
|
|
42
|
+
def test_valid(self):
|
|
43
|
+
data = {"results": [{"path": "a.py", "check_id": "x", "extra": {"severity": "ERROR", "message": "bad"}}]}
|
|
44
|
+
path = write_temp_json(data)
|
|
45
|
+
findings = SemgrepScanner().parse(path)
|
|
46
|
+
os.unlink(path)
|
|
47
|
+
assert len(findings) == 1
|
|
48
|
+
assert findings[0]["severity"] == "ERROR"
|
|
49
|
+
|
|
50
|
+
def test_invalid(self):
|
|
51
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
52
|
+
f.write("{invalid")
|
|
53
|
+
path = f.name
|
|
54
|
+
findings = SemgrepScanner().parse(path)
|
|
55
|
+
os.unlink(path)
|
|
56
|
+
assert findings == []
|
|
57
|
+
|
|
58
|
+
class TestPoutineScanner:
|
|
59
|
+
def test_valid(self):
|
|
60
|
+
data = {"findings": [{"rule_id": "x", "severity": "HIGH", "message": "bad", "location": {"file": "f", "line": 1}}]}
|
|
61
|
+
path = write_temp_json(data)
|
|
62
|
+
findings = PoutineScanner().parse(path)
|
|
63
|
+
os.unlink(path)
|
|
64
|
+
assert findings[0]["severity"] == "HIGH"
|
|
65
|
+
|
|
66
|
+
class TestZizmorScanner:
|
|
67
|
+
def test_valid(self):
|
|
68
|
+
data = {"findings": [{"rule_id": "z1", "severity": "LOW", "message": "m", "path": "p", "location": {"line": 2}}]}
|
|
69
|
+
path = write_temp_json(data)
|
|
70
|
+
findings = ZizmorScanner().parse(path)
|
|
71
|
+
os.unlink(path)
|
|
72
|
+
assert findings[0]["severity"] == "LOW"
|
|
73
|
+
|
|
74
|
+
class TestGitleaksScanner:
|
|
75
|
+
def test_valid_list(self):
|
|
76
|
+
data = [{"file": "f", "ruleID": "r", "description": "d", "line": 1}]
|
|
77
|
+
path = write_temp_json(data)
|
|
78
|
+
findings = GitleaksScanner().parse(path)
|
|
79
|
+
os.unlink(path)
|
|
80
|
+
assert len(findings) == 1
|
|
81
|
+
assert findings[0]["severity"] == "HIGH"
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from devsecops_radar.web.app import create_app
|
|
3
|
-
|
|
4
|
-
@pytest.fixture
|
|
5
|
-
def app():
|
|
6
|
-
app = create_app()
|
|
7
|
-
app.config['TESTING'] = True
|
|
8
|
-
return app
|
|
9
|
-
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def client(app):
|
|
12
|
-
return app.test_client()
|
|
13
|
-
|
|
14
|
-
def test_dashboard_route(client):
|
|
15
|
-
response = client.get('/')
|
|
16
|
-
assert response.status_code == 200
|
|
File without changes
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import tempfile
|
|
3
|
-
import os
|
|
4
|
-
from devsecops_radar.scanners.trivy import TrivyScanner
|
|
5
|
-
|
|
6
|
-
def test_trivy_parse():
|
|
7
|
-
scanner = TrivyScanner()
|
|
8
|
-
data = {
|
|
9
|
-
"Results": [{
|
|
10
|
-
"Target": "test-image",
|
|
11
|
-
"Vulnerabilities": [{
|
|
12
|
-
"VulnerabilityID": "CVE-2026-0001",
|
|
13
|
-
"Severity": "HIGH",
|
|
14
|
-
"Title": "Test vuln",
|
|
15
|
-
"Description": "Test"
|
|
16
|
-
}]
|
|
17
|
-
}]
|
|
18
|
-
}
|
|
19
|
-
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
20
|
-
json.dump(data, f)
|
|
21
|
-
f.flush()
|
|
22
|
-
findings = scanner.parse(f.name)
|
|
23
|
-
os.unlink(f.name)
|
|
24
|
-
assert len(findings) == 1
|
|
25
|
-
assert findings[0]["severity"] == "HIGH"
|
|
26
|
-
assert findings[0]["id"] == "CVE-2026-0001"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/attack_paths/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/css/bootstrap.min.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar/web/static/js/chart.umd.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devsecops_radar-0.3.2 → devsecops_radar-0.3.3}/devsecops_radar.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|