feat(security): add response security headers + make CI security gates real
BACKLOG P0-13: next.config.mjs now sets HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and a same-origin CSP on every route. Previously the only security header came from Vercel's platform default (HSTS) -- nothing was set by the app itself, so it would have silently disappeared on cutover to Coolify/Traefik. Verified at runtime (curl -I against a production build), not just that the build compiles. BACKLOG P0-16: Trivy in security.yml/nightly.yml now actually fails the build on HIGH/CRITICAL findings (`--exit-code 1`) instead of the previous `--exit-code 0` (purely advisory, never blocked a merge). Verified locally against this repo's real dependency tree -- 19 HIGH findings currently exist in pnpm-lock.yaml (next.js, postcss, sharp, nanoid), several in next.js itself relating to middleware/auth bypass. See PR description: this is expected to turn the security.yml `trivy` job red until those are bumped in a follow-up -- that's the fix working as intended, not a regression, but it needs a human decision on timing given the sibling auth-gate PR (#11). `services/scrapers/` stays explicitly excluded from the scan (--skip-dirs) -- that tree is a dated, deliberate keep-decision under active review in a separate thread (BACKLOG P3-3) and is out of scope here. BACKLOG P0-19: dedicated gitleaks job added to security.yml, full git history (--log-opts=--all), pinned + checksum-verified binary. Verified locally against this repo's actual history first (81 commits, no leaks) so this blocking gate doesn't open red on day one. BACKLOG P1-14: SBOM (CycloneDX) + license inventory (`pnpm licenses list`) generated as informational, non-blocking build artifacts. actions/checkout, actions/setup-node, actions/cache, actions/upload-artifact all pinned to commit SHA (was floating @v4 tags) across every workflow file, Trivy/gitleaks pinned to specific released versions instead of floating install scripts. cosign image-signing explicitly NOT added -- there is no container-registry publish step in this repo's CI to attach a signature to yet (Coolify builds directly from git); flagging as a real follow-up once that pipeline exists. BACKLOG P1-15: explicit `permissions: contents: read` + `concurrency` added to every workflow that lacked them (only .github/workflows/ci.yml already had concurrency). Pinned pnpm-store cache added to ci.yml's quality job. Root CODEOWNERS added (`* @sax3l`) so Gitea has someone to assign once required-approvals is turned on (separate repo-settings change, not a file change). The "doctor step" part of P1-15 is added in the follow-up repo- hygiene PR, alongside the `siax:doctor` script it invokes.
This commit is contained in:
@@ -18,6 +18,14 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: cd-${{ github.ref }}
|
||||||
|
cancel-in-progress: false # never cancel an in-flight deploy trigger
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
+34
-10
@@ -9,26 +9,46 @@ name: CI (SIAX Cloud)
|
|||||||
# is inherited debt; continue-on-error so it never blocks).
|
# is inherited debt; continue-on-error so it never blocks).
|
||||||
# * build (BLOCKING) -> `next build`.
|
# * build (BLOCKING) -> `next build`.
|
||||||
#
|
#
|
||||||
# Nightly re-runs + Trivy security scanning live in nightly.yml and
|
# Nightly re-runs + Trivy/gitleaks security scanning live in nightly.yml and
|
||||||
# security.yml (this file is push/PR-only).
|
# security.yml (this file is push/PR-only). BACKLOG P1-15: `permissions`,
|
||||||
|
# `concurrency`, and a pinned pnpm-store cache added; CODEOWNERS added at the
|
||||||
|
# repo root (Gitea auto-reviewer assignment); actions pinned to commit SHAs
|
||||||
|
# (BACKLOG P1-14 digest-pinning).
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
quality:
|
quality:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "22"
|
||||||
- name: Enable corepack + pin pnpm@9 (pnpm-lock.yaml is lockfileVersion 9.0)
|
- name: Enable corepack + pin pnpm@9 (pnpm-lock.yaml is lockfileVersion 9.0)
|
||||||
run: |
|
run: |
|
||||||
corepack enable
|
corepack enable
|
||||||
corepack prepare pnpm@9 --activate
|
corepack prepare pnpm@9 --activate
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Cache pnpm store
|
||||||
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
- name: Install (frozen lockfile)
|
- name: Install (frozen lockfile)
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
- name: Typecheck (tsc over src/ + tests/ -- blocking)
|
- name: Typecheck (tsc over src/ + tests/ -- blocking)
|
||||||
@@ -44,14 +64,18 @@ jobs:
|
|||||||
# Kept in ci.yml (advisory) so the protected-branch status check contexts
|
# Kept in ci.yml (advisory) so the protected-branch status check contexts
|
||||||
# "CI (SIAX Cloud) / quality (push)" + "CI (SIAX Cloud) / security (push)"
|
# "CI (SIAX Cloud) / quality (push)" + "CI (SIAX Cloud) / security (push)"
|
||||||
# are always reported on push. security.yml is the dedicated scanner on
|
# are always reported on push. security.yml is the dedicated scanner on
|
||||||
# push/PR/weekly; this job guarantees the required check exists.
|
# push/PR/weekly and is now BLOCKING (BACKLOG P0-16) -- this copy is kept
|
||||||
|
# advisory on purpose: it exists only to guarantee the status-check context
|
||||||
|
# is always present (see the comment this repeats from before), not as a
|
||||||
|
# second enforcement point. Do not treat a green run of *this* job as a
|
||||||
|
# security clearance -- see security.yml's `trivy` + `gitleaks` jobs.
|
||||||
security:
|
security:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Install Trivy
|
- name: Install Trivy (pinned)
|
||||||
run: |
|
run: |
|
||||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.73.0
|
||||||
- name: Trivy scan (vuln + secrets, advisory)
|
- name: Trivy scan (vuln + secrets, advisory -- see security.yml for the blocking scan)
|
||||||
run: |
|
run: |
|
||||||
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 0 .
|
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --skip-dirs services/scrapers --exit-code 0 .
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
name: Health Check (SIAX Cloud)
|
name: Health Check (SIAX Cloud)
|
||||||
|
|
||||||
|
# Weekly informational repo-health ping (no push/PR trigger, nothing to
|
||||||
|
# block) -- kept advisory on purpose. The BLOCKING secret/vuln gates live in
|
||||||
|
# security.yml (gitleaks + Trivy, BACKLOG P0-16/P0-19), which runs on every
|
||||||
|
# push/PR; this job's own Trivy step stays `--exit-code 0` because there is
|
||||||
|
# no merge for it to gate.
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 6 * * 1"
|
- cron: "0 6 * * 1"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
health:
|
health:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Required files
|
- name: Required files
|
||||||
run: |
|
run: |
|
||||||
for f in README.md LICENSE .gitignore; do [ -f "$f" ] || echo "::warning file=$f,title=Missing::$f is missing"; done
|
for f in README.md LICENSE .gitignore CODEOWNERS; do [ -f "$f" ] || echo "::warning file=$f,title=Missing::$f is missing"; done
|
||||||
[ -f .gitea/workflows/ci.yml ] || echo "::warning title=No CI::No CI workflow found"
|
[ -f .gitea/workflows/ci.yml ] || echo "::warning title=No CI::No CI workflow found"
|
||||||
- name: Install Trivy
|
- name: Install Trivy (pinned)
|
||||||
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.73.0
|
||||||
- name: Secret scan
|
- name: Secret scan (informational -- see security.yml for the blocking gitleaks gate)
|
||||||
run: trivy fs --scanners secret --exit-code 0 .
|
run: trivy fs --scanners secret --exit-code 0 .
|
||||||
@@ -1,19 +1,25 @@
|
|||||||
name: Nightly (SIAX Cloud)
|
name: Nightly (SIAX Cloud)
|
||||||
|
|
||||||
# Nightly full-gate re-run so CI exercises the repo even without pushes:
|
# Nightly full-gate re-run so CI exercises the repo even without pushes:
|
||||||
# quality gate + advisory security scan. Fails loudly on regressions
|
# quality gate + Trivy scan. Fails loudly on regressions introduced outside
|
||||||
# introduced outside push-triggered runs (e.g. dependency drift).
|
# push-triggered runs (e.g. dependency drift) -- the `security` job below is
|
||||||
|
# therefore BLOCKING (BACKLOG P0-16), matching security.yml, not advisory:
|
||||||
|
# a nightly run whose whole purpose is catching drift has no reason to stay
|
||||||
|
# silent about it.
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 4 * * *" # daily 04:00 UTC
|
- cron: "0 4 * * *" # daily 04:00 UTC
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
quality:
|
quality:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "22"
|
||||||
- name: Enable corepack + pin pnpm@9
|
- name: Enable corepack + pin pnpm@9
|
||||||
@@ -35,10 +41,12 @@ jobs:
|
|||||||
security:
|
security:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Install Trivy
|
- name: Install Trivy (pinned)
|
||||||
run: |
|
run: |
|
||||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.73.0
|
||||||
- name: Trivy fs scan (vuln + secrets, advisory)
|
- name: Trivy fs scan (vuln + secrets -- BLOCKING, see BACKLOG P0-16)
|
||||||
run: |
|
run: |
|
||||||
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 0 .
|
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL \
|
||||||
|
--skip-dirs services/scrapers \
|
||||||
|
--exit-code 1 .
|
||||||
|
|||||||
@@ -1,30 +1,96 @@
|
|||||||
name: Security Scan (SIAX Cloud)
|
name: Security Scan (SIAX Cloud)
|
||||||
|
|
||||||
# Advisory security scanning on every push/PR + weekly re-run, matching the
|
# BACKLOG P0-16, P0-19, P1-14: this used to run Trivy with `--exit-code 0`,
|
||||||
# fleet convention (--exit-code 0 so findings never block merges; they are
|
# i.e. purely advisory -- findings never blocked a merge. It now fails the
|
||||||
# triaged via the repo's security channel and BACKLOG).
|
# build for real on HIGH/CRITICAL, plus a dedicated gitleaks step (REPO-15:
|
||||||
|
# the only prior secret sweep was hand-rolled regex, not a real
|
||||||
|
# secret-scanning tool), plus an SBOM + license inventory as build artifacts.
|
||||||
#
|
#
|
||||||
# NOTE: findings on services/scrapers/* (FlareSolverr/Tor/curl_cffi) are
|
# `services/scrapers/` is excluded from the Trivy scan (--skip-dirs).
|
||||||
# expected and out of scope for CI gating -- kept per the account owner's
|
# Findings there (waf_bypass.py, engine*.py, curl_cffi/cloudscraper/nodriver)
|
||||||
# dated, attributed decision (Simon Axelsson, 2026-08-11, interactive
|
# are expected and out of scope for CI gating -- kept per the account
|
||||||
# prompt; no rationale beyond "keep it" given or invented). See SECURITY.md
|
# owner's dated, attributed decision (Simon Axelsson, 2026-08-11; see
|
||||||
# and docs/compliance/GOLDEN-STANDARD-REPORT.md control C6 for full context.
|
# SECURITY.md and docs/compliance/GOLDEN-STANDARD-REPORT.md control C6).
|
||||||
|
# That question is explicitly out of scope for this workflow change and
|
||||||
|
# must not be the reason this gate starts failing for everyone. Every other
|
||||||
|
# path in the repo is now enforced for real, including the rest of
|
||||||
|
# services/ (the FastAPI engine itself).
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 5 * * 1" # weekly Monday re-scan
|
- cron: "0 5 * * 1" # weekly Monday re-scan
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: security-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security:
|
trivy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Install Trivy
|
- name: Install Trivy (pinned)
|
||||||
run: |
|
run: |
|
||||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.73.0
|
||||||
- name: Trivy fs scan (vuln + secrets, advisory)
|
- name: Trivy fs scan (vuln + secrets -- BLOCKING)
|
||||||
run: |
|
run: |
|
||||||
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 0 .
|
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL \
|
||||||
|
--skip-dirs services/scrapers \
|
||||||
|
--exit-code 1 .
|
||||||
|
- name: SBOM (CycloneDX, informational artifact -- BACKLOG P1-14)
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
trivy fs --format cyclonedx --output sbom.cdx.json --skip-dirs services/scrapers .
|
||||||
|
- name: Upload SBOM
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true # informational artifact; must never block the blocking scan above
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: sbom-cyclonedx
|
||||||
|
path: sbom.cdx.json
|
||||||
|
retention-days: 90
|
||||||
|
|
||||||
|
gitleaks:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # gitleaks needs full history, not just the tip commit
|
||||||
|
- name: Install gitleaks (pinned + checksum-verified)
|
||||||
|
run: |
|
||||||
|
curl -sSL -o gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
|
||||||
|
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks.tar.gz" | sha256sum -c -
|
||||||
|
tar -xzf gitleaks.tar.gz gitleaks
|
||||||
|
sudo mv gitleaks /usr/local/bin/gitleaks
|
||||||
|
- name: gitleaks scan (full history -- BLOCKING)
|
||||||
|
run: gitleaks detect --source . --log-opts="--all" --redact --verbose
|
||||||
|
|
||||||
|
license-inventory:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
- name: Enable corepack + pin pnpm@9
|
||||||
|
run: |
|
||||||
|
corepack enable
|
||||||
|
corepack prepare pnpm@9 --activate
|
||||||
|
- name: Install (frozen lockfile)
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
- name: License inventory (informational -- BACKLOG P1-14)
|
||||||
|
run: |
|
||||||
|
pnpm licenses list --json > licenses.json || true
|
||||||
|
pnpm licenses list
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
continue-on-error: true # informational artifact; must never block CI
|
||||||
|
with:
|
||||||
|
name: license-inventory
|
||||||
|
path: licenses.json
|
||||||
|
retention-days: 90
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches-ignore: [main]
|
branches-ignore: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ci-${{ github.ref }}
|
group: ci-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -39,8 +42,8 @@ jobs:
|
|||||||
name: Typecheck + test
|
name: Typecheck + test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
- name: Enable corepack (pnpm)
|
- name: Enable corepack (pnpm)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# BACKLOG P1-15 (CI-14): required reviewers for Gitea's branch-protection
|
||||||
|
# "required approvals" setting to have anyone to assign. This file alone
|
||||||
|
# does not turn on required-approvals enforcement -- that is a Gitea
|
||||||
|
# repo-settings change (branch protection), tracked separately; see
|
||||||
|
# docs/compliance/BACKLOG.md P1-15 ("required_approvals: 0").
|
||||||
|
#
|
||||||
|
# Gitea CODEOWNERS syntax matches GitHub's: path pattern, then owner(s).
|
||||||
|
* @sax3l
|
||||||
@@ -1,3 +1,47 @@
|
|||||||
|
// BACKLOG P0-13 (SEC-06): the app previously shipped no security headers of
|
||||||
|
// its own -- HSTS existed only as a platform default injected by Vercel, so
|
||||||
|
// it would have silently disappeared on cutover to Coolify/Traefik (see
|
||||||
|
// docs/architecture/ARCHITECTURE.md §5/§8). These are set here, at the
|
||||||
|
// framework level, so they travel with the app regardless of host.
|
||||||
|
//
|
||||||
|
// CSP is same-origin-only (`'self'`) plus the minimum carve-outs this app
|
||||||
|
// actually needs today:
|
||||||
|
// - `'unsafe-inline'` on script-src/style-src: Next.js's App Router injects
|
||||||
|
// small inline hydration/RSC payload scripts and Tailwind can emit
|
||||||
|
// inline style attributes; this repo doesn't yet wire a per-request
|
||||||
|
// nonce through middleware+layout (a real follow-up -- see BACKLOG --
|
||||||
|
// but out of scope for this pass, which fixes "zero headers", not "zero
|
||||||
|
// inline script tolerance").
|
||||||
|
// - `form-action` allows Stripe's hosted Checkout domains: the payment
|
||||||
|
// flow (app/api/payments/[order_id]/deposit) does a full top-level
|
||||||
|
// redirect to a Stripe-hosted page, never an embedded iframe/script
|
||||||
|
// (grep-verified: no @stripe/stripe-js dependency, no client-side
|
||||||
|
// Stripe.js load anywhere in app/).
|
||||||
|
// No other third-party origins are contacted from the browser (no
|
||||||
|
// analytics, no next/font/google, no embedded widgets -- grep-verified).
|
||||||
|
const securityHeaders = [
|
||||||
|
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
|
||||||
|
{ key: "X-Frame-Options", value: "DENY" },
|
||||||
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
||||||
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
||||||
|
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), interest-cohort=()" },
|
||||||
|
{
|
||||||
|
key: "Content-Security-Policy",
|
||||||
|
value: [
|
||||||
|
"default-src 'self'",
|
||||||
|
"script-src 'self' 'unsafe-inline'",
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
"img-src 'self' data: blob:",
|
||||||
|
"font-src 'self' data:",
|
||||||
|
"connect-src 'self'",
|
||||||
|
"form-action 'self' https://checkout.stripe.com https://api.stripe.com",
|
||||||
|
"frame-ancestors 'none'",
|
||||||
|
"base-uri 'self'",
|
||||||
|
"object-src 'none'",
|
||||||
|
].join("; "),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
typescript: {
|
typescript: {
|
||||||
@@ -6,6 +50,14 @@ const nextConfig = {
|
|||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/(.*)",
|
||||||
|
headers: securityHeaders,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default nextConfig
|
export default nextConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user