fix: scanner now handles real guarddog v2 JSON format
This commit is contained in:
@@ -23,7 +23,9 @@ from guarddog_nexus.main import app # noqa: E402
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def db_engine():
|
||||
engine = create_async_engine("sqlite+aiosqlite:///file:guarddog_test?mode=memory&cache=shared&uri=true")
|
||||
engine = create_async_engine(
|
||||
"sqlite+aiosqlite:///file:guarddog_test?mode=memory&cache=shared&uri=true"
|
||||
)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
yield engine
|
||||
@@ -76,29 +78,44 @@ def sample_nexus_webhook():
|
||||
@pytest.fixture
|
||||
def guarddog_output_clean():
|
||||
return {
|
||||
"results": [],
|
||||
"errors": [],
|
||||
"package": "safe-pkg",
|
||||
"issues": 0,
|
||||
"errors": {},
|
||||
"results": {
|
||||
"obfuscation": {},
|
||||
"exec-base64": {},
|
||||
"shady-links": {},
|
||||
"typosquatting": None,
|
||||
"empty_information": None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def guarddog_output_flagged():
|
||||
return {
|
||||
"results": [
|
||||
{
|
||||
"rule": "shady-links",
|
||||
"severity": "WARNING",
|
||||
"message": "Package contains URL to suspicious domain",
|
||||
"location": "setup.py:15",
|
||||
},
|
||||
{
|
||||
"rule": "exec-base64",
|
||||
"severity": "ERROR",
|
||||
"message": "Base64-encoded code execution detected",
|
||||
"location": "core.py:42",
|
||||
},
|
||||
],
|
||||
"errors": [],
|
||||
"package": "bad-pkg",
|
||||
"issues": 3,
|
||||
"errors": {},
|
||||
"results": {
|
||||
"shady-links": [
|
||||
{
|
||||
"message": "Package contains URL to suspicious domain",
|
||||
"location": "setup.py:15",
|
||||
"code": "url = 'http://evil.com'",
|
||||
}
|
||||
],
|
||||
"exec-base64": [
|
||||
{
|
||||
"message": "Base64-encoded code execution detected",
|
||||
"location": "core.py:42",
|
||||
"code": "exec(base64.b64decode(...))",
|
||||
}
|
||||
],
|
||||
"empty_information": "Package description is empty",
|
||||
"obfuscation": {},
|
||||
"typosquatting": None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -109,15 +126,21 @@ def guarddog_normalized_flagged():
|
||||
{
|
||||
"rule": "shady-links",
|
||||
"severity": "WARNING",
|
||||
"message": "Suspicious URL",
|
||||
"message": "Package contains URL to suspicious domain",
|
||||
"location": "setup.py:15",
|
||||
},
|
||||
{
|
||||
"rule": "exec-base64",
|
||||
"severity": "ERROR",
|
||||
"message": "Base64 exec",
|
||||
"severity": "WARNING",
|
||||
"message": "Base64-encoded code execution detected",
|
||||
"location": "core.py:42",
|
||||
},
|
||||
{
|
||||
"rule": "empty_information",
|
||||
"severity": "WARNING",
|
||||
"message": "Package description is empty",
|
||||
"location": "",
|
||||
},
|
||||
],
|
||||
"errors": [],
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ async def test_harvest_new_package(db_session, guarddog_normalized_flagged):
|
||||
assert scan.ecosystem == "pypi"
|
||||
assert scan.status == "completed"
|
||||
assert scan.flagged is True
|
||||
assert scan.total_findings == 2
|
||||
assert scan.total_findings == 3
|
||||
assert scan.sha256 == "abc123"
|
||||
|
||||
findings = (
|
||||
@@ -42,7 +42,7 @@ async def test_harvest_new_package(db_session, guarddog_normalized_flagged):
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
assert len(findings) == 2
|
||||
assert len(findings) == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -58,11 +58,17 @@ async def test_harvest_skips_duplicate(db_session, guarddog_normalized_flagged):
|
||||
|
||||
first = await harvest(
|
||||
"http://nexus:8081/repo/pypi-proxy/packages/x/1.0/x-1.0.tar.gz",
|
||||
"pypi-proxy", "pypi", "packages/x/1.0/x-1.0.tar.gz", db_session,
|
||||
"pypi-proxy",
|
||||
"pypi",
|
||||
"packages/x/1.0/x-1.0.tar.gz",
|
||||
db_session,
|
||||
)
|
||||
second = await harvest(
|
||||
"http://nexus:8081/repo/pypi-proxy/packages/x/1.0/x-1.0.tar.gz",
|
||||
"pypi-proxy", "pypi", "packages/x/1.0/x-1.0.tar.gz", db_session,
|
||||
"pypi-proxy",
|
||||
"pypi",
|
||||
"packages/x/1.0/x-1.0.tar.gz",
|
||||
db_session,
|
||||
)
|
||||
|
||||
assert first is not None
|
||||
@@ -82,7 +88,10 @@ async def test_harvest_clean_package(db_session, guarddog_normalized_clean):
|
||||
|
||||
scan = await harvest(
|
||||
"http://nexus:8081/repo/pypi-proxy/packages/django/4.2/django-4.2.tar.gz",
|
||||
"pypi-proxy", "pypi", "packages/django/4.2/django-4.2.tar.gz", db_session,
|
||||
"pypi-proxy",
|
||||
"pypi",
|
||||
"packages/django/4.2/django-4.2.tar.gz",
|
||||
db_session,
|
||||
)
|
||||
|
||||
assert scan is not None
|
||||
@@ -97,7 +106,10 @@ async def test_harvest_download_failure(db_session):
|
||||
|
||||
scan = await harvest(
|
||||
"http://nexus:8081/repo/pypi-proxy/packages/fail/1.0/fail-1.0.tar.gz",
|
||||
"pypi-proxy", "pypi", "packages/fail/1.0/fail-1.0.tar.gz", db_session,
|
||||
"pypi-proxy",
|
||||
"pypi",
|
||||
"packages/fail/1.0/fail-1.0.tar.gz",
|
||||
db_session,
|
||||
)
|
||||
|
||||
assert scan is not None
|
||||
@@ -109,6 +121,9 @@ async def test_harvest_download_failure(db_session):
|
||||
async def test_harvest_skips_non_package_asset(db_session):
|
||||
scan = await harvest(
|
||||
"http://nexus:8081/repo/pypi-proxy/simple/index.html",
|
||||
"pypi-proxy", "pypi", "simple/index.html", db_session,
|
||||
"pypi-proxy",
|
||||
"pypi",
|
||||
"simple/index.html",
|
||||
db_session,
|
||||
)
|
||||
assert scan is None
|
||||
|
||||
@@ -11,18 +11,41 @@ def test_normalize_clean_output(guarddog_output_clean):
|
||||
|
||||
def test_normalize_flagged_output(guarddog_output_flagged):
|
||||
result = _normalize_output(guarddog_output_flagged)
|
||||
assert len(result["findings"]) == 2
|
||||
assert result["findings"][0]["rule"] == "shady-links"
|
||||
assert result["findings"][0]["severity"] == "WARNING"
|
||||
assert result["findings"][1]["rule"] == "exec-base64"
|
||||
assert result["findings"][1]["severity"] == "ERROR"
|
||||
assert len(result["findings"]) == 3
|
||||
rules = {f["rule"] for f in result["findings"]}
|
||||
assert "shady-links" in rules
|
||||
assert "exec-base64" in rules
|
||||
assert "empty_information" in rules
|
||||
|
||||
|
||||
def test_normalize_issues_format():
|
||||
def test_normalize_skips_null_and_empty_dicts():
|
||||
data = {
|
||||
"issues": [{"id": "test-rule", "severity": "ERROR", "description": "Bad"}],
|
||||
"errors": [],
|
||||
"issues": 0,
|
||||
"errors": {},
|
||||
"results": {
|
||||
"foo": None,
|
||||
"bar": {},
|
||||
"baz": "metadata finding",
|
||||
},
|
||||
}
|
||||
result = _normalize_output(data)
|
||||
assert len(result["findings"]) == 1
|
||||
assert result["findings"][0]["rule"] == "test-rule"
|
||||
assert result["findings"][0]["rule"] == "baz"
|
||||
assert result["findings"][0]["message"] == "metadata finding"
|
||||
|
||||
|
||||
def test_normalize_semgrep_list():
|
||||
data = {
|
||||
"issues": 2,
|
||||
"errors": {},
|
||||
"results": {
|
||||
"code-execution": [
|
||||
{"message": "Found exec()", "location": "setup.py:10", "severity": "ERROR"},
|
||||
{"message": "Found eval()", "location": "core.py:5", "severity": "ERROR"},
|
||||
],
|
||||
},
|
||||
}
|
||||
result = _normalize_output(data)
|
||||
assert len(result["findings"]) == 2
|
||||
assert result["findings"][0]["location"] == "setup.py:10"
|
||||
assert result["findings"][0]["severity"] == "ERROR"
|
||||
|
||||
Reference in New Issue
Block a user