fix(hub): note kan het deny-citaat niet sluiten + bytegrens op de intake-context #181
No reviewers
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
janpeter/Scrum4Me!181
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/hub-ingest-hardening"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Twee van de drie securitypunten die bij de review van de AskUserQuestion-brug buiten scope vielen, plus een plan voor de derde laag. Het derde punt (plain
httpop loopback inisSecureHubUrl) blijft bewust ongewijzigd — zie onderaan.1. Een note kan het deny-citaat niet meer sluiten
userNoteReasoninscripts/hooks/hub-permission-hook.mjsomkadert JP's toelichting als geciteerde tekst. Wat de oorspronkelijke review niet vaststelde maar wel bepalend is: bijallowis die string alleen voor de gebruiker zichtbaar, bijdenygaat hij als reden naar Claude. Daar telt de markering dus, en daar had een note met een»erin exact het gat dat eerder inhub-askuserquestion-hook.mjsgedicht is — tekst die ná het citaat lijkt te staan.Zelfde fix, één regel: het sluitteken wordt geneutraliseerd (
»→>>), de markeringszin blijft ongewijzigd. Restrisico dat blijft: newlines in de note blijven newlines. Het kanaal is HMAC-gebonden en komt van het gekoppelde toestel, dus dit is verdediging in de diepte, geen gat.2. Bytegrens op
contextin de CUSTOM-intakecontextwasz.record(z.string(), z.unknown())— elke diepte, elke omvang. De hook begrenst zijn eigen descriptions al, maar dat is cliëntdiscipline; het schema is het enige dat óók toekomstige intake-bronnen bindt, en dat veld is expres generiek.Grens: 16 KiB, gemeten in bytes (dat is wat de database en het detailscherm werkelijk dragen, niet tekens). Ruim boven het zwaarste bekende geval: volledige vraagtekst + 8 descriptions van elk max 500 tekens ≈ 6 KB.
Eén koppeling om te kennen, die ook in de code als commentaar staat: de ask-hook laat een vraag tot 100 000 tekens toe en zet de volledige tekst in de context. Een vraag boven ~15 KB krijgt nu een 422, waarna de hook zwijgt en de vraag gewoon in de terminal verschijnt. Dat is de bedoelde degradatie, maar wel een gedragsverandering voor een pathologisch lange vraag.
3. Plan voor de request-size-guard
docs/superpowers/plans/2026-08-19-hub-ingest-request-size-guard.md— 4 taken, TDD, met exacte code.De eerlijke grens van fix 2: Zod draait pas ná
request.json(), dus een payload van 50 MB is al geparsed voordat hij wordt afgewezen. Het plan legt de grens daarom inwithIngestSecret— de helper die alle vier de ingest-routes al aanroepen — en laat die de body zélf begrensd lezen en teruggeven, precies zoalswithSignedDevicedat al doet metrawBody. Twee dingen die het plan expliciet benoemt in plaats van weglaat:Content-Lengthvertrouwen. Die header is optioneel (chunked) en door de cliënt te vervalsen; de guard telt de werkelijke bytestroom en gebruikt de header hooguit als goedkope voorcontrole. Er staat een test voor een leugenachtige header in.withSignedDeviceblijft onbegrensd (await request.text()). Die routes zijn device-ondertekend en de handtekening wordt pas ná het lezen gecontroleerd; dat omdraaien is een grotere ingreep en een apart voorstel. Task 4 meet het en legt het vast in het REST-contract.Het plan bevat ook een zusterbevinding die ik tijdens dit werk tegenkwam en bewust niet in deze PR heb opgelost, om binnen de goedgekeurde scope te blijven: de permissie-intake (
app/api/hub/permissions/route.ts) heefthost,sessionLabel,toolName,toolInputSummaryencwdals kalez.string()— onbegrensd, terwijl het zusterschema overal grenzen heeft. Dat is Task 3 van het plan, met grenzen die aantoonbaar boven liggen wat de hook vandaag verstuurt.Waarom
isSecureHubUrlongewijzigd blijftDe loopback-uitzondering op de https-eis is in productie ongebruikt (beide hosts wijzen naar
https://thuis.jp-visser.nl), maar hij is wél wat het testen van de hele keten zonder telefoon mogelijk maakt — het hookscript tegen een neptaub op127.0.0.1. Weghalen kost die testroute. De uitzondering bijt alleen op een host waarHUB_URLnaar loopback wijst én een andere gebruiker een poort kan bezetten; dat is geen van beide fleet-hosts. Wil je hem tóch expliciet maken, dan is een opt-in (HUB_ALLOW_INSECURE_LOOPBACK=1) de vorm die het testpad heel laat.Verificatie
TDD: eerst drie falende tests (RED aangetoond), daarna de fixes.
npm run verifygroen — 272 bestanden, 2201 tests (was 2197).npm run docsgroen, 224 docs geïndexeerd.APPROVED
geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.
Findings
Review-notities
app/api/hub/approvals/intake/route.ts:21introduceert een explicieteMAX_CONTEXT_BYTES-grens en valideert op UTF-8 bytes viaBuffer.byteLength(JSON.stringify(v)), met tests voor overschrijding, meerbyte-tekens en een normale hook-context.scripts/hooks/hub-permission-hook.mjs:526neutraliseert»in user notes voordat de note in geciteerde tekst naar Claude gaat; de toegevoegde test dekt dat de note het citaat niet meer kan sluiten.linked_planbij deze review aangeleverd en wordt daarom niet als acceptatiecriterium voor deze PR afgedwongen.