Root cause
Other / insufficient public mechanism detail introduced
Initial commit — ciguard 0.1.0 ships the web UI
How AI contributed: Direct introductionPublished May 5, 2026 in
jo-jo98/ciguardAll 4 findings in this repository
CWE-693 protection mechanism failure: the bundled web UI served its responses without the standard defence-in-depth HTTP headers, so a hosted deployment would lose a layer against script injection, framing and cross-origin loading. The advisory records the local-only deployment as minimally affected and treats the hosted deployment planned for the project as the real exposure. Version 0.8.2 added a security-headers middleware.
The FastAPI web UI written for ciguard's first release mounted no security-header middleware, so every response went out without Content-Security-Policy, X-Frame-Options, the cross-origin headers, Permissions-Policy or X-Content-Type-Options.
Root cause
Initial commit — ciguard 0.1.0 ships the web UI
Fix
Adds a security-headers middleware that stamps Content-Security-Policy, X-Frame-Options, Referrer-Policy, Permissions-Policy and the cross-origin headers on every response, and registers it on the application.
AI-assisted fix: Claude Code · Jo.Moore
Case facts
@@ -0,0 +1,151 @@++# Allow running as `python -m src.web.app` from the project root+sys.path.insert(0, str(Path(__file__).parent.parent.parent))++from fastapi import FastAPI, File, HTTPException, Request, UploadFile+from fastapi.responses import HTMLResponse, JSONResponse+from fastapi.templating import Jinja2Templates++from ciguard.analyzer.engine import AnalysisEngine+from ciguard.parser.gitlab_parser import GitLabCIParser+from ciguard.reporter.html_report import HTMLReporter+from ciguard.web.scan_store import get_store++# ---------------------------------------------------------------------------+# App setup+# ---------------------------------------------------------------------------++TEMPLATES_DIR = Path(__file__).parent / "templates"+templates = Jinja2Templates(directory=str(TEMPLATES_DIR))++app = FastAPI(+ title="ciguard",+ description="CI/CD Pipeline Security Auditor",+ version="0.3.0",+ docs_url="/api/docs",+ redoc_url="/api/redoc",+)++_parser = GitLabCIParser()+_engine = AnalysisEngine()+_reporter = HTMLReporter()+++# ---------------------------------------------------------------------------+# API routes+# ---------------------------------------------------------------------------+[email protected]("/api/health", tags=["api"])+def api_health():+ """Health check — returns 200 when the service is ready."""+ return {"status": "ok", "version": "0.3.0", "scans_in_memory": len(get_store())}++[email protected]("/api/scan", tags=["api"])+async def api_scan(file: UploadFile = File(..., description="The .gitlab-ci.yml file to scan")):+ """Upload and scan a GitLab CI pipeline file.++ Returns a ``scan_id`` and high-level summary. Use+ ``GET /api/report/{scan_id}`` for the full JSON report.+ """+ if not file.filename:+ raise HTTPException(status_code=400, detail="No filename provided.")++ content = await file.read()++ if len(content) > GitLabCIParser.MAX_FILE_BYTES:+ raise HTTPException(+ status_code=413,+ detail=f"File too large. Maximum is {GitLabCIParser.MAX_FILE_BYTES // 1024} KB.",+ )@@ -0,0 +1,55 @@+"""HTTP security headers middleware (added v0.8.2).++Sets the standard set of defence-in-depth headers per the OWASP Secure+Headers Project. Per-path CSP carve-out for `/api/docs` because FastAPI's+auto-generated Swagger UI loads scripts/styles from the jsdelivr CDN.++The Web UI is intended for local-only deployment, but adding these+headers does no harm in that context and provides correct posture if a+user ever fronts ciguard with nginx + LetsEncrypt for a tiny private+team. Future PRD Slice 9 (GitHub App) and Slice 12+ (hosted dashboard)+will carry the same middleware forward.+"""+from __future__ import annotations++from starlette.middleware.base import BaseHTTPMiddleware+from starlette.requests import Request+from starlette.responses import Response+++class SecurityHeadersMiddleware(BaseHTTPMiddleware):+ async def dispatch(self, request: Request, call_next) -> Response:+ response = await call_next(request)+ response.headers["X-Content-Type-Options"] = "nosniff"+ response.headers["X-Frame-Options"] = "DENY"+ response.headers["Referrer-Policy"] = "no-referrer"+ response.headers["Permissions-Policy"] = "interest-cohort=()"+ response.headers["Cross-Origin-Opener-Policy"] = "same-origin"+ response.headers["Cross-Origin-Resource-Policy"] = "same-origin"+ # COEP intentionally NOT set to require-corp: Swagger UI's+ # cross-origin assets would break, and we have no SharedArrayBuffer+ # use that would benefit from it.++ # Per-path CSP. The Swagger UI at /api/docs needs jsdelivr.+ if request.url.path.startswith("/api/docs") or request.url.path.startswith("/api/redoc"):+ response.headers["Content-Security-Policy"] = (+ "default-src 'self'; "+ "script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; "+ "style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; "+ "img-src 'self' data: https://fastapi.tiangolo.com; "+ "font-src 'self' data:; "+ "connect-src 'self'"+ )+ else:+ response.headers["Content-Security-Policy"] = (+ "default-src 'self'; "+ "script-src 'self'; "+ "style-src 'self' 'unsafe-inline'; "+ "img-src 'self' data:; "+ "font-src 'self' data:; "+ "connect-src 'self'"+ )+ return response+++__all__ = ["SecurityHeadersMiddleware"]@@ -32,6 +32,7 @@ from ciguard.parser.gitlab_parser import GitLabCIParser from ciguard.parser.jenkinsfile import JenkinsfileParser, looks_like_jenkinsfile from ciguard.reporter.html_report import HTMLReporter from ciguard.web.scan_store import get_store+from ciguard.web.security_headers import SecurityHeadersMiddleware # --------------------------------------------------------------------------- # App setup@@ -47,6 +48,7 @@ app = FastAPI( docs_url="/api/docs", redoc_url="/api/redoc", )+app.add_middleware(SecurityHeadersMiddleware) _parser = GitLabCIParser() _engine = AnalysisEngine()AI-assisted change 7e9b6ed58ceafc8e0061a20f7b8df4ffe83cef9db711328a02cc89dccfd217cf · Fix 4a50832fd9efea1c3c860f95bf613e0a036d9b1578d2013c4b782f52057c0382