- Ya
- 27 Agosti 2026, 18:34 UTC
- Mwandishi
- Kamo
- Ahadi ya
- 1899050
POST **************** -- form-encoded or JSON, guarded by the CARRIER'S OWN SIGNATURE. Why a new endpoint. /api/voip/webhook cannot receive a STOP and fails silently: it readTree()s the body with no `consumes` restriction, every bulk-text carrier posts **************** the parse throws, the exception is swallowed and the carrier is answered 200 OK -- so it never retries. Confirmed in the current source before writing this. Why this path. **************** holds "/api/bulktext/send" and matches with startsWith, so anything under it demands an X-Internal-Auth header a carrier cannot send. /api/bulktext/inbound is a SIBLING of /send, is added to **************** and is deliberately NOT registered in InternalAuthFilter. Twilio signature: HMAC-SHA1 over the full URL (query string included) plus the POST parameters appended name-then-value in case-sensitive sorted order, base64, compared to X-Twilio-Signature with MessageDigest.isEqual. Verified against Twilio's security documentation, then pinned to the published vector from Twilio's own twilio-java RequestValidatorTest (token 12345 -> RSOYDt4T1cUTdK1PDd93/VVr8B8=), so the test cannot pass for a wrong algorithm. Modelled on MicrosoftTeamsProvider (blank secret -> log + false; exception -> false; constant-time compare); NOT on RingCentralProvider's `return true; // TODO`, and RingCentralSignatureUtil's equalsIgnoreCase is not reused. The signed URL is rebuilt from X-Forwarded-Proto/Host, not getRequestURL(). The edge terminates TLS, so getRequestURL() returns the container's own scheme and host, which is not what Twilio signed -- every signature would fail and the failure would look exactly like a forgery. FLOWROUTE IS PARSE-ONLY AND STILL REFUSES EVERY WEBHOOK. Flowroute publishes no webhook signature: its inbound-message docs specify the callback URL, the application/vnd.api+json content type and the payload shape, and no signature header, HMAC scheme or shared secret. There is nothing to verify, so it keeps the fail-closed default rather than getting an invented scheme -- an invented one would either reject all genuine traffic or be written to return true, which is the forgery surface this interface exists to prevent. Flowroute also does not post form-encoded, so `consumes` accepts vnd.api+json too. Sinch, Infobip and Vonage are unimplemented on both methods and log a warning naming themselves. TDD evidence ------------ Red (class absent): [ERROR] **************** cannot find symbol symbol: class BulkTextInboundController location: class **************** Green: Tests run: 27, Failures: 0, Errors: 0, Skipped: 0 Mutation 1 -- THE ONE THE PLAN NAMES, and it proves nothing. Plan Task 2 Step 8 says: make validateWebhookSignature default to `true` and aMissingWebhookSecretRejects must go red. Observed: Tests run: 24, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS Nothing went red. Every test drives Twilio, which OVERRIDES the default, so the fail-closed default was exercised by no test at all and could have been silently inverted at any time -- turning four carriers from "refuses everything" into "accepts anything" on an endpoint where accepting anything lets a stranger revoke any consumer's consent. Closed by adding **************** which pins the default on a bare implementation and on the four real carriers relying on it. Re-running the same mutation against it: **************** **************** [an unimplemented carrier must never accept an unverifiable webhook] Expecting value to be false but was true **************** **************** [SinchTextProvider must fail closed] Expecting value to be false but was true Mutation 2 -- the one that actually targets the plan's named test: Twilio's blank-authToken guard changed from `return false` to `return true`. Observed: **************** **************** expected: 403 but was: 200 **************** **************** Expecting value to be false but was true Mutation 3 -- the parameter sort reversed (TreeMap natural order -> Comparator.reverseOrder()), to show the algorithm test is not vacuous: **************** **************** Expecting value to be true but was false **************** **************** expected: 200 but was: 403 All three mutations reverted. Full suite after revert: Tests run: 290, Failures: 0, Errors: 0, Skipped: 0 / BUILD SUCCESS Known gaps, deliberate and logged at runtime **************** * A verified inbound message is recognised and LOGGED BUT NOT RECORDED. The hand-off to SmsKeywordService is a marked TODO because that class arrives in Task 3. This endpoint suppresses nothing yet. * Org resolution scans active instances and matches the to-number with ContactCanon.phone. There is no number inventory -- the only ownership record is the single nullable FROM_PHONE_NUMBER on BulkTextProviderInstance, with no finder and no index. No match, or a match across two orgs, attributes the message to nobody and logs; guessing a tenant would record a revocation against the wrong one, which is worse than not recording it. Needs ADD5/MS3. * InboundSms carries no carrier timestamp: Twilio's inbound SMS webhook does not send one. Task 3's "consentedAt comes from the carrier event, never now()" is therefore not satisfiable for Twilio. Refs: **************** Task 2