From b924e590485a9e34826f55fdc35b4a85d7c08a92 Mon Sep 17 00:00:00 2001 From: Krishna Suravarapu <36037520+KrishnaSuravarapu@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:51:44 +0530 Subject: [PATCH 1/3] security(hardening): CSP + headers, secrets untrack, DB/redis loopback, non-root app, OAuth log redaction, image provenance Addresses the in-repo legs of the open attack-chain tickets in one change (rate-limiting excluded - django-axes already enforces login lockout). - C-002: add django-csp CSPMiddleware + CSP policy, X-Frame-Options DENY, nosniff, referrer-policy (settings.py, requirements.txt) - C-004: untrack secrets/*.env (add .sample templates + .gitignore); bind MySQL and Redis to 127.0.0.1 so the DB is not network-reachable - C-001/C-003: app user gets its own group (not gid 0) and log file is no longer world-writable (Dockerfile) - C-005: attach SLSA provenance + SBOM to the pushed image (actions already SHA-pinned) - C-008: drop social_core.pipeline.debug.debug (logs OAuth tokens/PII) and lower app log level DEBUG -> INFO Not in this PR (cannot be code): rotating the previously-committed secrets (ops runbook), production bind-mount removal (deploy manifests repo), the F-022 self-approval IDOR decision, and a password-history validator. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/docker-push.yml | 4 +++ .gitignore | 4 +++ Dockerfile | 11 +++++-- EnigmaAutomation/settings.py | 32 +++++++++++++++++-- docker-compose.yml | 7 ++-- requirements.txt | 1 + secrets/ops_app_celery.env | 5 --- secrets/ops_app_celery.env.sample | 5 +++ secrets/ops_app_dev.env | 2 -- secrets/ops_app_dev.env.sample | 2 ++ secrets/ops_app_test.env | 2 -- secrets/ops_app_test.env.sample | 2 ++ ...mysql_dev.env => ops_mysql_dev.env.sample} | 2 +- 13 files changed, 62 insertions(+), 17 deletions(-) delete mode 100644 secrets/ops_app_celery.env create mode 100644 secrets/ops_app_celery.env.sample delete mode 100644 secrets/ops_app_dev.env create mode 100644 secrets/ops_app_dev.env.sample delete mode 100644 secrets/ops_app_test.env create mode 100644 secrets/ops_app_test.env.sample rename secrets/{ops_mysql_dev.env => ops_mysql_dev.env.sample} (54%) diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 9035b9a8..d7dfb864 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -41,3 +41,7 @@ jobs: target: web tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + # Attach SLSA build provenance + SBOM so a swapped/backdoored image can be + # detected against the attestation at deploy time (chain C-005). + provenance: true + sbom: true diff --git a/.gitignore b/.gitignore index 69e3b55b..e96a0d10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ config.json + +# Real secrets must never be committed (chain C-004) — only *.sample templates. +secrets/*.env + __pycache__/ *.py[cod] db.sqlite3 diff --git a/Dockerfile b/Dockerfile index 33154505..9b770227 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,10 +16,15 @@ RUN DEBIAN_FRONTEND=noninteractive \ # Set env variables used in this Dockerfile (add a unique prefix, such as DEV) RUN apt update && apt install -y netcat dnsutils libmariadb-dev -RUN mkdir -p /ebs/logs && touch /ebs/logs/engima.log && chmod 777 /ebs/logs/engima.log - ARG APPUID=1001 -RUN useradd -rm -d /home/app -s /bin/bash -g root -u "$APPUID" app +# Own group for the app user (not gid 0 / root group) so a container escape +# does not inherit root-group file access (chains C-001 / C-003). +RUN groupadd -g "$APPUID" app \ + && useradd -rm -d /home/app -s /bin/bash -g app -u "$APPUID" app + +# Log file owned by the app user, not world-writable (was chmod 777). +RUN mkdir -p /ebs/logs && touch /ebs/logs/engima.log \ + && chown -R app:app /ebs/logs && chmod 640 /ebs/logs/engima.log WORKDIR /srv/code/dev RUN mkdir -p logs RUN mkdir -p db diff --git a/EnigmaAutomation/settings.py b/EnigmaAutomation/settings.py index e89aa40b..73a34ba2 100644 --- a/EnigmaAutomation/settings.py +++ b/EnigmaAutomation/settings.py @@ -49,6 +49,30 @@ CSRF_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True +# --- Response security headers (chain C-002 / F-035) --- +# Sent by django.middleware.security.SecurityMiddleware + clickjacking middleware. +SECURE_CONTENT_TYPE_NOSNIFF = True +SECURE_REFERRER_POLICY = "same-origin" +X_FRAME_OPTIONS = "DENY" + +# --- Content-Security-Policy (django-csp / csp.middleware.CSPMiddleware) --- +# NOTE: 'unsafe-inline' is retained for script/style because the ops UI still +# ships inline