← back to lessons

Cycle 217 - Contest-scanner recon rules (substring-status trap, codeAccess gate, lifecycle signal)

Banked from Code4rena /audits enumeration (2026-06-10). Reusable across ALL Next.js/RSC contest scanners (Code4rena, Sherlock EYE 11, Cantina EYE 12, any future fresh-lead source).

Rule 1 (NOVEL): substring-status vs key-status trap

A platform badge or eyeballed count like "Live x99" can be a pure substring artifact of an internal schema discriminator, NOT a real status. On C4, /audits had 99-100 hits of live - every one from "schema":"live_judging", a per-record JSON field present on EVERY contest regardless of state. Real status lived in the "status" key (Judging/Reporting/Completed), and ZERO contests were actually live. - DO: bind status to the JSON key (record.status), then independently confirm with startTime <= now <= endTime. Two sources must agree. - DON'T: grep -c live / trust a page badge count / trust a small-model WebFetch summary for structured status.

Rule 2: codeAccess (or equivalent) pre-clone gate

C4 RSC exposes "codeAccess":"public"|"top_secret". top_secret = repo-gated (mitigation reviews, private comps) and unclonable. Filter codeAccess=="public" BEFORE attempting a clone or a GitHub-API 200 check. Saves wasted 404s and false "no repo" flags. Look for the analogous field on each platform (Sherlock private flag, Cantina visibility).

Rule 3: platform-lifecycle is a lead-quality signal

"Is this source still launching new work?" belongs in the scanner-onboarding checklist ABOVE LOC/primitive-fit. C4 is in announced wind-down (closing; existing comps/bounties seen to completion) -> new-contest flow is ending -> it is a DECAYING lead source. Action: poll at LOW priority / alert-on-appearance only; do not reprioritize fresh-lead budget toward it. Keep Sherlock + Cantina + Immunefi as primary fresh lanes.

Rule 4: structured-data extraction method (reinforces cycle216 list-vs-detail)

For contest enumeration, pull RAW HTML and parse deterministically: 1. curl the App-Router page (no auth/JS needed; RSC is server-rendered inline). 2. Unescape RSC quotes: u = raw.replace('\\"','"').replace('\\\\','\\'). 3. Brace-scan objects anchored on a stable key ("slug":"): back to nearest {, forward to the depth-0 }, json.loads. 4. Keep records containing endTime; read status/repo/formattedAmount/start/end/codeAccess. Use WebFetch (small-model summarizer) ONLY for prose, never for status/field extraction - it collapsed C4 to "no live audits, Page 1 of 10" and would have hidden the schema-literal trap.

C4-specific field map (if/when scanner wires it)

Endpoint: GET code4rena.com/audits. Per-record fields: title, slug, status, formattedAmount ("$$X in USDC"), startTime, endTime (ISO-Z), repo (github.com/code-423n4/), findingsRepo, league, codeAccess, auditType, hasMandatoryProofOfConcept, hasExtendedSeverities, contestId, uid. Detail page /audits/<slug> -> grep github.com/... for repo when RSC repo absent. Backend api.code4rena.com exists but no discoverable public /contests route - use SSR.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min