Exercise 1 — Token bucket
- With separate HGETALL/HSET, under concurrency MORE than 5 get through (the threads read the same stale "tokens" and each decides to allow): the classic check-then-act race (03/10/12: the same lesson in another store).
- With Lua (or django-ratelimit, which uses it), the script is atomic in Redis: exactly 5 get through and 15 get 429. The pattern: move the check+decrement up to the data server as a single operation.
- 429 with
Retry-After(seconds or a date) and an RFC 7807 body (type: rate-limited). The proposed limits: hard login (brute force), moderate checkout (bots), generous-but-present public API.
Exercise 2 — Secrets
- Trufflehog/gitleaks over the history: with 05's cleanup it should come out clean; if something shows up, the repair is rotating (next item) — deleting the commit fixes nothing.
- Dual-rotation runbook: (1) generate the new key at the provider; (2) configure
GATEWAY_SECRETS=[new, old](verification accepts either); (3) deploy; (4) switch signing/redemption usage to the new one; (5) confirm in metrics that nobody uses the old one; (6) remove the old one from the setting and revoke it at the provider. The code that enables it: verification iterates over the list of valid secrets (a single secret = a lock-in of coordinated deployment). - In the orchestrator/KMS (Secrets Manager/Vault): the Docker image is an artifact that gets exported/shared/leaked (public registries,
docker save); a secret baked into an image lives in its layers forever. The runtime environment (or a KMS fetch at boot) keeps the secret out of the artifact.
Exercise 3 — GDPR
- With low volume (<100 reservations per user): direct JSON in the response is correct (14: idempotent). If the history were huge: 202 + job + signed download (01: 202 Accepted). A decision by expected volume, documented.
- Anonymization in
transaction.atomic(): email →f"deleted-{uuid}@invalid", name → "Deleted user", state → DELETED, refresh tokens blacklisted, MFA deleted. Reservations/payments intact (fiscal obligation + audit). - MFA: deleted (a dead account doesn't need it). Tokens: revoked (blacklist). Organizer: if they have live events → block automatic deletion and force transferring events to another organizer (or cancellation): the manual support flow is the honest one; 00b's PROTECT FK already signals it.
Exercise 4 — Audit log
- Append-only in the app: the model exposes no update/delete and (stronger) at the DB:
REVOKE UPDATE, DELETE ON audit_log FROM app_user;— the DB guarantees it even with a code bug. SELECT * FROM audit_log WHERE action = 'role.change' AND created_at >= '2026-03-01' AND created_at < '2026-04-01' ORDER BY created_at;— with actor and before/after in JSONB: the incident's answer in one query (45 adds req_id).
Exercise 5 — Minimization
- Typical in the class project:
date_of_birth"just in case" (out: not needed to sell), an unusedavatar_url(out or justified), full address for electronic tickets (only if you ship physically). Art. 5: adequacy, minimization — every field with its purpose written down. - Proposed retention: expired PENDING → anonymize at 30 days (the seat returned to the pool long ago); reset tokens → 1h, already in the TTL; access logs → 90 days (security) with long-term anonymized sampling. The purge job (31) queries by date and anonymizes in transactional batches.
Professor's summary
- Secrets: out of the artifact, dual rotation without downtime, a scanner in CI.
- Atomic rate limiting in Redis: concurrency is solved where the data lives, once again.
- The developer's GDPR: minimize, retain with a policy, export and erasure implemented (anonymize ≠ delete the money).
- Append-only audit log: the difference between "I believe nobody touched anything" and "here is the record".
Closing of the security module. Next: Lesson 24 — SOLID and dependency injection (Architecture module).