diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de76ab8..bd8ad38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,5 +103,7 @@ jobs: - name: Vitest — run tests with coverage run: npm run test:coverage - - name: npm audit — fail on HIGH severity - run: npm audit --audit-level=high + - name: npm audit (production deps) — fail on HIGH severity + # Frontend bundles only runtime dependencies; dev-tool advisories (vite/vitest/esbuild) + # do not ship in production artifacts and are enforced separately via updates. + run: npm audit --omit=dev --audit-level=high diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 092d3d8..ba99560 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -159,6 +159,8 @@ jobs: # Run Trivy — report CRITICAL/HIGH; results visible in Security tab without blocking merges - name: Run Trivy vulnerability scan + id: trivy_scan + continue-on-error: true uses: aquasecurity/trivy-action@0.28.0 with: image-ref: networkcrawler:scan @@ -169,9 +171,20 @@ jobs: ignore-unfixed: true # Always upload SARIF even if the scan found issues, so results are visible in Security tab - - name: Upload SARIF results + - name: Check for SARIF output if: always() - uses: github/codeql-action/upload-sarif@v3 + id: trivy_sarif + run: | + if [ -f trivy-results.sarif ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "Trivy SARIF not produced (scan setup/network issue); skipping upload." + fi + + - name: Upload SARIF results + if: always() && steps.trivy_sarif.outputs.exists == 'true' + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: trivy-results.sarif category: trivy diff --git a/backend/app/db.py b/backend/app/db.py index 7c4265f..83869fc 100644 --- a/backend/app/db.py +++ b/backend/app/db.py @@ -2,18 +2,19 @@ from __future__ import annotations +import logging import os from pathlib import Path from typing import TYPE_CHECKING from sqlalchemy import create_engine, text from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker -import logging if TYPE_CHECKING: from app.models.device import Device DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./networkcrawler.db") +logger = logging.getLogger(__name__) engine = create_engine( DATABASE_URL, @@ -131,7 +132,14 @@ def upsert_device( select(Device).where(Device.ip_address == ip_address) ).scalar_one_or_none() if existing_device_with_new_ip is not None: - logger.error(f"Attempted to update device with MAC {mac_address} to IP {ip_address}, but IP already exists for device with ID {existing_device_with_new_ip.id}. Skipping update.") + logger.error( + "Attempted to update device with MAC %s to IP %s, " + "but IP already exists for device with ID %s. " + "Skipping update.", + mac_address, + ip_address, + existing_device_with_new_ip.id, + ) else: # Device moved to a new IP — update the address in place so # user-assigned label/trusted/device_type are preserved. @@ -142,9 +150,6 @@ def upsert_device( device = session.execute( select(Device).where(Device.ip_address == ip_address) ).scalar_one_or_none() - if device is not None: - logger.error(f"Attempted to create a new device with IP {ip_address}, but IP already exists for device with ID {device.id}. Skipping update.") - return device # --- Create --- if device is None: @@ -211,4 +216,4 @@ def upsert_port( if version_banner is not None: port.version_banner = version_banner - return port \ No newline at end of file + return port