# Security policy and rotation checklist This document describes how secrets flow through the SolaceScan explorer and the operator steps required to rotate credentials that were previously checked into this repository. ## Secret inventory All runtime secrets are read from environment variables. Nothing sensitive is committed to the repo. | Variable | Used by | Notes | |---|---|---| | `JWT_SECRET` | `backend/api/rest/server.go` | HS256 signing key. Must be ≥32 bytes. Required when `APP_ENV=production` or `GO_ENV=production`. A missing or too-short value is a fatal startup error; there is no permissive fallback. | | `CSP_HEADER` | `backend/api/rest/server.go` | Full Content-Security-Policy string. Required in production. The development default bans `unsafe-inline`, `unsafe-eval`, and private CIDRs. | | `DB_PASSWORD` | deployment scripts (`EXECUTE_DEPLOYMENT.sh`, `EXECUTE_NOW.sh`) and the API | Postgres password for the `explorer` role. | | `SSH_PASSWORD` | `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` | SSH password used to reach the Besu VMs. Scripts fail fast if unset. | | `NEW_PASSWORD` | `scripts/set-vmid-password.sh`, `scripts/set-vmid-password-correct.sh` | Password being set on a Proxmox VM. Fail-fast required. | | `CORS_ALLOWED_ORIGIN` | `backend/api/rest/server.go` | Optional. When set, restricts `Access-Control-Allow-Origin`. Defaults to `*` — do not rely on that in production. | | `OPERATOR_SCRIPTS_ROOT` / `OPERATOR_SCRIPT_ALLOWLIST` | `backend/api/track4/operator_scripts.go` | Required to enable the Track-4 run-script endpoint. | | `OPERATOR_SCRIPT_TIMEOUT_SEC` | as above | Optional cap (1–599 seconds). | ## Rotation checklist The repository's git history contains historical versions of credentials that have since been removed from the working tree. Treat those credentials as compromised. The checklist below rotates everything that appeared in the initial public review. > **This repository does not rotate credentials on its own. The checklist > below is the operator's responsibility.** Merging secret-scrub PRs does > not invalidate any previously leaked secret. 1. **Rotate the Postgres `explorer` role password.** - Generate a new random password (`openssl rand -base64 24`). - `ALTER USER explorer WITH PASSWORD '';` - Update the new password in the deployment secret store (Docker swarm secret / Kubernetes secret / `.env.secrets` on the host). - Restart the API and indexer services so they pick up the new value. 2. **Rotate the Proxmox / Besu VM SSH password.** - `sudo passwd besu` (or equivalent) on each affected VM. - Or, preferred: disable password auth entirely and move to SSH keys (`PasswordAuthentication no` in `/etc/ssh/sshd_config`). 3. **Rotate `JWT_SECRET`.** - Generate 32+ bytes (`openssl rand -base64 48`). - Deploy the new value to every API replica simultaneously. - Note: rotating invalidates every outstanding wallet auth token. Plan for a short window where users will need to re-sign. - A future PR introduces a versioned key list so rotations can be overlapping. 4. **Rotate any API keys (e.g. xAI / OpenSea) referenced by `backend/api/rest/ai.go` and the frontend.** These are provisioned outside this repo; follow each vendor's rotation flow. 5. **Audit git history.** - Run `gitleaks detect --source . --redact` at HEAD. - Run `gitleaks detect --log-opts="--all"` over the full history. - Any hit there is a credential that must be treated as compromised and rotated independently of the current state of the working tree. - Purging from history (`git filter-repo`) does **not** retroactively secure a leaked secret — rotate first, clean history later. ## History-purge audit trail Following the rotation checklist above, the legacy `L@ker$2010` / `L@kers2010` / `L@ker\$2010` password strings were purged from every branch and tag in this repository using `git filter-repo --replace-text` followed by a `--replace-message` pass for commit message text. The rewritten history was force-pushed with `git push --mirror --force`. Verification post-rewrite: ``` git log --all -p | grep -cE 'L@ker\$2010|L@kers2010|L@ker\\\$2010' 0 gitleaks detect --no-git --source . --config .gitleaks.toml 0 legacy-password findings ``` ### Residual server-side state (not purgable from the client) Gitea's `refs/pull/*/head` refs (the read-only mirror of each PR's original head commit) **cannot be force-updated over HTTPS** — the server's `update` hook declines them. After a history rewrite the following cleanup must be performed **on the Gitea host** by an administrator: 1. Run `gitea admin repo-sync-release-archive` and `gitea doctor --run all --fix` if available. 2. Or manually, as the gitea user on the server: ```bash cd /var/lib/gitea/data/gitea-repositories/d-bis/explorer-monorepo.git git for-each-ref --format='%(refname)' 'refs/pull/*/head' | \ xargs -n1 git update-ref -d git gc --prune=now --aggressive ``` 3. Restart Gitea. Until this server-side cleanup is performed, the 13 `refs/pull/*/head` refs still pin the pre-rewrite commits containing the legacy password. This does not affect branches, the default clone, or `master` — but the old commits remain reachable by SHA through the Gitea web UI (e.g. on the merged PR's **Files Changed** tab). ### Re-introduction guard The `.gitleaks.toml` rule `explorer-legacy-db-password-L@ker` was tightened from `L@kers?\$?2010` to `L@kers?\\?\$?2010` so it also catches the shell-escaped form that slipped past the original PR #3 scrub (see commit `78e1ff5`). Future attempts to paste any variant of the legacy password — in source, shell scripts, or env files — will fail the `gitleaks` CI job wired in PR #5. ## Build-time / CI checks (wired in PR #5) - `gitleaks` pre-commit + CI gate on every PR. - `govulncheck`, `staticcheck`, and `go vet -vet=all` on the backend. - `eslint` and `tsc --noEmit` on the frontend. ## Reporting a vulnerability Do not open public issues for security reports. Email the maintainers listed in `CONTRIBUTING.md`.