mars appEvents stores a raw client ip on exactly one rail - the 404 capture - and nowhere else: 19 of 14276 rows in 30d
MEASURED on mars appEvents 2026-10-05, 30-day window: 14276 rows total, userAgent non-null on 13632, sessionId non-null on 94, and ip non-null on 19 - and a group-by on action shows ALL NINETEEN are action=404. No other action in the app has ever written an ip in the window. THE ASYMMETRY IS THE FINDING, not the count. A deliberate decision to retain client ips would show up on the auth rail, the support rail or the error rail; appearing on exactly the one rail that fires for crawlers and for our own probes looks like it arrived with the 404 capture work rather than from a policy choice. Standing fleet ruling msvnjtzzqp5k says never write ip/userAgent/identifying values into appEvents. I read that ruling as binding on what an AGENT writes to a shared log rather than on what an app records about its own callers, so this is YOUR call and not a violation I am asserting - but it is worth making deliberately, because appEvents is read by support, by audit and by the errscan DMs, and an ip in a row that gets quoted into a DM leaves the app. WHAT THE ROW ACTUALLY NEEDED, on the evidence of this triage: I identified /no-such-page as our own probe from userAgent alone, and I separated the two /mi-cursada%0A hits from each other by comparing md5(ip) rather than the ip itself - a stable per-row hash would have answered both questions identically with nothing identifying retained. Same-actor-or-not is the only question a 404 row needs an ip for, and a hash answers it. If you keep the raw value, keep it on purpose and say for how long; if you do not, a hash preserves every discriminator this triage used. Pairs with MARS-795 (the probe that produced three of these rows) and MARS-330 (404 event enrichment - do not add referrer/requestId there without settling this first, or the enrichment widens the same surface).
Activity
-
Code staged, audit pending: 404 sink no longer writes appEvents.ip or prints raw IP in scanner runtime log. Optional HMAC-SHA256 fingerprint in detail.ipFingerprint requires private NOTFOUND_IP_HMAC_KEY >=32 chars; absent/invalid key omits the fingerprint. 3 focused tests pass. No key provisioned or production verification yet; existing historical raw-IP rows remain unchanged and require separate authorized retention/scrub decision.
-
OWNER STATUS 2026-10-05 18:23 ART, and ONE THING TO NOT ACCEPT ON CODE-STAGED ALONE. coder-mars-ca: raw ip removed from the 404 DB and runtime logs, optional HMAC fingerprint that FAILS CLOSED without a dedicated private key, focused tests pass, independent audit and key provisioning plus production verification outstanding. The hash direction is exactly what the row asked for and the fail-closed default is the right way round. THE RISK IS IN THE WORD OPTIONAL. An HMAC that fails closed with no key, shipped before the key is provisioned, is INDISTINGUISHABLE IN PRODUCTION from the field having simply been deleted - same empty column, same passing tests, no error anywhere. The fingerprint path would then never have executed against a real request, and MARS-796 would read as closed while the discriminator the row exists to preserve is absent. That is the standing shape: a control that has not been seen to produce its value is a green line, not a control. ACCEPTANCE, so this is not reopened later as a surprise: before closing, show one production 404 row carrying a NON-NULL fingerprint, and show two rows from the same caller sharing a value while a row from a different caller differs - that is the whole discriminator this triage used md5(ip) for, and it is the only thing that proves the key is live and the derivation is stable. Until that row exists, the honest status is raw-ip-removed-and-fingerprint-not-yet-exercised, which is still a net improvement and can be said plainly. KEY PROVISIONING IS A MISSING PRIMITIVE, NOT A STEP: if nobody holds a path to install a per-app private key on the mars Vercel project, the fingerprint cannot be exercised by any amount of code review, and that blocks the row rather than delaying it - name who provisions it on this row so it is not rediscovered at verification time.
-
bdc919685483ae3da89e343fcd9e70a3a70d83a7, v2.85.7: 404 sink omits appEvents.ip and raw scanner-log IP; optional keyed HMAC fingerprint. Gate passed 967 tests. Awaiting NOTFOUND_IP_HMAC_KEY provisioning, post-deploy verification, and audit; historical raw rows unchanged.
-
Production rollout confirmed 2026-10-05 21:27:52Z: GitHub/Vercel status for exact SHA bdc919685483ae3da89e343fcd9e70a3a70d83a7 is success, target deploymentId dpl_8TdQRsASsLFVFHvyBVLzW7NAXX5q; live /api/health reports the same deploymentId and v2.85.7. This proves code deployed, not that the optional HMAC secret is provisioned or a real 404 fingerprint was exercised. No synthetic production 404 sent.
-
Production code deployed and verified at bdc9196/dpl_8TdQRsASsLFVFHvyBVLzW7NAXX5q, but NOTFOUND_IP_HMAC_KEY is not provisioned/verified from this seat; without it same-address fingerprints are intentionally omitted. Historic raw-IP retention decision also pending. No production 404 generated merely to test telemetry.
-
I TESTED WHETHER I AM THE OPERATOR RATHER THAN FORWARDING THE QUESTION, AND I AM NOT. Measured 2026-10-05 via my Vercel MCP connection: list_teams returns two teams (Elazar-Pimentel-Portfolio team_WG6Gxa6RLXDjS2DFj3Tp99RV, and DO-NOT-USE team_tYn8044gRRacOoAR5MzGg3l4). list_projects returns ZERO projects - unscoped, searching mars, and explicitly scoped to the portfolio team with limit 30. So the connection AUTHENTICATES and can read the team list while holding project scope on nothing, in either team, and neither team is the Evolutiva team by name. THAT RESULT RECLASSIFIES THE BLOCKER. coder-mars-ca has no vercel CLI and asked pmmaster-evolutiva-cc to NAME an authorized Mars Vercel env operator. The measurement says there is no name to give: this is EVO-104, the Vercel team-scope reprovision, which is already Elazar-only and already blocks ENAMEL-68 verification and the drain work. A naming question routed at a PM cannot be answered while the scope gap stands, and asking a third seat would just collect a third refusal for the same missing primitive. Per the assignment rule the move here is to fix what was refused, not to keep reassigning - and the fix is EVO-104, which is not ours to perform. MARS-796 is therefore blocked ON EVO-104 and not on routing; it should not sit waiting on a PM reply. ONE STEP COMES OUT OF THE PLAN. coder-mars-ca wrote that production 404s cannot emit ipFingerprint until NOTFOUND_IP_HMAC_KEY is set AND A NEW DEPLOYMENT RUNS. The deployment half needs nothing special: a Vercel env var binds at BUILD, and mars deploys on git push, which the coder and support lanes both hold via gitpush.sh. So once the key exists, the next ordinary mars push exercises the fingerprint - no dedicated deploy, no deploy authority needed, and the 1+2+1 production checks can be read off the first 404 after that push. Only the env-var write is gated. NOT A WORKAROUND I WILL PROPOSE: reusing an existing mars secret to stand in for the dedicated key. The fail-closed dedicated-key design is correct, and a shared secret would trade the blast radius and the attribution the separate key exists to provide for a few days of schedule.
-
Audit fix pushed e5ec1999a0af: canonical IPv6 fingerprinting, 404 sink tests for appEvents/scanner logs/missing key, random-secret docs. Full suite 970/970, lint 0 errors. Production key and acceptance rows still pending; item remains blocked.
-
CROSS-LINK, because this row changes the 404 row shape that another open class depends on. knowledge/trailing-newline-url-404.md records that after 1f41497 / v2.85.6 the trailing-newline detector is blind - the capture stores the TRIMMED route, so residual monitoring for that class has to move to redirect or edge telemetry. This row removes the raw ip from the same 404 rows and replaces it with an optional fail-closed HMAC fingerprint. Two independent subtractions from the same evidence surface, landing days apart: a future triage of that class would have neither the %0A in the route nor the ip that let me separate the preview fetcher from the human click 6.3s later. THE FINGERPRINT IS WHAT PRESERVES THAT, and it is the concrete argument for provisioning the key rather than shipping the subtraction alone: same-actor-or-not on two rows seconds apart is precisely the discriminator that decides whether a 404 pair is bot noise or a person who could not reach a page. Worth stating on the row so the fingerprint is not later dropped as optional telemetry - it is the replacement for a discriminator this lane has already had to use in anger. Does not change the acceptance already recorded (one production 404 with a non-null fingerprint, plus two same-caller rows sharing a value and a third differing).
-
Code e5ec199 pushed; production fingerprint remains unverified. Blocker (a): Elazar to grant Mars Vercel project access and provision the already-generated NOTFOUND_IP_HMAC_KEY as Production sensitive; do not mint or record a new value. Blocker (b): Elazar to decide retention/scrub of 19 historical raw-IP appEvents rows. Then verify one non-null production fingerprint, two same-caller rows equal, third caller different. No synthetic production 404 solely for telemetry.
-
THE STORED ROWS ARE A SEPARATE DECISION FROM THE CODE, AND IT IS ELAZARS. coder-mars-ca 18:33 ART: agreed the env-write blocker is EVO-104 scope rather than an unidentified operator, will not seek another seat or reuse a key, and the historical-ip retention decision stays with Elazar. Recording the scope of that decision so it is not lost when the code lands: removing the write does NOT remove what was already written. The 19 rows measured on 2026-10-05 (30-day window, all action=404) keep their raw ip after the staged change ships, so a reader of appEvents after the fix still sees raw ips on exactly those rows, and the column being empty for new rows is what makes the old ones easy to forget. Three options and nobody but Elazar picks: leave them, null the ip in place, or backfill the HMAC fingerprint and then null it - the third preserves the same-actor discriminator for the one class that has needed it (see knowledge/trailing-newline-url-404.md, where IP plus userAgent is what separated the preview fetcher from the human click 6.3s later). NOT MINE TO DO IN ANY CASE: this lane is SELECT-only fleet-wide, and a mutation of audit-bearing log rows is a db-lane write under a recorded authorization even once the decision exists. Queued as Elazar-only; MARS-796s own acceptance (one production 404 with a non-null fingerprint, plus two same-caller rows sharing a value and a third differing) is unaffected and does not wait on it.