Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,60 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
<<<<<<< HEAD
python-version: "3.11"
- name: Install backend system dependencies
run: sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config
- name: Install backend development dependencies
run: |
python -m pip install --upgrade pip
pip install -r backend/requirements.txt -r backend/requirements-dev.txt
- name: Run backend lint baseline
run: ruff check backend testing/backend

backend-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install backend system dependencies
run: sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config
- name: Install backend dependencies
run: |
python -m pip install --upgrade pip
pip install -r backend/requirements.txt -r backend/requirements-dev.txt
- name: Run backend tests
run: pytest testing/backend -q

frontend-checks:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
run: npm ci
- name: Run frontend TypeScript typecheck
run: npm run typecheck
- name: Note TypeScript typecheck in job summary
if: always()
run: |
echo "Frontend TypeScript typecheck: npm run typecheck" >> "$GITHUB_STEP_SUMMARY"
- name: Run frontend quality gate
run: npm run quality
- name: Run unit tests
run: npm run test
- name: Build frontend
run: npm run build
=======
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
Expand All @@ -29,6 +83,7 @@ jobs:
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
run: python3 scripts/select_tests.py
>>>>>>> upstream/main

formatting-hygiene:
needs: detect-changes
Expand Down
Empty file added 0
Empty file.
3 changes: 3 additions & 0 deletions backend/requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ httpx>=0.28.1
ruff>=0.15.12
pytest-asyncio>=0.24.0
anyio>=4.0.0
<<<<<<< HEAD
=======
trio>=0.27.0
>>>>>>> upstream/main
6 changes: 6 additions & 0 deletions backend/secuscan/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ async def run_scan(target: str, plugin_id: str, output_format: str, output_file:
return 1

# Create task
<<<<<<< HEAD
inputs = {"target": target}
try:
task_id = await executor.create_task(plugin_id, inputs, consent_granted=True)
=======
safe_mode = bool(settings.safe_mode_default)
inputs = {"target": target, "safe_mode": safe_mode}
try:
task_id = await executor.create_task(plugin_id, inputs, safe_mode=safe_mode, consent_granted=True)
>>>>>>> upstream/main
except Exception as e:
print(f"Error creating task: {e}")
return 1
Expand Down
5 changes: 4 additions & 1 deletion backend/secuscan/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,13 @@ class Settings(BaseSettings):
task_start_max_field_length: int = 1_000 # max chars per string input value
task_start_max_array_length: int = 50 # max items in any list/multiselect input

<<<<<<< HEAD
=======
# Parser sandbox limits
parser_sandbox_timeout_seconds: int = 30
parser_sandbox_max_output_bytes: int = 8 * 1024 * 1024 # 8 MB

>>>>>>> upstream/main
# Logging
log_level: str = "INFO"
log_file: str = str(PROJECT_ROOT / "logs" / "secuscan.log")
Expand Down Expand Up @@ -151,4 +154,4 @@ def ensure_directories(self) -> None:


# Global settings instance
settings = Settings()
settings = Settings()
13 changes: 13 additions & 0 deletions backend/secuscan/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
from .config import settings
from .database import get_db
from .plugins import get_plugin_manager
<<<<<<< HEAD
from .models import TaskStatus
from .ratelimit import concurrent_limiter
from .ratelimit import concurrent_limiter
=======
from .models import TaskStatus, ScanPhase
from .ratelimit import concurrent_limiter
from .risk_scoring import compute_risk_score, compute_risk_factors
Expand Down Expand Up @@ -58,6 +63,7 @@ def _validate_risk_fields(finding: dict) -> None:
ae = finding.get("asset_exposure")
if ae is not None and ae.lower() not in ("critical", "high", "medium", "low"):
raise ValueError(f"asset_exposure must be one of critical/high/medium/low, got {ae}")
>>>>>>> upstream/main

# Modular Scanners
from .scanners.port_scanner import PortScanner
Expand Down Expand Up @@ -465,6 +471,8 @@ async def execute_task(self, task_id: str):
await self._invalidate_cached_views()
raise # let asyncio complete the cancellation

<<<<<<< HEAD
=======
except CapabilityDeniedError as e:
logger.warning("Task %s blocked by capability policy: %s", task_id, e)
duration = (time.time() - start_time) if "start_time" in locals() else 0
Expand Down Expand Up @@ -499,6 +507,7 @@ async def execute_task(self, task_id: str):
task_id=task_id,
)

>>>>>>> upstream/main
except Exception as e:
logger.error(f"Task {task_id} failed: {e}", exc_info=True)

Expand Down Expand Up @@ -741,6 +750,10 @@ async def get_task_status(self, task_id: str) -> Optional[Dict]:
"exit_code": task_row["exit_code"],
"error_message": task_row["error_message"],
"preset": task_row["preset"],
<<<<<<< HEAD
"inputs": json.loads(task_row["inputs_json"] or "{}"),
=======
>>>>>>> upstream/main
"queue_position": queue_position,
"pending_count": pending_count,
}
Expand Down
3 changes: 3 additions & 0 deletions backend/secuscan/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ def compute_plugin_digest(metadata_file: Path, parser_file: Path) -> str:

return hashlib.sha256(f"{metadata_digest}:{parser_digest}".encode("utf-8")).hexdigest()

<<<<<<< HEAD
=======
def verify_parser_at_exec_time(
self, plugin: PluginMetadata, plugin_dir: Path
) -> bool:
Expand Down Expand Up @@ -260,6 +262,7 @@ def verify_parser_at_exec_time(

return True

>>>>>>> upstream/main
def get_plugin(self, plugin_id: str) -> Optional[PluginMetadata]:
"""Get plugin by ID"""
return self.plugins.get(plugin_id)
Expand Down
3 changes: 3 additions & 0 deletions backend/secuscan/redaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ def redact_dict(data: dict[str, Any]) -> dict[str, Any]:
return result


<<<<<<< HEAD
=======
# Keys whose values are unconditionally redacted in task inputs regardless of
# value format. Matched case-insensitively against the full key name.
_SENSITIVE_INPUT_KEYS: frozenset[str] = frozenset({
Expand Down Expand Up @@ -258,6 +260,7 @@ def redact_inputs(inputs: dict[str, Any]) -> dict[str, Any]:
return result


>>>>>>> upstream/main
# ── Helpers ───────────────────────────────────────────────────────────────────


Expand Down
Loading
Loading