Repository navigation
Merged
Conversation
- Replace deprecated ::set-output with $GITHUB_OUTPUT - Bump actions/checkout v2 → v4 - Bump docker/setup-qemu-action v1 → v3 - Bump docker/setup-buildx-action v1 → v3 - Bump docker/login-action v1 → v3 - Bump docker/build-push-action v2 → v6 - Use --password-stdin for JCR docker login
sys.exit(1) inside a FastAPI handler raises SystemExit which propagates past ASGI exception handling and terminates the uvicorn process. Any unauthenticated caller with an unknown container name could crash the server. Raise HTTP 404 instead. Also tightens the container-matching predicate from 'x.short_id in cname' (substring match - exploitable for path traversal) to exact equality, as a prerequisite for the path-traversal fix.
The cname parameter was used directly in open(cname + '-docker-compose.yaml')
without sanitization. Because the old container-matching logic used a substring
check ('x.short_id in cname'), an attacker could pass a value like
'abc123/../../etc/cron.d/backdoor' to write files at arbitrary paths.
Fix: validate cname against Docker's allowed character set [a-zA-Z0-9_.-]
before processing, and write output to a secure tempfile rather than a
user-controlled path.
…gger UI Starlette's CORSMiddleware reflects the requesting Origin verbatim when allow_credentials=True is combined with allow_origins=["*"], allowing any cross-origin page to make credentialed requests and read API responses (including container secrets). The app serves its own frontend from the same origin, so no CORS middleware is needed at all. Also disables the FastAPI Swagger UI and ReDoc endpoints (docs_url=None, redoc_url=None) which provided an unauthenticated attack map of all API endpoints, and removes the duplicate FastAPI instantiation.
The endpoint wrote all container data (including env vars with secrets) to a single shared file 'docker-compose.yaml' then read it back. Concurrent requests could read each other's output, disclosing secrets between callers. Files were also never cleaned up, accumulating secrets on disk indefinitely. pyaml.dump() returns the YAML string directly when no stream is given, so no intermediate file is needed.
Adds X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Content-Security-Policy headers to all responses, preventing MIME sniffing, clickjacking, and restricting resource loading to the same origin.
- docker 4.4.4 → 7.1.0: 4 year old SDK with security fixes across 5.x-7.x - pyaml 20.4.0 → 24.4.0: resolves transitive PyYAML pre-CVE-2020-14343 range - Add explicit pins for fastapi, uvicorn, loguru previously inherited from the techblog/fastapi base image without version control - Remove six (no longer required by docker-py 7.x)
- Switch FROM techblog/fastapi:latest (unverified custom image, mutable tag) to python:3.12-slim (official Docker image, verified publisher) - Add non-root user (appuser, uid 10001) to limit blast radius if the application is compromised - Add HEALTHCHECK so orchestrators can detect crashed app state - Set PYTHONDONTWRITEBYTECODE/PYTHONUNBUFFERED/PIP_NO_CACHE_DIR for leaner, more predictable image builds
Without a .dockerignore the full repo (including .env files, .git history, AI assistant context, and documentation) is sent to the Docker daemon as build context. A mistaken 'COPY . .' would silently embed credentials or internal tooling configuration into published images.
- Mount Docker socket read-only (:ro) — app only needs read access; write access allowed container-to-host escape via socket API - Drop all Linux capabilities (cap_drop: ALL) — no capabilities needed - Add no-new-privileges to block setuid escalation - Set read_only filesystem with /tmp tmpfs for writes the app needs - Add healthcheck so orchestrators detect crashed application state - Change restart policy to unless-stopped (avoid restart loops on crash)
Mutable version tags (e.g. @V3) can be silently updated by a supply-chain attacker to run malicious code in CI. Pin all actions to their immutable commit SHAs: actions/checkout 11bd71901bbe5b1630ceea73d27597364c9af683 (v4.2.2) docker/setup-qemu-action 49b3bc8e6bdd4a60e6116a5414239cba5943d3cf (v3.2.0) docker/setup-buildx-action c47758b77c9736f4b2ef4073d4d51994fabfe349 (v3.7.1) docker/login-action 9780b0c442fbb1117ed29e0efdff1e18412f7567 (v3.3.0) docker/build-push-action 4f58ea79222b3b9dc2c8bbdd6debcef730109a75 (v6.9.0) Also replaces the JCR inline shell login (which interpolated the token directly into a shell command, exposing it in debug logs) with docker/login-action, which handles credentials out of the shell context.
Owner
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
There was a problem hiding this comment.
Pull request overview
This PR applies a multi-layer security hardening pass across the DeCompose FastAPI app, its containerization, and CI workflows, addressing reported vulnerabilities (DoS, path traversal, CORS misconfig, supply-chain pinning, and container runtime restrictions).
Changes:
- Hardens FastAPI endpoints and defaults: safer container lookup + 404s, removal of permissive CORS, disables Swagger UI/ReDoc, adds security response headers, avoids shared compose-file I/O.
- Strengthens container security posture: switches to
python:3.12-slim, runs as non-root, adds healthchecks, and tightens docker-compose runtime settings (read-only FS, drop caps, no-new-privileges, read-only docker.sock). - Hardens CI supply chain: pins GitHub Actions to immutable SHAs and avoids token interpolation in shell for registry login.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Dockerfile | Switch to official Python base, install pinned deps, run as non-root, add healthcheck |
| docker-compose.yaml | Harden runtime settings (RO socket, cap drop, RO FS, tmpfs, healthcheck) |
| decompose/requirements.txt | Pin runtime deps (but currently missing jinja2; see comments) |
| decompose/decompose.py | Fix DoS behavior, remove risky CORS, add security headers, safer download handling |
| .github/workflows/main.yml | Pin actions by SHA; modernize outputs; use login-action |
| .github/workflows/docker-image.yml | Pin actions by SHA; use login-action + build-push-action |
| .dockerignore | Reduce build context; exclude sensitive/unneeded files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+177
to
+180
| with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as tmp: | ||
| pyaml.dump(data, tmp) | ||
| tmp_path = tmp.name | ||
| return FileResponse(tmp_path, media_type='application/octet-stream', filename=filename) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full security audit and remediation of the DeCompose application.
10 vulnerabilities fixed across 4 layers (application, dependencies,
container, CI/CD), each in an isolated commit.
Fixes
2514e2dsys.exit(1)in request handler terminates the uvicorn process on any unknown container name — unauthenticated one-request DoSHTTPException(404)instead; tighten container-matching69b1dbf/api/download—cnameused directly as a filename; old substring matchx.short_id in cnameallowedabc123/../../etc/backdoorto bypass validationcname[a-zA-Z0-9_.-]; write output toNamedTemporaryFilee4da7ddallow_origins=["*"]+allow_credentials=Truecauses Starlette to reflect the requestingOriginverbatim, allowing any cross-origin page to make credentialed requests and read full APIdocs_url=None, redoc_url=None)20884a1docker-compose.yaml— concurrent requests could read each other's output, leaking container secrets across callers; secret-containing files accumulated on diskpyaml.dump(data)string directly — no file I/O needed0b603ddX-Frame-Options,X-Content-Type-Options,Referrer-Policy,Content-Security-PolicySecurityHeadersMiddlewaresetting all four headers onc41144bdocker==4.4.4(4 years old) andpyaml==20.4.0(locks transitive PyYAML to pre-CVE-2020-14343 range);fastapi/uvicorn/loguruinherited unversioned from base imagedocker==7.1.0,pyaml==24.4.0; pin all runtime deps explicitlyaa22196techblog/fastapi:latest— unverified custom image on a mutable tag (supply-chain risk); container ran as root (uid 0)python:3.12-slim; addappuser(uid 10001)USERdirective; addHEALTHCHECK1a9adc9.dockerignore— full repo sent to Docker daemon as build context;.envfiles or credentials present at repo root would be silently embedded in published images.dockerignore.git,.env*,CLAUDE.md,.claude/, docs, screenshots07d108eno-new-privileges; no healthcheck — combined with running as root, any RCE in the app yields full host:roto socket mount;cap_drop: ALL;security_opt: [no-new-privileges:true];read_only: truewith/tmptmpfs;healthcheck8158de0@v3,@v4) — supply-chain compromise could silently execute malicious code in CI; JCR login interpolated token directly into a shell command, exposingdocker loginshell withdocker/login-actionTest plan
GET /api/generate?cname=doesnotexistreturns HTTP 404, server stays upGET /api/download?cname=abc123/../../../etc/passwdreturns HTTP 400fetchto/api/containersis rejected by the browser/docsand/redocreturn HTTP 404X-Frame-Options: DENYandContent-Security-Policypython:3.12-slimdocker exec decompose idshows a non-root user (uid 10001):roand the container starts and passes healthcheck