Skip to content

Commit 61dd491

Browse files
devatsecureclaude
andcommitted
fix: Resolve 4 pipeline runtime issues for full phase execution
1. Trivy/Checkov disk space: Set TMPDIR=/var/tmp in Dockerfile, ensure /var/tmp has 1777 permissions for large temp files (DB downloads) 2. Fuzzing: Fix method mismatch scanner.scan() -> scanner.fuzz_function() with Python file discovery and crash-to-finding conversion 3. Runtime Security: Fix monitor.monitor() -> monitor.monitor_realtime() with ThreatAlert-to-HybridFinding conversion 4. DAST: Auto-discover OpenAPI/Swagger specs when no --dast-target-url provided, search target dir for openapi.json/swagger.yaml patterns 5. Docker socket: Add docker group (GID 999) to agentuser for sandbox validation access Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6b645d5 commit 61dd491

File tree

2 files changed

+109
-56
lines changed

2 files changed

+109
-56
lines changed

Dockerfile.complete

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ RUN wget -q https://github.com/zaproxy/zaproxy/releases/download/v2.14.0/ZAP_2.1
5050
RUN wget -q https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static -O /usr/local/bin/opa && \
5151
chmod +x /usr/local/bin/opa
5252

53-
# Set Python environment variables
53+
# Set Python environment variables + use /var/tmp for large temp files (Trivy DB, Checkov)
54+
# /tmp in containers is often a small overlay; /var/tmp persists and has more space
5455
ENV PYTHONUNBUFFERED=1 \
5556
PYTHONDONTWRITEBYTECODE=1 \
5657
PIP_NO_CACHE_DIR=1 \
57-
PYTHONPATH=/app
58+
PYTHONPATH=/app \
59+
TMPDIR=/var/tmp
5860

5961
WORKDIR /app
6062

@@ -82,16 +84,20 @@ COPY profiles/ ./profiles/
8284
COPY schemas/ ./schemas/
8385
COPY config/ ./config/
8486

85-
# Create non-root user for security with home directory (needed by semgrep, trivy, etc.)
86-
RUN groupadd -r agentuser && useradd -r -g agentuser -u 1000 -m agentuser
87+
# Create non-root user with docker group access for sandbox validation
88+
# GID 999 matches the host docker group on most Linux distros
89+
RUN groupadd -r -g 999 docker || true && \
90+
groupadd -r agentuser && \
91+
useradd -r -g agentuser -G docker -u 1000 -m agentuser
8792

88-
# Create workspace and output directories with proper permissions
89-
RUN mkdir -p /workspace /output /home/agentuser/.semgrep && \
93+
# Create workspace, output, and temp directories with proper permissions
94+
RUN mkdir -p /workspace /output /var/tmp /home/agentuser/.semgrep && \
9095
chmod 755 /workspace /output && \
96+
chmod 1777 /var/tmp && \
9197
chown -R agentuser:agentuser /app /workspace /output /home/agentuser
9298

93-
# Initialize Trivy database
94-
RUN trivy image --download-db-only || true
99+
# Initialize Trivy database (use /var/tmp to avoid /tmp space issues)
100+
RUN TMPDIR=/var/tmp trivy image --download-db-only || true
95101

96102
WORKDIR /workspace
97103

scripts/hybrid/scanner_runners.py

Lines changed: 95 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"""
3030

3131
import logging
32+
import os
3233
from typing import Any
3334

3435
from hybrid.models import HybridFinding
@@ -241,14 +242,41 @@ def run_api_security(scanner: Any, target_path: str, logger: logging.Logger) ->
241242
return findings
242243

243244

245+
def _discover_openapi_spec(target_path: str, logger: logging.Logger) -> str | None:
246+
"""Auto-discover OpenAPI/Swagger spec files in the target directory."""
247+
import glob as glob_mod
248+
spec_patterns = [
249+
"openapi.json", "openapi.yaml", "openapi.yml",
250+
"swagger.json", "swagger.yaml", "swagger.yml",
251+
"**/openapi.json", "**/openapi.yaml", "**/openapi.yml",
252+
"**/swagger.json", "**/swagger.yaml", "**/swagger.yml",
253+
"api-spec.*", "api-docs.*",
254+
]
255+
for pattern in spec_patterns:
256+
matches = glob_mod.glob(os.path.join(target_path, pattern), recursive=True)
257+
if matches:
258+
logger.info(f" 🔍 DAST: Auto-discovered OpenAPI spec: {matches[0]}")
259+
return matches[0]
260+
return None
261+
262+
244263
def run_dast(scanner: Any, target_path: str, logger: logging.Logger, config: dict, dast_target_url: str | None = None) -> list[HybridFinding]:
245-
"""Run DAST Scanner and convert to HybridFinding format"""
264+
"""Run DAST Scanner and convert to HybridFinding format.
265+
266+
If no --dast-target-url is provided, auto-discovers OpenAPI/Swagger
267+
spec files in the target directory for static DAST analysis.
268+
"""
246269
findings = []
247270

248-
# DAST requires a target URL
271+
# If no URL provided, try to auto-discover an OpenAPI spec
249272
if not dast_target_url:
250-
logger.info(" ℹ️ DAST: No target URL provided, skipping")
251-
return findings
273+
openapi_spec = _discover_openapi_spec(target_path, logger)
274+
if openapi_spec:
275+
# Reconfigure scanner with discovered spec
276+
scanner.openapi_spec = openapi_spec
277+
else:
278+
logger.info(" ℹ️ DAST: No target URL or OpenAPI spec found, skipping")
279+
return findings
252280

253281
try:
254282
# Run DAST scanner
@@ -325,32 +353,52 @@ def run_supply_chain(scanner: Any, target_path: str, logger: logging.Logger) ->
325353

326354

327355
def run_fuzzing(scanner: Any, target_path: str, logger: logging.Logger) -> list[HybridFinding]:
328-
"""Run Intelligent Fuzzing Engine and convert to HybridFinding format"""
356+
"""Run Intelligent Fuzzing Engine and convert to HybridFinding format.
357+
358+
Discovers Python files with parseable functions and fuzzes them
359+
using FuzzingEngine.fuzz_function().
360+
"""
329361
findings = []
330362

331363
try:
332-
# Run Fuzzing scanner
333-
fuzzing_result = scanner.scan(target_path)
334-
335-
# Convert to HybridFinding format
336-
if isinstance(fuzzing_result, list):
337-
for fuzz_finding in fuzzing_result:
338-
finding = HybridFinding(
339-
finding_id=fuzz_finding.get("id", "unknown"),
340-
source_tool="fuzzing",
341-
severity=normalize_severity(fuzz_finding.get("severity", "medium")),
342-
category="security",
343-
title=fuzz_finding.get("title", "Fuzzing Crash"),
344-
description=fuzz_finding.get("description", ""),
345-
file_path=fuzz_finding.get("file_path", target_path),
346-
line_number=fuzz_finding.get("line_number"),
347-
cwe_id=fuzz_finding.get("cwe_id"),
348-
recommendation=fuzz_finding.get("recommendation", ""),
349-
references=fuzz_finding.get("references", []),
350-
confidence=fuzz_finding.get("confidence", 1.0),
351-
llm_enriched=False,
364+
import glob as glob_mod
365+
366+
# Discover Python files to fuzz (focus on security-sensitive patterns)
367+
py_files = glob_mod.glob(os.path.join(target_path, "**", "*.py"), recursive=True)
368+
if not py_files:
369+
logger.info(" ℹ️ No Python files found for fuzzing")
370+
return findings
371+
372+
# Fuzz up to 5 files to keep duration reasonable
373+
fuzz_targets = py_files[:5]
374+
for py_file in fuzz_targets:
375+
rel_path = os.path.relpath(py_file, target_path)
376+
try:
377+
fuzzing_result = scanner.fuzz_function(
378+
function_path=py_file,
379+
function_name="__main__",
380+
duration_minutes=1,
352381
)
353-
findings.append(finding)
382+
# Convert crashes from FuzzResult to HybridFinding
383+
if hasattr(fuzzing_result, "crashes"):
384+
for crash in fuzzing_result.crashes:
385+
finding = HybridFinding(
386+
finding_id=f"fuzz-{crash.crash_id}",
387+
source_tool="fuzzing",
388+
severity=normalize_severity(getattr(crash, "severity", "medium")),
389+
category="security",
390+
title=f"Fuzzing crash in {rel_path}: {crash.crash_type}",
391+
description=f"Crash type: {crash.crash_type}\nStack trace: {crash.stack_trace[:500]}",
392+
file_path=rel_path,
393+
cwe_id=getattr(crash, "cwe", None),
394+
recommendation="Review and fix the crash-inducing input handling",
395+
confidence=1.0 if crash.reproducible else 0.7,
396+
llm_enriched=False,
397+
)
398+
findings.append(finding)
399+
except Exception as e:
400+
logger.debug(f" Fuzzing {rel_path} skipped: {e}")
401+
continue
354402

355403
except Exception as e:
356404
logger.error(f"❌ Fuzzing failed: {e}")
@@ -455,34 +503,33 @@ def run_remediation(engine: Any, findings: list[HybridFinding], logger: logging.
455503

456504

457505
def run_runtime_security(monitor: Any, target_path: str, logger: logging.Logger, monitoring_duration: int) -> list[HybridFinding]:
458-
"""Run Container Runtime Security Monitoring"""
506+
"""Run Container Runtime Security Monitoring using Falco-based monitor_realtime()."""
459507
findings = []
460508

461509
try:
462510
logger.info(f" 🐳 Monitoring runtime security for {monitoring_duration}s...")
463511

464-
# Run runtime security monitor
465-
runtime_result = monitor.monitor(target_path)
512+
# Use monitor_realtime() which returns List[ThreatAlert]
513+
alerts = monitor.monitor_realtime(
514+
duration_seconds=monitoring_duration,
515+
)
466516

467-
# Convert to HybridFinding format
468-
if isinstance(runtime_result, list):
469-
for runtime_finding in runtime_result:
470-
finding = HybridFinding(
471-
finding_id=runtime_finding.get("id", "unknown"),
472-
source_tool="runtime-security",
473-
severity=normalize_severity(runtime_finding.get("severity", "medium")),
474-
category="runtime",
475-
title=runtime_finding.get("title", "Runtime Security Threat"),
476-
description=runtime_finding.get("description", ""),
477-
file_path=runtime_finding.get("file_path", target_path),
478-
line_number=runtime_finding.get("line_number"),
479-
cwe_id=runtime_finding.get("cwe_id"),
480-
recommendation=runtime_finding.get("recommendation", ""),
481-
references=runtime_finding.get("references", []),
482-
confidence=runtime_finding.get("confidence", 0.9),
483-
llm_enriched=False,
484-
)
485-
findings.append(finding)
517+
# Convert ThreatAlert objects to HybridFinding format
518+
for alert in (alerts or []):
519+
finding = HybridFinding(
520+
finding_id=f"runtime-{getattr(alert, 'alert_id', 'unknown')}",
521+
source_tool="runtime-security",
522+
severity=normalize_severity(getattr(alert, "severity", "medium")),
523+
category="runtime",
524+
title=getattr(alert, "title", "Runtime Security Threat"),
525+
description=getattr(alert, "description", str(alert)),
526+
file_path=getattr(alert, "container_id", target_path),
527+
cwe_id=getattr(alert, "cwe_id", None),
528+
recommendation=getattr(alert, "recommendation", "Review runtime security event"),
529+
confidence=0.9,
530+
llm_enriched=False,
531+
)
532+
findings.append(finding)
486533

487534
except Exception as e:
488535
logger.error(f"❌ Runtime security monitoring failed: {e}")

0 commit comments

Comments
 (0)