Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions archive/2026-05/plans/OPERATOR_ACTIONS_2026-05-12.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# OPERATOR ACTIONS — 2026-05-12

Items surfaced by the 2026-05-12 senior reviews (backend, DB, infra, security)
that agents should not autonomously execute. Each is a checklist with explicit
commands, a maintenance-window estimate, and explicit dependencies. Severity
labels:
The 11 items below were surfaced by the 2026-05-12 senior reviews (backend, DB,
infra, security) that agents should not autonomously execute. (This checklist
started as five items in PR #67 and grew to 11 as the 2026-05-12 branch work
landed.) Each is a checklist with explicit commands, a maintenance-window
estimate, and explicit dependencies. Severity labels:

- **CRITICAL** — exposes a live, exploitable security or correctness gap.
- **HIGH** — drift between deployed config and committed config; reviewers
cannot reason about prod from code.
- **MEDIUM** — hygiene + cosmetic; safe to defer but easy to land.
- **LOW** — non-urgent follow-up or cleanup; no live failure if deferred.

---

Expand Down Expand Up @@ -117,7 +119,7 @@ SELECT relname, relrowsecurity, relforcerowsecurity
```

**Blast radius.**
A SQL-injection (or a deliberately misuse of `JdbcTemplate.queryForList`)
A SQL-injection (or a deliberate misuse of `JdbcTemplate.queryForList`)
that omits a `tenant_id =` predicate returns rows from every tenant. The
admin-IP whitelist on `/swagger-ui` and `/actuator` does not help here —
the entry point is the application code itself.
Expand Down Expand Up @@ -196,8 +198,9 @@ SELECT relname, relforcerowsecurity FROM pg_class
Commit `6bdedd2` (2026-04-30 morning, since-rotated) committed the
biometric API key plaintext into `web-app/.env.production`. The bio-side
key was rotated 2026-04-30 05:05 UTC and confirmed dead — the live value
is now `API_KEY_SECRET=fcb06b7…` (verified by the 2026-05-12 security
review). However the on-disk template at
is now `API_KEY_SECRET=<redacted>` (verified by the 2026-05-12 security
review; fetch the real value from the host `.env.prod` / secret store, not
this doc). However the on-disk template at
Comment on lines 199 to +203
`/opt/projects/fivucsas/web-app/.env.production` still contains the
leaked literal in `VITE_BIOMETRIC_API_KEY=…` form (2 occurrences,
verified today by `grep -c`).
Expand Down Expand Up @@ -302,8 +305,12 @@ git fetch origin
# already on master, so this is a fast-forward).
git merge-base --is-ancestor origin/main origin/master \
&& echo "OK: main is an ancestor of master, fast-forward safe."
# Apply:
git push origin master:main --force-with-lease
# Apply (normal fast-forward — the ancestor check above guarantees this
# is non-destructive; no force needed):
git push origin master:main
# Reserve `--force-with-lease` ONLY for a documented recovery scenario
# (e.g. main was accidentally advanced and the ancestor check above fails),
# and only when branch protection allows it.
```

**Acceptance check.**
Expand Down
2 changes: 1 addition & 1 deletion docs-site/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
- "traefik.http.routers.fivucsas-docs.tls.certresolver=letsencrypt"
- "traefik.http.services.fivucsas-docs.loadbalancer.server.port=80"
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://127.0.0.1:80/ || exit 1"]
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://127.0.0.1:80/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
Expand Down
2 changes: 1 addition & 1 deletion docs-site/html/identity/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
<div id="fallback" style="display: none;">
<div class="fallback-message">
<h2>Identity Core API Reference</h2>
<p>The OpenAPI specification is available for authenticated users. Below is a summary of all available endpoints.</p>
<p>The full OpenAPI specification is publicly available at <a href="/identity/openapi.json">/identity/openapi.json</a>. Below is a summary of all available endpoints.</p>

<div class="base-url">
<span class="base-url-label">Base URL</span>
Expand Down
22 changes: 19 additions & 3 deletions infra/traefik/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,34 @@ sudo cp /opt/projects/fivucsas/infra/traefik/config/traefik.yml \
sudo cp /opt/projects/fivucsas/infra/traefik/config/dynamic.yml \
/opt/projects/infra/traefik/config/dynamic.yml

# 2. Validate (Traefik watches dynamic.yml live; traefik.yml requires restart)
# 2a. Validate the Compose file itself (interpolation, service shape).
# NOTE: this does NOT validate Traefik's own traefik.yml/dynamic.yml.
docker compose -f /opt/projects/infra/traefik/docker-compose.yml \
--env-file /opt/projects/infra/traefik/.env config

# 2b. Validate Traefik's YAML (syntax + semantics) before restarting.
# Traefik has no offline "lint" subcommand, so do a one-shot dry-run:
# boot a throwaway container against the live config and watch for
# "configuration error" lines. It exits non-zero on a fatal parse error.
docker run --rm \
-v /opt/projects/infra/traefik/config/traefik.yml:/etc/traefik/traefik.yml:ro \
-v /opt/projects/infra/traefik/config/dynamic.yml:/etc/traefik/dynamic.yml:ro \
traefik:v3 traefik --configfile=/etc/traefik/traefik.yml 2>&1 \
| grep -iE "error|invalid" || echo "no config errors detected"
Comment on lines +31 to +37

# 3. Apply
# dynamic.yml changes: zero-restart, picked up via inotify (`watch: true`)
# traefik.yml changes: require container restart
docker compose -f /opt/projects/infra/traefik/docker-compose.yml \
--env-file /opt/projects/infra/traefik/.env restart traefik

# 4. Verify access log writes peer IP, not client-supplied XFF
docker logs traefik 2>&1 | tail -20
# 4. Verify access log writes peer IP, not client-supplied XFF.
# NOTE: traefik.yml sets `accessLog.filePath: /var/log/traefik/access.log`,
# so access logs go to that FILE inside the container, not stdout —
# `docker logs traefik` shows only Traefik's own runtime/error log.
# Read the access log from the file instead:
docker exec traefik tail -20 /var/log/traefik/access.log
# (Traefik's startup/error log is still on stdout: `docker logs traefik`.)
```

## XFF / Rate-Limit Hardening (2026-05-12)
Expand Down
17 changes: 7 additions & 10 deletions landing-website/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,16 @@ body {

/*
* Force English-locale casing on any uppercase'd English text in
* Turkish-mode pages. CSS text-transform uses the html lang attribute
* to pick casing rules — Turkish maps lowercase "i" to "İ" (dotted
* capital), so "identity" becomes "İDENTİTY" even though the source
* text is English. Marking the element `lang="en"` solves it cleanly.
*
* Belt-and-braces: also force the codepoints below to render with
* Latin small/capital "I" inside English-attributed nodes regardless
* of which font fallback the browser picks.
* Turkish-mode pages. CSS text-transform is locale-aware and uses the
* html lang attribute to pick casing rules — under lang="tr" the browser
* maps lowercase "i" to "İ" (dotted capital), so "identity" becomes
* "İDENTİTY" even though the source text is English. Scoping the rule to
* :lang(en) means the uppercasing here only applies to nodes explicitly
* marked lang="en", which gives them the Latin (dotless) "I" casing.
*/
:lang(en).uppercase,
:lang(en) .uppercase {
text-transform: uppercase;
text-transform: uppercase; /* keep — old WebKit needed it */
text-transform: uppercase; /* locale-aware: lang="en" → Latin "I" casing */
}

@layer utilities {
Expand Down