5.5 KiB
Refinement Phase 5 — Security & config hygiene (deployment blockers)
Mission: remove the committed secrets and dev-grade defaults that block any non-local deployment. None of this changes behavior, but two items are live credential leaks sitting in git today. Do the credential rotation (5.1) now, independently of everything else.
Track: backend (config/security) · Depends on: nothing (can run in parallel with Phases 0–4) · Unlocks: any real/shared-environment deployment Before you start, read ../../phases/_shared/agent-operating-rules.md.
1. Context
This phase is the already-written server audit's post-phase-1. It is reproduced here as a refinement phase for sequencing; the full evidence (file/line for every item) and the exact fixes live in ../server/post-phase-backend-plan.md § post-phase-1. Read that section — it is the spec. This file only orders and frames it.
The findings, in short (all verified in code, dated 2026-07-10):
- Committed
saconnection string — a real remote SQL Server (public IP87.107.152.16, plaintext password) inappsettings.jsonandappsettings.Development.json(byte-identical), for both the app DB and the log DB. Anyone with repo access owns the database. Violates rootCLAUDE.mdagreement #6. - Placeholder JWE signing/encryption + PII field-encryption keys (
local-dev-…-change-me), a ~7-day access-token lifetime (ExpirationMinutes: 10000),RequireHttpsMetadata = false, and stubIssuer/Audience(MyWebsite). - Seeded
admin/qw123321full-admin user on every non-Testing boot. - Auto-seeded active sandbox ZarinPal gateway on every boot (a production DB would silently hold an active sandbox money gateway).
- Kestrel HTTP/2-only default (breaks non-TLS HTTP/1.1 hops).
- Rate limiter not proxy-aware (
RemoteIpAddress, noForwardedHeaders) → behind a proxy every client shares one bucket; plus the two payment webhooks use mismatched rate policies.
2. Required reading
- ../server/post-phase-backend-plan.md § post-phase-1 (items 1.1–1.6) — the authoritative, file/line-cited spec.
- ../server/runtime-services.md § 1–3 (SQL Server + reverse-proxy notes).
- Phase 0 — it already introduced a secret-free local DB default; this phase finishes the rotation and the deployed-environment story.
3. Scope — deliver plan items 1.1–1.6
- 5.1 (do first, today) — rotate & remove the committed
saconnection strings. Assume compromised: rotate the password, create a least-privilege app login, move both connection strings to user-secrets (dev) / env vars (deploy), commit only a placeholder, and add a secret-scanning pre-commit hook. Consider history scrubbing (git filter-repo). - 5.2 — replace the placeholder JWE + field-encryption keys with per-environment secrets; set a sane
access-token lifetime (≤ 60 min; refresh already exists);
RequireHttpsMetadata = trueoutside Development; realIssuer/Audience. (Rotating the field key needs a re-encryption migration — do it before real PII exists.) - 5.3 — environment-gate the seeded admin (read bootstrap creds from config; Development-only; force a password change on first login).
- 5.4 — environment-gate the sandbox gateway seed (Development/Testing only, or seed
is_active = false). - 5.5 — fix the Kestrel HTTP/2-only default (
Http1AndHttp2; give gRPC its own endpoint if kept). - 5.6 — make rate limiting proxy-aware (
ForwardedHeaderstrusting the known proxy; partition on the resolved client IP) and pick one deliberate webhook rate policy.
4. Mocks & seams
None. Config/security only.
5. Critical rules
- 5.1 is urgent and independent — it doesn't wait for this phase's slot; the credential is leaking now.
- Rotating the field-encryption key invalidates existing dev-DB ciphertext — acceptable pre-launch, but do it before real PII exists and plan the re-encryption migration if any real data exists.
- Don't break local dev: Development keeps working defaults (via user-secrets/env), only the committed values become placeholders.
6. Definition of Done
- No working secret remains in any committed file (connection strings, JWE keys, field keys, admin password, gateway creds) — verified by a secret scan.
- Access-token lifetime sane;
RequireHttpsMetadatagated by environment; real issuer/audience. - Admin + sandbox-gateway seeds are Development-gated (or config-driven).
- Kestrel default is
Http1AndHttp2; rate limiter honors forwarded headers; webhooks use one deliberate policy.dotnet build/dotnet testgreen.
7. How to test
- Fresh clone with no user-secrets → the app fails fast with a clear "missing connection string" error (not a silent connect to a leaked remote). With user-secrets/env set → boots normally.
- Confirm no
admin/qw123321and no active sandbox gateway appear in a non-Development boot. - Behind a reverse proxy, distinct client IPs get distinct rate-limit buckets.
8. Hand off & document
- Update
server/CLAUDE.md(startup wiring / identity / rate-limiting) to reflect the config-driven secrets and the forwarded-headers middleware. Record the credential rotation in the security log / handoff. Save a memory note that the committedsastring was rotated and externalized.