squad: security review + honest arrival denominator - #650
Open
nategeier wants to merge 4 commits into
Open
Conversation
Security (round 1 of the 2026-08-18 squad, Security lane):
- broker_execute now PROVES the wallet: a required wallet_signature
(personal_sign over deskExecuteConsentMessage(intent_id, wallet)) is
recovered server-side before any job row exists. Before this, any
unauthenticated agent could open an intent naming a stranger's wallet
+ any agent_key string and plant a needs-you job in that wallet's
Jobs rail ("swap … then send … to 0xATTACKER"). Consent is bound to
intent id + wallet, single-use (intent must be open). Drill + docs
updated; harness signs it and pins the three refusals.
- broker_send sender_label can no longer wear Pantessa-stamped marks:
leading "@" (claimed handles) and 0x… short-address shapes are
stripped, so "from @nategeier" can only ever mean the real handle.
Honest denominator (THE WEEK item 3 + coordinator/GTM ask):
- is_internal added (additive DDL run on Neon `yeetful`) to
intent_links, wallet_working_sets, jobs, chats; lib/internal-run.ts is
the one reader of the Q3 signal (x-yf-internal-run header /
internalRun body). Stamped at every write site: /api/intent-links,
/api/mosaics, /api/working-set (sticky-on), /api/jobs, /api/chats,
createJob (chat jobs, HL auto-fund, mosaic), the broker desk (handoff,
tile, send, execute — read off extra.requestInfo.headers), inbox sends.
Each route echoes `internal: true` so drills can assert.
- lib/gtm-arc.ts addrsUnion + arcQuery exclude stamped rows on every
table they read (chats/messages via the join, job_steps via jobs,
intent_link_events via their link); cohorts milestone CTEs match;
digest counts organic links only.
- test:api: global x-yf-internal-run on every BASE request (telemetry
keeps per-probe stamps; x-yf-organic-probe opts a probe out); pins:
stamped mint/write never arrives, organic write does (then re-stamped
sticky-on so the suite leaves no stranger), SQL-text pins, signal
reader pin, and an end-of-suite per-wallet "no throwaway left as an
arrival" pin (admin ?only= filter on /api/admin/cohorts).
- scripts/backfill-internal-arrivals.ts: dry-run default, --apply
owner-gated; flags legacy rows by the audit's heuristics (fixture-only
throwaway creators, bare {} working sets, dev-env jobs, fixture
wallets, receipts chats) + the #637 embed_turns predicate.
Gates: tsc + build + test:api 1470/1 known-red vs :3803.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…mps, rule-7 brand denylist, honest hero, idempotent HL delegation - is_internal (additive DDL run) on dca_schedules, broker_intents, ask_failures; stamped at their write sites (runDcaTurn + the period jobs it mints inherit; broker open/tile/send; ask-failure log + wallet-refusal beacon). gtm-arc/cohorts exclude dca_schedules; getAgentRecord excludes internal intents (a harness identity has NO public /agents record); /api/admin/ask-failures hides stamped rows by default (+ ?internal=1 toggle + label on /dashboard/failures). - lib/brand-denylist.ts (rule 7): POST /api/intent-links/brand refuses third-party financial brand hosts by name before any fetch (+ the post-redirect host); brandFromRow() is the ONE render-time reader on /l page + OG, /i page + OG — a denied stored row renders house (/l/yeet stops wearing Robinhood at deploy, data untouched). - LinksHero "Links live"/"Opens" go through is_internal (route is static; lands at deploy). Source-contract pin. - POST /api/hl/delegation is idempotent per wallet+chain within 20 min (same row + byte-identical typed data, reused:true); superseded pending rows are deleted, never accumulate. - Harness: per-wallet no-stranger pin also collects wallet-in-body chat turns; backfill script grows D1/D2/B1/A1 rules. Gates: tsc + build + test:api 1478/1 known-red vs :3803. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nategeier
added a commit
that referenced
this pull request
Aug 18, 2026
…s, live poll pill, internal rows hidden by default The ten-strangers drill has Nate watching /dashboard/failures and the per-link funnel while a recruit signs; GTM verified ?funded=1 was not read, nothing auto-refreshed, and useIntentLinks loaded once. - /dashboard/failures reads ?funded=1 / ?kind=wallet-refused / ?internal=1 on mount and writes them back on every toggle (replaceState) — a watch link opens on the right slice. New "wallet refused" + "internal" toggles. - components/LivePill.tsx: useLivePoll (visible-only interval, pauses when the tab is hidden, refires on return) + the "live · updated Xs ago" pill. Failures page polls quietly every 25s; useIntentLinks re-reads every 30s and exposes updatedAt — pill on the rail Links tab + /dashboard/links. - /api/admin/ask-failures: ?kind= filter, ?internal=1 opt-in; rows carry internal:boolean read from ask_failures.is_internal via a raw query that tolerates the column's absence (#650 adds it) — never a 500; stamped rows hidden by default, counted as internalHidden, tagged "internal" when shown. - test:api pins: ?kind slice + internal tag + ?internal=1 zeroes the hidden count; page source pin for the URL params + pill. Live-proven on :3801 as the burner (real SIWE via mock EIP-6963 + local signer): toggles write the URL, router.push with the query lights the toggles on mount, pill ticks on failures + /dashboard/links, a stamped drill row wears the tag only under ?internal=1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…esidual @/0x (QA bypasses), docs entity-space fix - QA adversarial pass on #650: "@@ @nategeier" survived a single-pass strip; fullwidth @ (U+FF20) was stored verbatim. cleanSenderLabel now NFKC-normalizes, strips @-/0x-shapes until stable, then REFUSES any label still carrying an at-sign of any variant or a 0x-hex run (the two Pantessa-stamped marks). Pinned pure + on the wire (both bypass strings land as the bare word; a mid-label @ is refused, never stored). - app/docs/desk: `<code>wallet_signature</code>{' '}—` (SWC entity-space class; composes with UI/UX's fence, same edit as QA's f2957a9). Gates: tsc + build + test:api 1479/1 known-red vs :3803. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
Round-2 follow-up (QA adversarial pass): |
nategeier
added a commit
that referenced
this pull request
Aug 18, 2026
…p on the ask-failures feed Both lanes added ?internal=1 to /api/admin/ask-failures + /dashboard/failures: #650 via the new typed is_internal column (hidden by default in the WHERE), #653 via a raw-SQL id read tolerant of the missing column + ?kind= slicing. Merged: #653's structure (kind filter, page toggles/tag/poll) on #650's typed column; raw reader retired; internalHidden counted server-side; response carries kind + internal + per-row internal. test-api: both appended check blocks kept. Duplicate 'internal' field in the page type dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lets (first_seen ≥ run start); cohorts honors ?days= 1..90 QA's integration run replays REAL wallets read-only through chat bodies; the pin treated every body wallet as throwaway and ?days=1 fell to 14d, so an organic 08-12 arrival read as a leak. Now: pre-run first_seen = replayed real wallet (skipped, never a leak); ?days= honored 1..90 (pinned: days=1 → windowDays 1, 999 → 14). Gates: tsc + build + test:api 1480/1 known-red vs :3803. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nategeier
added a commit
that referenced
this pull request
Aug 18, 2026
…s, live poll pill, internal rows hidden by default The ten-strangers drill has Nate watching /dashboard/failures and the per-link funnel while a recruit signs; GTM verified ?funded=1 was not read, nothing auto-refreshed, and useIntentLinks loaded once. - /dashboard/failures reads ?funded=1 / ?kind=wallet-refused / ?internal=1 on mount and writes them back on every toggle (replaceState) — a watch link opens on the right slice. New "wallet refused" + "internal" toggles. - components/LivePill.tsx: useLivePoll (visible-only interval, pauses when the tab is hidden, refires on return) + the "live · updated Xs ago" pill. Failures page polls quietly every 25s; useIntentLinks re-reads every 30s and exposes updatedAt — pill on the rail Links tab + /dashboard/links. - /api/admin/ask-failures: ?kind= filter, ?internal=1 opt-in; rows carry internal:boolean read from ask_failures.is_internal via a raw query that tolerates the column's absence (#650 adds it) — never a 500; stamped rows hidden by default, counted as internalHidden, tagged "internal" when shown. - test:api pins: ?kind slice + internal tag + ?internal=1 zeroes the hidden count; page source pin for the URL params + pill. Live-proven on :3801 as the burner (real SIWE via mock EIP-6963 + local signer): toggles write the URL, router.push with the query lights the toggles on mount, pill ticks on failures + /dashboard/links, a stamped drill row wears the tag only under ?internal=1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nategeier
added a commit
that referenced
this pull request
Aug 18, 2026
… refusal rows, "Confirm in your wallet…", /i exit ramps gated, eyebrow = the OG card Merge hygiene (QA r3): rebased onto feat/squad-security 4a222c0 and resolved the ask-failures overlap exactly as the integration branch did (8a91da0): #653's structure (?kind= slice, page toggles/tag/poll) on #650's typed is_internal column; the raw-SQL id reader is retired. - lib/wallet-refusal.ts walletErrorWords: the row's reply carries the wallet's ACTUAL words — viem wraps RPC failures so e.message leads with "An internal error was received."; the diagnosable line lives in .details / .data.message / .cause — first specific line wins. Used at every beacon site (SendTxButton, SignOrderButton, SignHlActionButton). - N1: the in-flight label is "Confirm in your wallet…" on every sign/arm button (was "Sign in wallet…" — read as a login). - N2: the /i header exit ramps (MAKE A LINK / OPEN THE APP) render only on receipt or flow-nudge — never above the card mid-flow. - N3: linkEyebrow (lib/intent-links) mirrors the OG card: From @handle · your wallet signs / Call by <agent> · … / Intent link · from <sender> · … (branded splash keeps · powered by Pantessa); the /i page passes the creator's claimed handle. No more "CALL · BY @handle". - Pins: walletErrorWords on viem/node/plain shapes; linkEyebrow pure + rendered creator/house eyebrows; N1/N2 source pins. 1513/1 known-red vs :3801 on the rebased branch (Security's pins included). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security lane, rounds 1+2 of the 2026-08-18 standing squad.
A. Security review — desk (#638/#644), inbox human-send (#645), HL delegated sign (#647)
Core still HOLDS (guarded builds, signature-gated money, no tx material over MCP, HMAC webhooks with SSRF fence, recover-verified HL consent + delegation activation). Fixed in-lane, each pinned:
broker_opennaming a stranger's wallet + anyagent_keystring, thenbroker_execute→ ajobsrow owned by that wallet, listed in their Jobs rail with a needs-you badge ("swap … then send … to 0xATTACKER"). Nowbroker_executerequireswallet_signature— personal_sign overdeskExecuteConsentMessage(intent_id, wallet)— recovered server-side before any row exists; bound to intent + wallet, single-use. Drill/docs updated."@nategeier").cleanSenderLabelstrips leading@and 0x-address shapes./l/yeetwas live wearing Robinhood from a scan drill).lib/brand-denylist.ts: POST /api/intent-links/brand refuses denied hosts (uniswap.org, cow.fi, robinhood.com, metamask.io, coinbase.com, aave.com, lido.fi, hyperliquid.xyz, opensea.io, snapshot, near, li.fi, morpho, wallets + venue policy hosts, incl. subdomains) by name BEFORE any fetch and again on the post-redirect host;brandFromRow()is the ONE render-time reader on /l page + OG and /i page + OG — a denied stored row renders house at deploy, data untouched.reused:true); superseded pending rows are deleted, never accumulate (QA's retry-spam finding).Accepted risks (rationale in
squad-2026-08-18/security.md): client-posted/ifunnel events can spoofsigned/settled(webhook / broker_status / inbox drop) — same trust class as first-party telemetry; intent ids as capabilities; per-IP-fenced funding scan; HL Guardian arm needs no getCode belt (approveAgent is an ECDSA-only artifact; PATCH recover-verify refuses ERC-1271 sigs before the venue).B. THE WEEK item 3 — honest arrival denominator
is_internal boolean not null default false(+ index) added on Neonyeetfultointent_links,wallet_working_sets,jobs,chats,dca_schedules,broker_intents,ask_failures(additive; done).lib/internal-run.ts= one reader of the Q3 signal.createJobfrom chat/HL auto-fund/mosaic, DCA schedules + the period jobs they mint, broker open/handoff/tile/send/execute viaextra.requestInfo.headers, inbox sends, ask-failure log + wallet-refusal beacon); routes echointernal: true.lib/gtm-arc.tsexcludes stamped rows on every table it reads (messages via chats, job_steps via jobs, events via their link, dca_schedules); cohorts milestones + digest + hero strip ("Links live"/"Opens" — static route, lands at deploy) +/api/admin/ask-failures(hidden by default,?internal=1+ label) match.x-yf-organic-probeopts out). Pins: stamped mint never arrives / organic write does (then re-stamped so the suite leaves no stranger); SQL-text pins; end-of-suite per-wallet "no throwaway (signed-in OR wallet-in-body chat) left as an arrival" via admin?only=on/api/admin/cohorts; harness identity has no /agents record; stamped refusal hidden by default.scripts/backfill-internal-arrivals.ts— dry-run default,--applyowner-gated.Owner-gated data steps (do not drop)
Dry run 2026-08-18 ~10:30Z: intent_links 3,410/3,518 (40 organic left) · wallet_working_sets 550/586 · jobs 235/308 (dev-env + fixture wallets) · dca_schedules 6/13 · broker_intents 258/324 · ask_failures 0/28 · chats 8/434 · embed_turns 1,959 (the #637 predicate). Wallet-level rules refuse TEST_WALLETS and any wallet that signed real money.
Clear the /l/yeet drill brand row (destructive → owner):
(
snapshotrow wears snapshot.org — also denied at render now; same UPDATE withhandle='snapshot'if wanted.)Gates
tsc + build + test:api 1478 / 1 known-red (router-select) vs
next start -p 3803.🤖 Generated with Claude Code