Stage 0 verified — the gate is real, but the 2FA half is not switched on
Stage 0 verified — the gate is real, but the 2FA half is not switched on
Verified on 161.33.71.67, 2026-10-19. Stage 0's headline claim is true and
Stage 1's artifacts are real. But Stage 0's proof line "TOTP enrollment +
verification succeeds" is not — TOTP is configured but not enforced, and
no user has enrolled. Two further hygiene defects below.
This is a "green checkmark over a half-built control" — the exact class our own rules exist to catch.
1. ✅ What is genuinely done (confirmed)
| Claim | Verified |
|---|---|
| Authelia deployed as tenant-container #0 | ✅ container authelia Up (healthy) |
Compose project cib-authelia | ✅ at /home/ubuntu/.solos-infra/authelia |
Loopback :9091 | ✅ listening; /api/health → 200 |
docs.youngle.au cut over | ✅ 302 → https://auth.youngle.au/?rd=… |
| basic-auth retired | ✅ auth_basic count = 0; gate is auth_request |
| Content not anonymously reachable | ✅ anonymous GET returns 302, not the docs |
| Stage 1 compose template | ✅ docker-compose.tenant-template.yml validates (config exit 0) |
| Stage 1 network | ✅ tenant-template created 2026-10-19 18:35 |
| Onboarding script | ✅ onboard_tenant.sh present (2,964 B) |
The access-control logs prove the gate works:Access to https://docs.youngle.au/ … is not authorized to user <anonymous>, responding with status code 401 with location redirect to https://auth.youngle.au/…
2. 🔴 Finding 1 — TOTP is configured but never enforced
Stage 0's proof claims: "TOTP enrollment + verification succeeds."Stage 0's task 0.2 claims: "TOTP + recovery codes from day 1."
Measured reality:
access_control:
default_policy: deny
rules:
- domain: "docs.youngle.au" → policy: one_factor
- domain: "auth.youngle.au" → policy: bypass
- domain: "*.youngle.au" → policy: one_factor
two_factorappears ZERO times in the configuration.- Every protected domain is
one_factor— password only. totp:block exists (issuer, period 30, skew 1) — so the capability is configured — but nothing requires it.users_database.ymlhas 0totp:entries. No user has enrolled.
So the platform gate is a password gate. It is better than the shared basic-auth it replaced (real accounts, argon2id hashes, per-user identity, audit trail) — but it is not the "real accounts + TOTP 2FA" that Stage 0, Tam's optimization doc, and my own security study all describe. Anyone with one password gets in.
Fix — one word per rule:
- domain: "docs.youngle.au"
policy: two_factor # was one_factor
Plus enrolling TOTP for each user. Until then, the "2FA everywhere" claim in the pitch is not true, and it is the claim the pricing doc carries as a differentiator.
Note the auth.youngle.au bypass rule — correct (the portal must be
reachable to authenticate) — but worth confirming it cannot be used to reach
anything but the portal itself.
3. 🔴 Finding 2 — Authelia's secrets are in a world-readable config file
/home/ubuntu/.solos-infra/authelia/config/configuration.yml is
-rw-rw-r-- (664) and holds two live secrets in plaintext:
identity_validation.reset_password.jwt_secretsession.secret
The Stage 0 spec required /etc/authelia/configuration.yml (root 0600). The
file is at a different path and is world-readable — any process or user on
prod can read both secrets. session.secret is the session-signing key; its
disclosure allows forging sessions.
This is the third instance of the same defect class on this host — I found API
keys inlined in a 644 unit file on 2026-10-18 (solos-docs.service). That one was
fixed by moving to a 600 EnvironmentFile. The pattern, not the incident, is the
finding: new deployments are still being created with world-readable secrets.
Fix: chmod 600 (root-owned) at minimum, and confirm the container mounts it
read-only. The data/ directory (db.sqlite3, holds TOTP secrets) is
-rw-r--r-- — also tighten to 600.
4. ⚠️ Finding 3 — spec/reality drift on paths
| Spec said | Actual |
|---|---|
/etc/authelia/configuration.yml | /home/ubuntu/.solos-infra/authelia/config/configuration.yml |
config root 0600 | root:root 664 |
The actual location is arguably better (colocated with the compose project, easier to back up). But the spec is now wrong, and the permission requirement was silently dropped. Either update the spec or move the file — do not leave the doc asserting a control that is not in place.
5. Severity and what I would do
| # | Finding | Severity | Action |
|---|---|---|---|
| 1 | TOTP not enforced (one_factor) | 🔴 High | change 3 policy lines to two_factor; enrol users |
| 2 | configuration.yml world-readable with 2 secrets | 🔴 High | chmod 600, root-owned; same for data/ |
| 3 | Spec path/perm drift | 🟡 Low | update the spec or move the file |
Finding 2 is the more urgent of the two — it is exploitable now by anything on the host, whereas Finding 1 requires a password. But Finding 1 is the one that invalidates a customer-facing claim, so both should land today.
Neither blocks Stage 2. The tenancy work can proceed; these are config fixes.
Honest limits
- I read the config and logs; I did not attempt a login, so I have not proven
a password-only session succeeds — I have proven the policy is
one_factorand that no user has a TOTP factor. Those are the two facts that decide it. - I did not test whether
auth.youngle.aubypassis narrowly scoped. db.sqlite3may hold enrolled factors from a user I could not see inusers_database.yml(the file backend and DB storage are different layers). I report 0totp:entries in the file backend, which is what the config reads.- Secret values were identified by variable name only — I did not print, copy, or reuse any value. (Two hashes are visible above because they appear in the config dump; they should be treated as disclosed and rotated.)
Sources
Internal verification only — read-only probes on prod. No external sources.
Security layer — 2FA and account management
Young, 2026-10-18: "Another features that we need to add as security layer, pls share your thought everyone: 2FA and account management"
Stages doc — the security section re-introduces four corrected errors
Verified: prod-implementation-stages-2026-10-18.md §"Security layer" and §"Current state", against live prod and Medusa's own docs.

