feat: scoped npm support (@angular/core style paths) in extract_npm_info

This commit is contained in:
Marker689
2026-05-11 20:46:42 +03:00
parent b50c64aadb
commit 2dd26272cb
3 changed files with 64 additions and 7 deletions

View File

@@ -63,16 +63,27 @@ def extract_go_info(asset_path: str) -> tuple[str, str] | None:
def extract_npm_info(asset_path: str) -> tuple[str, str] | None:
"""Extract package name and version from an npm proxy asset path.
Path format: packages/react/-/react-18.2.0.tgz
Path format:
packages/react/-/react-18.2.0.tgz
packages/@angular/core/-/core-18.0.0.tgz (scoped)
"""
parts = asset_path.strip("/").split("/")
if len(parts) < 4 or parts[0] != PKG_PATH_PREFIX:
return None
# Scoped package: @scope/name
if parts[1].startswith("@"):
if len(parts) < 5:
return None
name = f"{parts[1]}/{parts[2]}"
short_name = parts[2]
else:
name = parts[1]
# Last segment: <name>-<version>.tgz
short_name = name
last = parts[-1]
if last.startswith(name + "-"):
raw = last[len(name) + 1 :]
if last.startswith(short_name + "-"):
raw = last[len(short_name) + 1 :]
for ext in (".tgz", ".tar.gz"):
if raw.endswith(ext):
return name, raw[: -len(ext)]

View File

@@ -103,6 +103,44 @@ class TestWebhookToScanFlow:
data = resp.json()
assert data["status"] == "accepted"
@pytest.mark.asyncio
async def test_e2e_webhook_accepts_scoped_npm_asset(self, e2e_client, e2e_db_session):
"""Verify that scoped npm (@scope/name) assets are accepted."""
payload = {
"action": "UPDATED",
"repositoryName": "npm-proxy",
"initiator": "e2e-test",
"asset": {
"format": "npm",
"name": "/packages/@angular/core/-/core-18.0.0.tgz",
"downloadUrl": "http://nexus:8081/repository/npm-proxy/@angular/core/-/core-18.0.0.tgz",
},
}
async def mock_harvest(*args, **kwargs):
from guarddog_nexus.db.models import Scan, ScanStatus
scan = Scan(
package_name="@angular/core",
package_version="18.0.0",
ecosystem="npm",
repository="npm-proxy",
nexus_asset_url=args[0],
status=ScanStatus.COMPLETED.value,
total_findings=0,
flagged=False,
)
e2e_db_session.add(scan)
await e2e_db_session.commit()
await e2e_db_session.refresh(scan)
return scan
with patch("guarddog_nexus.routes.webhooks._scan_in_background", mock_harvest):
resp = await e2e_client.post("/webhooks/nexus", json=payload)
assert resp.status_code == 200
assert resp.json()["status"] == "accepted"
class TestWebhookSignatureValidation:
"""E2E tests for webhook signature validation."""

View File

@@ -56,8 +56,16 @@ class TestNpmExtractor:
)
def test_scoped_package(self):
# Note: scoped packages have a different path in Nexus
assert extract_npm_info("packages/@scope/name/-/name-1.0.0.tgz") is None
assert extract_npm_info("packages/@scope/name/-/name-1.0.0.tgz") == (
"@scope/name",
"1.0.0",
)
def test_scoped_angular_core(self):
assert extract_npm_info("packages/@angular/core/-/core-18.0.0.tgz") == (
"@angular/core",
"18.0.0",
)
def test_not_packages(self):
assert extract_npm_info("/other/lodash/-/lodash-4.17.21.tgz") is None