fix(security): fail-fast on missing JWT_SECRET, harden CSP, strip hardcoded passwords #3

Merged
nsatoshi merged 1 commits from devin/1776538631-fix-jwt-and-csp-hardening into master 2026-04-18 19:34:31 +00:00
Owner

Summary

PR #3 of the 11-PR completion sequence. Addresses the two highest-severity findings in the repo review:

  1. NewServer silently falling back to a per-process ephemeral JWT secret (or a predictable time-based one on rand.Read error).
  2. A default Content-Security-Policy that shipped 'unsafe-inline', 'unsafe-eval', and the private 192.168.11.x RPC CIDRs to every visitor.

It also removes hardcoded L@kers2010 / L@ker$2010 defaults from 7 helper scripts + 2 top-level EXECUTE_*.sh runners and the README.md / docs/DEPLOYMENT.md operator tables, and introduces docs/SECURITY.md with the rotation checklist.

JWT secret loading

<ref_snippet file="/home/ubuntu/repos/explorer-monorepo/backend/api/rest/server.go" lines="66-92" />

  • JWT_SECRET must be ≥32 bytes; otherwise log.Fatal.
  • JWT_SECRET is required when APP_ENV=production or GO_ENV=production.
  • In non-prod we generate 32 bytes from crypto/rand. A rand.Read failure is now fatal — the previous ephemeral-jwt-secret-<unix_nano> fallback is gone.

CSP hardening

<ref_snippet file="/home/ubuntu/repos/explorer-monorepo/backend/api/rest/server.go" lines="36-49" />

  • defaultDevCSP drops 'unsafe-inline' and 'unsafe-eval'.
  • Drops the private RPC CIDRs.
  • Adds frame-ancestors 'none', base-uri 'self', form-action 'self'.
  • CSP_HEADER is required in production (fatal on startup if unset).

Hardcoded password removal

All 9 occurrences of L@kers2010 / L@ker$2010 that live in tracked files other than the three docs being deleted by PR #2 (START_HERE.md, LETSENCRYPT_CONFIGURATION_GUIDE.md) are gone:

  • scripts/analyze-besu-logs.sh, scripts/check-besu-config.sh, scripts/check-besu-logs-with-password.sh, scripts/check-failed-transaction-details.sh, scripts/enable-besu-debug-api.sh, scripts/set-vmid-password.sh, scripts/set-vmid-password-correct.shSSH_PASSWORD/NEW_PASSWORD now required via env or argv; fail-fast with exit 2 pointing at docs/SECURITY.md.
  • EXECUTE_DEPLOYMENT.sh, EXECUTE_NOW.shDB_PASSWORD (and RPC_URL) are now :? guarded.
  • README.md — hardcoded Database Password: L@ker$2010 replaced with an env-variable reference table.
  • docs/DEPLOYMENT.mdPASSWORD: SSH password (default: L@kers2010) replaced with "required; no default".

New docs/SECURITY.md

  • Full inventory keyed by env variable name ↔ consuming file.
  • Five-step rotation checklist (Postgres role, VM SSH password, JWT_SECRET, vendor API keys, gitleaks history audit).
  • Explicit note that merging scrub PRs does not invalidate previously leaked credentials — rotation is still required.

Tests

<ref_file file="/home/ubuntu/repos/explorer-monorepo/backend/api/rest/server_security_test.go" />

  • TestLoadJWTSecretAcceptsSufficientlyLongValue
  • TestLoadJWTSecretStripsSurroundingWhitespace
  • TestLoadJWTSecretGeneratesEphemeralInDevelopment (asserts the new ephemeral secret does NOT start with the old ephemeral-jwt-secret- prefix)
  • TestIsProductionEnv (7 cases)
  • TestDefaultDevCSPHasNoUnsafeDirectivesOrPrivateCIDRs

Verification

  • go build ./... — clean.
  • go vet ./... — clean.
  • go test ./api/rest/ -run 'LoadJWTSecret|IsProduction|DefaultDevCSP' — passes.
  • Grep L@kers\?2010\|L@ker\$2010 across all *.sh, *.go, *.yml, *.yaml, *.md — only remaining hits are in START_HERE.md and LETSENCRYPT_CONFIGURATION_GUIDE.md, which are deleted by PR #2.

Completion criterion advanced

2. Secrets & config hardened — "JWT_SECRET is fatal on missing/short value in prod; CSP excludes unsafe-* and private CIDRs; no passwords hardcoded in scripts or docs."

Still outstanding: rotation of the leaked credentials (operator-side, tracked in docs/SECURITY.md) and gitleaks CI wiring (PR #5).

## Summary PR #3 of the 11-PR completion sequence. Addresses the two highest-severity findings in the repo review: 1. `NewServer` silently falling back to a per-process ephemeral JWT secret (or a predictable time-based one on `rand.Read` error). 2. A default `Content-Security-Policy` that shipped `'unsafe-inline'`, `'unsafe-eval'`, and the private `192.168.11.x` RPC CIDRs to every visitor. It also removes hardcoded `L@kers2010` / `L@ker$2010` defaults from 7 helper scripts + 2 top-level `EXECUTE_*.sh` runners and the `README.md` / `docs/DEPLOYMENT.md` operator tables, and introduces `docs/SECURITY.md` with the rotation checklist. ## JWT secret loading <ref_snippet file="/home/ubuntu/repos/explorer-monorepo/backend/api/rest/server.go" lines="66-92" /> - `JWT_SECRET` must be ≥32 bytes; otherwise `log.Fatal`. - `JWT_SECRET` is required when `APP_ENV=production` or `GO_ENV=production`. - In non-prod we generate 32 bytes from `crypto/rand`. A `rand.Read` failure is now fatal — the previous `ephemeral-jwt-secret-<unix_nano>` fallback is gone. ## CSP hardening <ref_snippet file="/home/ubuntu/repos/explorer-monorepo/backend/api/rest/server.go" lines="36-49" /> - `defaultDevCSP` drops `'unsafe-inline'` and `'unsafe-eval'`. - Drops the private RPC CIDRs. - Adds `frame-ancestors 'none'`, `base-uri 'self'`, `form-action 'self'`. - `CSP_HEADER` is required in production (fatal on startup if unset). ## Hardcoded password removal All 9 occurrences of `L@kers2010` / `L@ker$2010` that live in tracked files other than the three docs being deleted by PR #2 (`START_HERE.md`, `LETSENCRYPT_CONFIGURATION_GUIDE.md`) are gone: - `scripts/analyze-besu-logs.sh`, `scripts/check-besu-config.sh`, `scripts/check-besu-logs-with-password.sh`, `scripts/check-failed-transaction-details.sh`, `scripts/enable-besu-debug-api.sh`, `scripts/set-vmid-password.sh`, `scripts/set-vmid-password-correct.sh` — `SSH_PASSWORD`/`NEW_PASSWORD` now required via env or argv; fail-fast with `exit 2` pointing at `docs/SECURITY.md`. - `EXECUTE_DEPLOYMENT.sh`, `EXECUTE_NOW.sh` — `DB_PASSWORD` (and `RPC_URL`) are now `:?` guarded. - `README.md` — hardcoded `Database Password: L@ker$2010` replaced with an env-variable reference table. - `docs/DEPLOYMENT.md` — `PASSWORD: SSH password (default: L@kers2010)` replaced with "required; no default". ## New `docs/SECURITY.md` - Full inventory keyed by env variable name ↔ consuming file. - Five-step rotation checklist (Postgres role, VM SSH password, `JWT_SECRET`, vendor API keys, gitleaks history audit). - Explicit note that merging scrub PRs does **not** invalidate previously leaked credentials — rotation is still required. ## Tests <ref_file file="/home/ubuntu/repos/explorer-monorepo/backend/api/rest/server_security_test.go" /> - `TestLoadJWTSecretAcceptsSufficientlyLongValue` - `TestLoadJWTSecretStripsSurroundingWhitespace` - `TestLoadJWTSecretGeneratesEphemeralInDevelopment` (asserts the new ephemeral secret does NOT start with the old `ephemeral-jwt-secret-` prefix) - `TestIsProductionEnv` (7 cases) - `TestDefaultDevCSPHasNoUnsafeDirectivesOrPrivateCIDRs` ## Verification - `go build ./...` — clean. - `go vet ./...` — clean. - `go test ./api/rest/ -run 'LoadJWTSecret|IsProduction|DefaultDevCSP'` — passes. - Grep `L@kers\?2010\|L@ker\$2010` across all `*.sh`, `*.go`, `*.yml`, `*.yaml`, `*.md` — only remaining hits are in `START_HERE.md` and `LETSENCRYPT_CONFIGURATION_GUIDE.md`, which are deleted by [PR #2](https://gitea.d-bis.org/d-bis/explorer-monorepo/pulls/2). ## Completion criterion advanced > **2. Secrets & config hardened** — "JWT_SECRET is fatal on missing/short value in prod; CSP excludes `unsafe-*` and private CIDRs; no passwords hardcoded in scripts or docs." Still outstanding: rotation of the leaked credentials (operator-side, tracked in `docs/SECURITY.md`) and gitleaks CI wiring (PR #5).
nsatoshi added 1 commit 2026-04-18 19:03:04 +00:00
backend/api/rest/server.go:
- NewServer() now delegates to loadJWTSecret(), which:
    - Rejects JWT_SECRET < 32 bytes (log.Fatal).
    - Requires JWT_SECRET when APP_ENV=production or GO_ENV=production.
    - Generates a 32-byte crypto/rand ephemeral secret in dev only.
    - Treats rand.Read failure as fatal (removes the prior time-based
      fallback that was deterministic and forgeable).
- Default Content-Security-Policy rewritten:
    - Drops 'unsafe-inline' and 'unsafe-eval'.
    - Drops private CIDRs (192.168.11.221:854[5|6]).
    - Adds frame-ancestors 'none', base-uri 'self', form-action 'self'.
    - CSP_HEADER is required in production; fatal if unset there.

backend/api/rest/server_security_test.go (new):
- Covers the three loadJWTSecret() paths (valid, whitespace-trimmed,
  ephemeral in dev).
- Covers isProductionEnv() across APP_ENV / GO_ENV combinations.
- Asserts defaultDevCSP contains no unsafe directives or private CIDRs
  and includes the frame-ancestors / base-uri / form-action directives.

scripts/*.sh:
- Removed 'L@kers2010' default value from SSH_PASSWORD / NEW_PASSWORD in
  7 helper scripts. Each script now fails with exit 2 and points to
  docs/SECURITY.md if the password isn't supplied via env or argv.

EXECUTE_DEPLOYMENT.sh, EXECUTE_NOW.sh:
- Replaced hardcoded DB_PASSWORD='L@ker$2010' with a ':?' guard that
  aborts with a clear error if DB_PASSWORD (and, for EXECUTE_DEPLOYMENT,
  RPC_URL) is not exported. Other env vars keep sensible non-secret
  defaults via ${VAR:-default}.

README.md:
- Removed the hardcoded Database Password / RPC URL lines. Replaced with
  an env-variable reference table pointing at docs/SECURITY.md and
  docs/DATABASE_CONNECTION_GUIDE.md.

docs/DEPLOYMENT.md:
- Replaced 'PASSWORD: SSH password (default: L@kers2010)' with a
  required-no-default contract and a link to docs/SECURITY.md.

docs/SECURITY.md (new):
- Full secret inventory keyed to the env variable name and the file that
  consumes it.
- Five-step rotation checklist covering the Postgres role, the Proxmox
  VM SSH password, JWT_SECRET, vendor API keys, and a gitleaks-based
  history audit.
- Explicit note that merging secret-scrub PRs does NOT invalidate
  already-leaked credentials; rotation is the operator's responsibility.

Verification:
- go build ./... + go vet ./... pass clean.
- Targeted tests (LoadJWTSecret*, IsProduction*, DefaultDevCSP*) pass.

Advances completion criterion 2 (Secrets & config hardened). Residual
leakage from START_HERE.md / LETSENCRYPT_CONFIGURATION_GUIDE.md is
handled by PR #2 (doc consolidation), which deletes those files.
nsatoshi merged commit 0a08e159c5 into master 2026-04-18 19:34:31 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: d-bis/explorer-monorepo#3