cleanup phases 6

This commit is contained in:
hamid
2026-08-02 18:48:32 +03:30
parent e2db97392a
commit 51e86a1e5f
239 changed files with 118 additions and 70 deletions
@@ -0,0 +1,90 @@
# 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 04) ·
> **Unlocks:** any real/shared-environment deployment
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../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](../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):
1. **Committed `sa` connection string** — a real remote SQL Server (public IP `87.107.152.16`, plaintext
password) in `appsettings.json` **and** `appsettings.Development.json` (byte-identical), for both the app DB
and the log DB. Anyone with repo access owns the database. Violates root `CLAUDE.md` agreement #6.
2. **Placeholder JWE signing/encryption + PII field-encryption keys** (`local-dev-…-change-me`), a ~7-day
access-token lifetime (`ExpirationMinutes: 10000`), `RequireHttpsMetadata = false`, and stub
`Issuer`/`Audience` (`MyWebsite`).
3. **Seeded `admin` / `qw123321`** full-admin user on every non-Testing boot.
4. **Auto-seeded active sandbox ZarinPal gateway** on every boot (a production DB would silently hold an active
sandbox money gateway).
5. **Kestrel HTTP/2-only default** (breaks non-TLS HTTP/1.1 hops).
6. **Rate limiter not proxy-aware** (`RemoteIpAddress`, no `ForwardedHeaders`) → 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](../server/post-phase-backend-plan.md)** § post-phase-1 (items
1.11.6) — the authoritative, file/line-cited spec.
- **[../server/runtime-services.md](../server/runtime-services.md)** § 13 (SQL Server + reverse-proxy notes).
- [Phase 0](refinement-phase-0-bring-up.md) — 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.11.6
- **5.1 (do first, today) — rotate & remove the committed `sa` connection 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 = true` outside Development;
real `Issuer`/`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** (`ForwardedHeaders` trusting 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; `RequireHttpsMetadata` gated 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 test` green.
## 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`/`qw123321` and 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 committed `sa` string was rotated and externalized.