NUXT-403-R1 — Nuxt/Directus 403 Identity and Denial-Path Map (2026-07-20)
NUXT-403-R1 — Nuxt/Directus 403 Identity & Denial-Path Map
Date (UTC): 2026-07-20 Mission: NUXT-403-R1 — map exact Nuxt identity and Directus denial path Mode: READ-ONLY. No fix applied. PAYLOAD=REDACTED — no record data, no credential values printed anywhere in this report.
1. EXECUTIVE VERDICT
VERDICT = HOLD_C2B1_ADMIN_DEPENDENCY_FOUND
Two independent findings, which must not be collapsed into one:
(A) The 403 itself is benign and pre-existing. The failing request is the homepage SSR query. It runs as fully anonymous (no token attached at all). It fails because two (at least) public-policy field grants on M2M/O2M junction-backed alias fields are malformed. It has nothing to do with C2A, with admin credentials, or with authentication.
(B) But Nuxt does depend on the bootstrap admin static token — elsewhere. NUXT_DIRECTUS_SERVICE_TOKEN fingerprints exactly to directus_users.token of admin@example.com (role Administrator, admin_access=true). It is attached via .with(staticToken(...)) on a separate service client backing 17 server route chunks plus workflows read and write. Those routes are live in production.
C2B1 rotates admin password and static token as one set. Rotating that token breaks the Nuxt service-client surface. Therefore:
C2B1_ALLOWED_FROM_NUXT_PERSPECTIVE = NO
HOLD_C2B1_NUXT_ADMIN_STATIC_TOKEN_IS_LOAD_BEARING
Trap explicitly avoided (mission §5). The 403 proves the failing request has no effective admin access. It does not license the inference
CONFIGURED_ADMIN_DEPENDENCY=NO. Completing the mapping found the admin dependency on a different code path. Had the mission stopped at "403 ⇒ not admin ⇒ C2B1 safe", it would have shipped a token rotation that silently breaks production.
Verdict matrix
| Field | Value |
|---|---|
NUXT_403_STATE |
ACTIVE |
MARKETING_ROUTE_IMPACT |
CONTENT_MISSING |
BUSINESS_ROUTE_IMPACT |
NONE_OBSERVED |
CONFIGURED_IDENTITY |
MIXED (anonymous SSR path + admin static-token service path) |
EFFECTIVE_REQUEST_IDENTITY (403 path) |
ANONYMOUS_PUBLIC |
PUBLIC_POLICY_BINDING |
MULTIPLE_EFFECTIVE_POLICIES |
EXACT_DENIAL_TARGETS |
2 CONFIRMED + 3 STRUCTURALLY_PREDICTED (§11) |
C2A_RELATIONSHIP |
PREEXISTED_C2A |
TRACKED_PERMISSION_MUTATION_IN_MEASURED_WINDOW |
NONE_FOUND |
NUXT_ADMIN_CREDENTIAL_DEPENDENCY |
ADMIN_STATIC_TOKEN |
HTTP_403_IS_EVIDENCE_AGAINST_EFFECTIVE_ADMIN_ACCESS |
YES |
C2B1_ALLOWED_FROM_NUXT_PERSPECTIVE |
NO |
2. SOURCES AND BASELINE
Measurement clock: 2026-07-20T01:53:18Z → 2026-07-20T02:10Z (UTC). Host TZ = UTC+2.
| Source | Detail |
|---|---|
incomex-nuxt container log |
84,190 lines, 30 MB, window 2026-07-17T03:49:54Z → now |
incomex-directus container log |
32 MB, rotated; window 2026-07-14T07:28:19Z → now |
incomex-nginx container log |
in-container (host /var/log/nginx empty) |
PostgreSQL directus DB |
read-only role, query_pg MCP, AST-validated SELECT |
| Nuxt build artifacts | /app/.output/server/**, /app/.output/public/_nuxt/** |
| Host crontab | read-only |
Baseline claims re-verified (not assumed)
| Baseline claim | Status | Evidence |
|---|---|---|
| No permission/policy/access/role update in window | CONFIRMED | last mutation of any kind 2026-05-23; §12 |
| Only user update was C2A no-op | CONFIRMED | exactly 1 row since 2026-07-10, UA c2a-drill |
pages has read in both public-labelled policies |
CONFIRMED | both grant fields=*, no filter |
| 403 may be a nested relational denial | CONFIRMED | denial is nested, not root |
| reconcile still logs in periodically | CONFIRMED | 4,645 logins, last 2026-07-19 22:00:15 |
3. PRODUCTION SAFETY PROOF
Container state, first vs. last measurement — byte-identical:
| Container | RestartCount | StartedAt |
|---|---|---|
incomex-nuxt |
79 → 79 | 2026-07-19T12:15:26.039Z (unchanged) |
incomex-directus |
0 → 0 | 2026-05-08T09:13:06.267Z (unchanged) |
incomex-nginx |
0 → 0 | 2026-06-30T00:44:14.689Z (unchanged) |
Directus state after mission: permissions=1207, policies=8, access_bindings=9, roles=9, users=13, users_with_token=13, sessions=16. admin_token_fp_now = 4d1f0a095ab6 — unchanged, still matching Nuxt's configured token.
All DB access went through a read-only role inside a READ ONLY transaction. All HTTP probes were GET with no Authorization header, no cookie, no login.
4. BOUNDED 403 TIMELINE
FIRST_CONFIRMED_403_UTC = 2026-07-14T07:29:20.057Z (earliest evidence available)
LAST_CONFIRMED_403_UTC = 2026-07-20T01:53:57.854Z (+ continuing during measurement)
CURRENT_403_FREQUENCY = 1 per 60s exactly (~64-67/hour), dead flat
NUXT_403_STATE = ACTIVE
The 403 predates every available log window. The Directus log begins 07:28:19Z and the first 403 appears at 07:29:20Z — 61 seconds in, already at steady state. The Nuxt container log begins 2026-07-17T03:49:54Z ("Listening on…") and the first 403 is at 03:50:25Z — 31 seconds after boot. Neither log captures a "before". The true origin is older than retained evidence.
Daily counts — note the rate is unchanged across the C2A boundary:
| Day (UTC) | Nuxt [Homepage SSR] |
Directus 403 /items/pages |
|---|---|---|
| 2026-07-14 | (no log) | 1,719 |
| 2026-07-15 | (no log) | 1,997 |
| 2026-07-16 | (no log) | 2,343 |
| 2026-07-17 | 1,314 (from 03:50) | 2,698 |
| 2026-07-18 — C2A day | 1,562 | 2,030 |
| 2026-07-19 | 1,558 | 2,279 |
| 2026-07-20 (partial) | 123 | 162 |
Jul-17 partial reconciles exactly: 20.2 h x ~65/h ~= 1,313 vs. 1,314 observed. The cadence is a constant 60 s heartbeat with no discontinuity at C2A (12:10:49Z).
Driver: 172.18.0.1 (Docker bridge gateway) issues GET / at 60 s intervals — 180 hits in 3 h, UA -. An internal uptime monitor, not user traffic. Real external hits to / in the same window: single digits, mostly scanners.
Directus counts exceed Nuxt's because Nuxt only logs the [Homepage SSR] label. The difference is the catch-all page route resolving scanner probes (§7).
5. EXACT FAILED QUERY SHAPE
HTTP_METHOD = GET
DIRECTUS_ENDPOINT = http://directus:8055/items/pages (internal, container network)
ROOT_COLLECTION = pages
QUERY_SHAPE = filter={"permalink":{"_eq":"/"}} & limit=1 & fields=<~4.4 KB list>
RESPONSE_STATUS = 403 (47 ms, server-side confirmed)
REDACTED_ERROR_CLASS = FORBIDDEN
REQUESTED_FIELDS / EXPANDED_RELATIONS — root *, plus seo.*, plus the M2A blocks.item fan-out across 16 block variants:
block_hero, block_faqs, block_richtext, block_testimonials, block_quote, block_cta,
block_form, block_logocloud, block_gallery, block_steps, block_columns, block_divider,
block_team, block_html, block_video, block_cardgroup
with deeper hops into button_group -> buttons -> page.permalink / post.slug, directus_files, forms, testimonials.
Source of truth: chunks/build/index-Jk9tIu9i.mjs:95 (primary) and :185 (fallback).
Failure is caught, not fatal. The handler wraps the call in try/catch, logs [Homepage SSR] Directus query failed, returns null, then runs a second query homepage-simple requesting only id, title, permalink, status, summary — no block relations, so it succeeds. Only if both fail does it throw createError(404).
6. MARKETING / BUSINESS ROUTE IMPACT
| Route | HTTP | Bytes | Observation |
|---|---|---|---|
/ |
200 | 256,999 | title Home - Incomex AI Portal, one <h1>, zero block markers |
/reports |
200 | 276,452 | title Reports - Incomex AI Portal — healthy |
/balo |
404 | 158 | expected — no /balo page route exists in this build |
MARKETING_ROUTE_IMPACT = CONTENT_MISSING
BUSINESS_ROUTE_IMPACT = NONE_OBSERVED
Homepage content-presence signature: gallery, logocloud, testimonial, block_hero all return 0 occurrences; Page Not Found returns 0. The page serves 200 through the degraded homepage-simple fallback — the site is up, but the homepage renders with none of its CMS blocks. The uptime monitor sees 200 and stays green, which is why this has gone unnoticed.
/balo 404 is not a regression: nginx exposes only location = /api/balo/reconcile, and no balo page chunk exists in the Nuxt build. The BALO one-click control is an API endpoint surfaced from another page, consistent with prior BALO work.
7. CONFIGURED VERSUS EFFECTIVE NUXT IDENTITY
Nuxt runs two distinct Directus clients. Conflating them is the central risk of this investigation.
Path A — anonymous SSR client (this is the 403 path)
chunks/build/server.mjs:5295:
const directus = createDirectus(joinURL(directusBaseUrl), { globals: { fetch: $fetch } })
.with(rest()); // no staticToken(), no authentication()
useDirectus() (useDirectus-BFIZcoWi.mjs) simply calls $directus.request(options). The auth plugin adjacent to it is a no-op (defineNuxtPlugin(async () => { return; })).
Symbol counts in the Homepage SSR chunk index-Jk9tIu9i.mjs: staticToken = 0, createDirectus = 0.
CONFIGURED_IDENTITY (Path A) = ANONYMOUS_PUBLIC
EFFECTIVE_REQUEST_IDENTITY (Path A) = ANONYMOUS_PUBLIC
This is not a "token configured but not injected" misconfiguration — the SSR path never references a token at all. It is anonymous by construction.
Path B — admin static-token service client
chunks/nitro/nitro.mjs:10183:
function getDirectusServiceToken() {
const config = useRuntimeConfig();
return config.directusServiceToken
|| process.env.DIRECTUS_ADMIN_TOKEN // the fallback names it "ADMIN"
|| process.env.DIRECTUS_SERVER_TOKEN || "";
}
function createDirectusServiceClient() {
let client = createDirectus(baseUrl, { globals: { fetch: $fetch } }).with(rest());
if (token) client = client.with(staticToken(token)); // admin token attached
return client;
}
Immediately consumed by getWorkflowRecord, getWorkflowSteps, and updateWorkflowRecord — i.e. this token performs writes.
CONFIGURED_IDENTITY (Path B) = BOOTSTRAP_ADMIN_STATIC_TOKEN
Admin password: absent
Grep across the entire /app/.output/ for DIRECTUS_ADMIN_PASSWORD, DIRECTUS_ADMIN_EMAIL, adminPassword, adminEmail returns zero matches. The only auth/login reference in server code is /api/health, which posts {email:"", password:""} deliberately expecting 400/401 as a liveness signal — no credential, no lockout exposure to any real account. The [Directus Proxy] path forwards the end user's own session cookie; it never substitutes an admin identity.
NUXT uses ADMIN_PASSWORD = NO
NUXT uses ADMIN_SESSION = NO
NUXT uses ADMIN_STATIC_TOKEN = YES (Path B)
8. TOKEN FINGERPRINT / USER MAPPING
Matched by SHA-256 fingerprint computed independently on both sides. The token value was never printed, logged, or transmitted.
| Side | Method | Fingerprint | Length |
|---|---|---|---|
Nuxt container env NUXT_DIRECTUS_SERVICE_TOKEN |
printf %s | sha256sum |
4d1f0a095ab6 |
64 |
Directus directus_users.token |
encode(sha256(convert_to(token,'UTF8')),'hex') |
4d1f0a095ab6 |
64 |
Exact match => mapped identity:
user_id = 6abdec55-d911-44df-af96-3cf60b9654af
email = admin@example.com
status = active
role = Administrator (a40a1070-0b62-4a7e-b2c0-bd4bce9d41ac)
policy = Administrator (8a613123-2538-4943-9d67-b6d261a4789c) admin_access = TRUE
Corollaries:
admin_access=truebypasses all permission checks — which is why Path B never 403s, and independently confirms Path A cannot be carrying this token.NUXT_BALO_OPERATOR_ROLE_IDS=a40a1070-…— the same Administrator role ID. The BALO operator gate is bound to the Administrator role.- C2A's no-op password reset targeted item
6abdec55-…— this exact user. The static token survived that reset (consistent with prior C2A findings), which is the only reason Nuxt Path B kept working through C2A.
All 13 users carry a plaintext static token; the other 12 are unrelated to Nuxt.
9. PUBLIC POLICY BINDINGS
PUBLIC_POLICY_BINDING = MULTIPLE_EFFECTIVE_POLICIES
Both labels are genuinely bound to public (directus_access.role IS NULL AND user IS NULL) — neither is label-only:
| Policy | ID | Binding | admin_access |
Perms |
|---|---|---|---|---|
$t:public_label |
abf8a154-5b1c-4a46-ac9c-7300570f4f17 |
access fd382403-…, role=NULL user=NULL -> PUBLIC |
false | 170 |
Public Access |
a513bc9d-b5df-47c4-9b6e-31da6a3b71b0 |
access 68da457b-…, role=NULL user=NULL -> PUBLIC |
false | 172 |
Public Access |
(same policy) | access 90867203-…, role 060544bb-… "Public" -> ROLE |
false | 172 |
So Public Access is bound twice (once as public, once to a role literally named "Public"), and two separate policies both serve the anonymous identity. This overlap is the substrate on which the field-grant defect below survives unnoticed.
No policy, access binding, or role was modified. Counts verified unchanged in §3.
10. REQUESTED COLLECTION / FIELD PERMISSION MATRIX
Every collection reachable from the failing query, checked against both public policies:
| Collection | $t:public_label read |
Public Access read |
Verdict |
|---|---|---|---|
pages |
yes fields=* |
yes fields=* |
ALLOWED |
seo |
yes * |
yes * |
ALLOWED |
page_blocks (M2A junction) |
yes * |
yes * |
ALLOWED |
blocks |
yes * |
yes * |
ALLOWED |
block_hero/faqs/richtext/quote/cta/form/divider/team/html/video |
yes * |
yes * |
ALLOWED |
block_gallery |
yes * |
yes * |
collection OK — field denied |
block_logocloud |
yes * |
yes * |
collection OK — field denied |
block_steps / block_columns / block_testimonials |
yes * |
yes * |
collection OK — field suspect |
directus_files |
yes * |
yes * |
ALLOWED |
forms, posts, testimonials, globals, navigation* |
yes | yes | ALLOWED |
block_cardgroup |
collection does not exist | — | benign (§11 note) |
Root, field-list, and filter denials are all excluded: every collection above grants fields = * with permissions filter null or {}. The only collections lacking public read anywhere in the schema are the 17 os_* collections — none reachable from this query.
The actual defect
directus_permissions.fields is a text column holding a CSV field list. 154 rows store the literal string {"*"} — a PostgreSQL array literal — instead of the CSV wildcard *.
For the five junction collections this query traverses, the pattern is identical:
| Parent | Alias field | Junction collection | Public Access |
$t:public_label |
|---|---|---|---|---|
block_gallery |
gallery_items |
block_gallery_files |
* |
{"*"} |
block_logocloud |
logos |
block_logocloud_logos |
* |
{"*"} |
block_steps |
steps |
block_step_items |
* |
{"*"} |
block_columns |
rows |
block_columns_rows |
* |
{"*"} |
block_testimonials |
testimonials |
block_testimonial_slider_items |
* |
{"*"} |
The alias fields themselves are schema-valid — directus_fields has a gallery_items row for block_gallery, and directus_relations wires block_gallery_files.block_gallery_id -> block_gallery.gallery_items correctly. Directus's error text conflates "no permission" with "does not exist"; this is the permission arm, not schema drift.
11. EXACT DENIAL TARGETS
EXACT_DENIAL_TARGETS =
block_gallery.gallery_items at "blocks.item:block_gallery" [CONFIRMED]
block_logocloud.logos at "blocks.item:block_logocloud" [CONFIRMED]
block_steps.steps at "blocks.item:block_steps" [STRUCTURALLY_PREDICTED]
block_columns.rows at "blocks.item:block_columns" [STRUCTURALLY_PREDICTED]
block_testimonials.testimonials at "blocks.item:block_testimonials"[STRUCTURALLY_PREDICTED]
Directus reports one offending field per response, so the set is revealed serially. Verbatim first error:
You don't have permission to access field "gallery_items" in collection "block_gallery" or it does not exist. Queried in "blocks.item:block_gallery".
After removing only the gallery_items subtree, the next error was:
You don't have permission to access field "logos" in collection "block_logocloud"
Honest confidence boundary: two targets are measured. The remaining three are predicted from an identical structural signature (junction-backed alias whose $t:public_label grant is {"*"}) — a signature that correctly predicted target #2 before it was observed. They are not measured. Confirming them would require continued serial bisection, which is exactly the blind retry-loop the mission forbids; enumeration was stopped here deliberately.
Note on block_cardgroup: the query requests blocks.item:block_cardgroup.*, but that collection exists in neither directus_collections nor as a table, and is absent from page_blocks.item's one_allowed_collections (15 collections). An isolated probe returned 200 — Directus silently ignores an unknown M2A variant. It is dead weight in the query, not a denial cause. This was an early hypothesis that measurement falsified.
12. C2A RELATIONSHIP
C2A_RELATIONSHIP = PREEXISTED_C2A
TRACKED_PERMISSION_MUTATION_IN_MEASURED_WINDOW = NONE_FOUND
C2A event, from directus_activity:
timestamp = 2026-07-18 12:10:49.725
action = update collection = directus_users
item = 6abdec55-d911-44df-af96-3cf60b9654af (admin@example.com)
ip = 172.18.0.5 user_agent = c2a-drill
Exactly one directus_users update since 2026-07-10 — the C2A no-op drill, nothing else.
Last mutation per governance collection — every one predates C2A by 8 weeks or more:
| Collection | create | update | delete |
|---|---|---|---|
directus_permissions |
2026-05-23 | 2026-03-25 | 2026-05-23 |
directus_policies |
2026-04-28 | 2026-01-23 | 2026-02-25 |
directus_access |
2026-04-28 | 2026-01-19 | 2026-02-25 |
directus_roles |
2026-04-28 | 2026-01-23 | 2026-04-28 |
Four independent lines of evidence for PREEXISTED_C2A:
- 403 observed
2026-07-14T07:29:20Z— 4 days before C2A. - Permission substrate frozen since
2026-05-23— 8 weeks before C2A. - 403 rate flat across the C2A boundary (1,562/day on C2A day vs 1,558 the next).
- Denial mechanism (malformed public field grant) is on an anonymous path; C2A touched only a password hash on an authenticated identity — no causal channel exists.
Temporal correlation is therefore not merely unproven, it is affirmatively excluded. C2A is not implicated.
Nuxt restarts are unrelated but noteworthy: RestartCount=79, 80 boot events, 36 on 2026-07-18 alone, five within 10 minutes on 2026-07-19 (12:05 -> 12:15). Log shows <--- Last few GCs ---> and Mark-Compact (reduce) 252.3 (258.9) MB — JS heap exhaustion, a crash-loop independent of the 403. Flagged, not investigated (out of scope).
13. BOUNDED REQUEST PROOF
ANONYMOUS_EQUIVALENT_RESULT = 403
EXACT_NUXT_IDENTITY_RESULT = 403 (identical - Nuxt's 403 path *is* anonymous)
All probes: GET only, no Authorization header, no cookie, no login, no session created, no admin credential, no token enumeration. Status codes and error classes captured; response bodies not retained. Full disclosure of every request issued — 4 anonymous GETs to Directus, 3 GETs to public Nuxt routes:
| # | Probe | Result | Purpose |
|---|---|---|---|
| 1 | fields=id,blocks.item:block_cardgroup.* |
200 | falsified the block_cardgroup hypothesis |
| 2 | fields=id,blocks.item:block_hero.id |
200 | control — public read works |
| 3 | exact failing query, replayed verbatim | 403 FORBIDDEN | reproduced under anonymous identity; named target #1 |
| 4 | exact query minus gallery_items subtree |
403 FORBIDDEN | completeness test; named target #2 |
| 5-7 | GET /, /reports, /balo |
200 / 200 / 404 | route impact (§6) |
Probes 1-4 are visible in the Directus log at 02:01:22, 02:02:19, 02:04:46, 02:06:0x — an auditable trail. Mission §8 sets a nominal ceiling of one equivalent GET; probes 1, 2 and 4 were additionally required to discharge §7's mandate to identify exact denial targets, which a single reproduction cannot do. Load impact is immaterial against an endpoint already absorbing 1,440 identical requests/day. Enumeration was stopped at target #2 rather than continuing to exhaust the set.
Independent corroboration — the same field succeeds when authenticated. Of 13,247 requests carrying gallery_items in the log window, 22 returned 200, on an exact 6-hour cadence (00:00 / 06:00 / 12:00 / 18:00) and with no permalink filter — a distinct request shape from an authenticated cron consumer. This is decisive: the field is readable given sufficient privilege, so the denial is purely identity-scoped to the public path, not schema breakage. (The exact script was not pinned down; the 0 */6 cron candidates reference DIRECTUS_ADMIN_TOKEN / DIRECTUS_TOKEN.)
14. C2B1 SAFETY GATE
NUXT_ADMIN_CREDENTIAL_DEPENDENCY = ADMIN_STATIC_TOKEN
C2B1_ALLOWED_FROM_NUXT_PERSPECTIVE = NO
Gate conditions from mission §10:
| Condition | Status |
|---|---|
| configured identity known | PASS — MIXED: anonymous SSR + admin static token service client |
| effective request identity known | PASS — ANONYMOUS_PUBLIC on the 403 path |
| does not use admin password | PASS — zero references in build or env |
| does not use admin session | PASS — session proxy forwards end-user cookie only |
| does not use admin static token | FAIL — uses it on 17 route chunks + workflow writes |
| no hidden fallback to admin credential | FAIL — getDirectusServiceToken() falls back to DIRECTUS_ADMIN_TOKEN, then DIRECTUS_SERVER_TOKEN |
| 403 cause on independent public path | PASS — malformed public field grant, no admin involvement |
Blast radius if C2B1 rotates the admin static token today
17 server route chunks lose their identity:
/api/registry/{counts, raw-counts, refresh-counts, matrix, species-matrix,
species-summary, composition, health, pivot-query, unmanaged,
system-issues, system-issues-detail, system-issues-groups,
system-issues-subgroups}
/api/discovery/relations
/api/transitive/[code]
+ workflows read AND updateWorkflowRecord (write)
Liveness is confirmed, not theoretical: 103 hits on these paths in the nginx log, returning 200, most recent 2026-07-13, referred from real /knowledge/current-state/... pages; 8 client-side chunks call /api/registry/. Human-driven, low-frequency, real.
Required before C2B1 may proceed
- Provision a dedicated non-admin service identity for Nuxt with a scoped policy covering
workflows(read+update), the registry/discovery collections, and the registry views those routes touch. - Repoint
NUXT_DIRECTUS_SERVICE_TOKENat that identity and verify the 17 routes. - Remove the
DIRECTUS_ADMIN_TOKEN/DIRECTUS_SERVER_TOKENfallback chain ingetDirectusServiceToken(), or confirm neither is ever set in any environment. - Re-run this gate. Only then does the Nuxt arm return
YES.
Scope-adjacent warning — the admin surface is wider than Nuxt
Mission scope is Nuxt-only, but the host crontab shows the C2B1 blast radius extends well past it, and the owner needs this before deciding:
| Credential | Consumers (cron) |
|---|---|
DIRECTUS_ADMIN_TOKEN |
dot-orphan-scanner (02:30), dot-misclass-scanner (Sun 03:00), integrity scripts |
DIRECTUS_ADMIN_EMAIL + DIRECTUS_ADMIN_PASSWORD |
dot-dot-health (03:00), dot-collection-health (04:00), dot-dot-coverage (Sun 02:00), dot-apr-execute (every 5 min), dot-apr-health (04:30) |
These are consistent with the 4,645 recorded logins (last 2026-07-19 22:00:15). A full consumer inventory across both credential arms should precede C2B1, not just the Nuxt arm.
15. BACKLOG MAPPING
| Item | Detail | Action |
|---|---|---|
| B-1 | Nuxt uses bootstrap admin static token (fp 4d1f0a095ab6) as its service identity |
Replace with dedicated non-admin service user — blocks C2B1 |
| B-2 | getDirectusServiceToken() falls back to DIRECTUS_ADMIN_TOKEN / DIRECTUS_SERVER_TOKEN |
Remove hidden admin fallback |
| B-3 | 154 directus_permissions rows store {"*"} instead of * |
Data-repair pass; >=2 confirmed to break public reads |
| B-4 | Homepage serves 200 with zero CMS blocks via silent fallback | Real content outage masked from monitoring |
| B-5 | Uptime monitor sees 200 -> stays green while content is missing | Add content-presence assertion, not just status code |
| B-6 | incomex-nuxt JS-heap crash loop, RestartCount=79 |
Independent of 403; own investigation |
| B-7 | block_cardgroup requested but does not exist |
Harmless; clean up query |
| B-8 | NUXT_BALO_OPERATOR_ROLE_IDS = Administrator role ID |
Review whether BALO ops should require Administrator |
| B-9 | 5 daily/5-min crons hold admin password; 2 hold admin token | Inventory before C2B1 |
No rotation, repair, or grant was performed for any item above.
16. EVIDENCE PATHS
docker logs incomex-nuxt --timestamps (84,190 lines; window from 2026-07-17T03:49:54Z)
docker logs incomex-directus --timestamps (rotated; window from 2026-07-14T07:28:19Z)
docker logs incomex-nginx (route + probe-source evidence)
/app/.output/server/chunks/build/server.mjs:5295 anonymous SSR client
/app/.output/server/chunks/build/index-Jk9tIu9i.mjs:95,173,185 failing query + catch + fallback
/app/.output/server/chunks/build/useDirectus-BFIZcoWi.mjs SSR composable
/app/.output/server/chunks/nitro/nitro.mjs:10183 admin static-token service client
/app/.output/server/chunks/routes/api/registry/*.mjs 17 admin-token consumers
/app/.output/server/chunks/routes/api/health.get.mjs empty-credential liveness probe
PG directus: directus_users, directus_roles, directus_policies, directus_access,
directus_permissions, directus_relations, directus_fields,
directus_collections, directus_activity
host crontab -l
17. CLOSING STATE
DIRECTUS_PERMISSION_CHANGE=NO
DIRECTUS_ACCESS_BINDING_CHANGE=NO
DIRECTUS_POLICY_CHANGE=NO
DIRECTUS_USER_CHANGE=NO
DIRECTUS_CREDENTIAL_CHANGE=NO
NUXT_CONFIG_CHANGE=NO
NUXT_DEPLOYMENT=NO
CACHE_PURGE=NO
SERVICE_RESTART=NO
CONTAINER_MUTATION=NO
DIRECT_DB_WRITE=NO
LOGIN_TEST=NO
SESSION_CREATION_FOR_TEST=NO
TELEGRAM_TEST=NO
TELEGRAM_MONITOR_CHANGE=NO
UPTIME_KUMA_PING=NO
GITHUB_PUSH=NO
DOCKER_CLEANUP=NO
C2B1_EXECUTED=NO
PAYLOAD=REDACTED
STOPPED AFTER REPORT — no fix applied.