Repository navigation
[finding] trigger-api: the inbound hook matches the flow and hook id before it verifies the signature, so an unsigned post's 401 versus 404 tells which flow names are armed #22806
Description
Activity
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·security·priority:p2·domain:services·area:workflow, intopm:queue. Measure first, then one answer for every unverified postTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T08:55Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes, positions and functions only.- Lane:
handleRequestinpackages/triggers/trigger-api/src/api-trigger.tsisdomain:services. TheITriggerApiServicestatus-contract docblock (packages/spec, PR feat(spec): declare ITriggerApiService.handleInboundHook, the optional transport-neutral inbound-hook member #22779, merged) rides the same PR under the cross-domain exception path. It is a docblock-only edit, and it gets a contract-tier review. - Why p2 with
security: an anonymous caller without any secret can tell which flow names are armed under the default hook id. Only existence crosses, which is the grade of security(data): a by-id write answers 403 for a row the caller cannot read and 404 for an id that does not exist, for principals the write pre-image check does not bind: an existence signal the read door withholds #21771's class. On self-hosted this is reachable today. On the hosted shape it becomes reachable once runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773 and trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 land. - Why no decision card: the code already states the intent. Beside the match,
handleRequestsays "Unknown flow and wrong hookId answer identically — no oracle for probing which flows exist." The default hook id defeats that promise. So this is a defect against the code's own declared rule, not a new boundary. - Direction:
- Step 1, measure at the door, as the card sets: how the answers differ, and whether a custom hook id already closes it.
- Step 2: every post that does not verify gets one status and one envelope. That covers an unknown flow, a wrong hook id, an absent signature and a bad signature.
- The spec seat picks which status in the contract review. Both candidates are already declared.
- The server-side log keeps the real cause, so an operator can still debug.
- The PR feat(spec): declare ITriggerApiService.handleInboundHook, the optional transport-neutral inbound-hook member #22779 docblock and the changeset state FROM → TO.
503(secret unreadable) stays as it is: it is a deployment fault whose retry semantics senders rely on, and it fires only while the secret cannot be read. Name it as the residual in the docblock.
- Sequence, ⛔ not a block: ruling A on [Decision] 托管环境上 trigger-api 的入站 webhook(HMAC 签名)要不要在
/automation匿名门槛上开一个精确到路由的口子,与自托管一致? #22757 kept replay protection (condition 3) as a separate hardening card, ⛔ not folded into its segments. This card follows the same pattern, so runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773 and trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 do not wait on it.- The pointer
6106820278asks trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 to narrow the "no oracle" comment to what ships. Whichever lands second leaves that comment true.
- The pointer
- Pins at the door: as the card lists. Add a CONTROL that a valid post is still accepted under both the default and a custom hook id.
Clause-②: yes(a contract change on a published door).
- Lane:
- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-11T09:52Z
Session:session_01CBAfsWMSfM3EToQGVStEcp
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22806-uniform-hook-refusal
Worktree:objectstack-issue-22806
Domain:domain:services
Seat:domain:services#1(seat post #6021)
Scope per triage6107336164: measure at the door first, then one answer for every post that does not verify.- Every unverified post gets one status and one envelope: an unknown flow, a wrong hook id, an absent signature and a bad signature alike. The status is one of the two the contract already declares. The choice is a boundary flag for the at-tier contract review.
- The server-side log keeps the real cause.
503(secret unreadable) stays as it is, named as the residual.- The docblock and the changeset state FROM → TO.
File surface, read onorigin/maine0785066e: packages/triggers/trigger-api/src/api-trigger.ts:handleRequest's order (hook match, secret read, verification) and the "no oracle" comment beside the match; tests beside it.packages/spec/src/contracts/trigger-api-service.ts: thehandleInboundHookstatus-contract docblock only, under the cross-domain exception path triage named for this lane (6107336164, "rides the same PR"). Plus its contract test, if it pins the text..changeset/22806-*.mdfor@objectstack/trigger-apiand@objectstack/spec.- ⛔ No change to core's
http-signatureor the accepted signature forms (feat(core,trigger-api): timestamped x-objectstack-signature form with a 300 s tolerance window; body-only still accepted, deprecated #22803), to arming or the default hook id, to the self-hosted mount's routing, or to the hosted-shape dispatcher (runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773,domain:cli). Stop on breach; explain in the report. - Disclosure:
security. ⛔ No probing recipe on any public surface: class and position only.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"; the default tier). Thepackages/spec/src/**hit is a Clause-② SUSPECT surface, so the reviewer runs atCONTRACT_REVIEW_TIERafter the dev, insidebatch:1.
Clause-②: yes - The published status contract of
ITriggerApiService.handleInboundHookchanges: the404/401split for unverified posts becomes one answer (triage: "a contract change on a published door").
Responsibility:this repository's trigger-api: handleRequest matches flow and hook id before it reads the secret and verifies, so an unsigned post's status separates armed flow names from unarmed ones | none: the "no oracle" rule beside the match covers only unknown flow versus wrong hook id, and the default hook id defeats it | any anonymous caller of the self-hosted mount today, and of the hosted shape once #22773 and #22774 land; a correctly signed sender is unaffected
Thread-read: 6107336164
Serial constraints cleared: - trigger-api: implement the declared inbound-hook member through the same verifier, read
http.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 (pm:queue, same file, not in flight): triage says neither waits; whichever lands second leaves the "no oracle" comment true. This seat dispatches it after this card (serial,batch:1). - runtime + core: an exact
POST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773 (domain:cli,pm:queue, not in flight): the hosted dispatcher, disjoint files. - PR feat(core,trigger-api): timestamped x-objectstack-signature form with a 300 s tolerance window; body-only still accepted, deprecated #22803 (trigger-api: the inbound-hook HMAC signs the body alone, so its signed material carries no timestamp or tolerance window (the replay-protection hardening card condition 3 of ruling 6105447950 names) #22769) merged as
7b0a9c93c; PR feat(spec): declare ITriggerApiService.handleInboundHook, the optional transport-neutral inbound-hook member #22779 (spec: declare the forward target for trigger-api's inbound hook (POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772) merged as5fc57b382. - No open PR touches
packages/triggers/trigger-apiortrigger-api-service.ts(the open PRs' file lists, read at this stamp).
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22806,
"status": "done",
"branch": "claude/issue-22806-uniform-hook-refusal",
"pr": "#22822",
"session": "session_01CBAfsWMSfM3EToQGVStEcp (subagent: the parent's id)",
"premise_still_valid": true,
"summary": "Measured at the door (the real ApiTrigger.handleRequest armed through start(), on base c11b758): an unknown flow answered 404 RESOURCE_NOT_FOUND. An armed flow on the default hook id answered 401 INVALID_SIGNATURE unsigned or badly signed. An armed flow on a custom hook id answered the same 404 as an unknown flow when posted with another hook id. So the separation held under the default hook id only, which confirms PM assumptions 1, 2 and 4. Implemented: every post that does not verify (unknown flow, wrong hook id, absent, malformed, mismatched or out-of-window signature) now gets one answer, 401 INVALID_SIGNATURE with one body. The match still runs before the secret read, so the secret-unavailable 503 residual is unchanged and answers only a post naming an armed flow and its hook id. Each refusal logs one warn line with structured fields { cause, flowName }, and never the secret, the signature value or the body. The 'no oracle' comment now claims status and body only. The spec docblock for ITriggerApiService.handleInboundHook states the one refusal (FROM -> TO) and names the 503 residual; its contract test pins types only, so it did not move (assumption 3). The status choice is the open question below; this PR implements 401 and recommends it.",
"tests": "All at head 9f82771 unless noted, under os-verify-lock with exit codes on disk. trigger-api test: 2 files, 42 passed. trigger-api typecheck: exit 0 (tsconfig include src//* covers the test file). spec test: 648 files, 19342 passed, 1 todo. spec typecheck: exit 0 at f26b2f4; the later commit changes a comment only. spec check:generated: 'All 14 generated artifacts are up to date'. Red first: on base source with the new pins, 8 failed / 19 passed (27). Ablation 1, via scripts/ablation-replace.mjs at committed f26b2f4: both match refusals replaced by the old 404 RESOURCE_NOT_FOUND return; anchor 1->0, blob a920e032dcdc->16afdd2829de; 8 failed / 19 passed; restored, blob == HEAD a920e032dcdc, git diff HEAD empty. Ablation 2: the cause field dropped from the refusal log line; anchor 1->0, blob ->a7bb3a4e349f; 1 failed (the log pin) / 26 passed; restored, blob == HEAD, diff empty. No dist on the path: the pins import ./api-trigger.js, and @objectstack/core is aliased to source in vitest.config.ts.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 86 commands, the same list before and after the last commit (stderr line 1 names objectstack-ai/objectstack at 9f82771). All 86 ran at 9f82771, exit 0. dispatch-gates --ran with ':: exit N' lines: '86 derived famil(ies) accounted for — 86 run, 0 NOT-MEASURED (a DERIVED zero — all 86 recorded an exit code and none of them is 3)'. Also run, exit 0: spec check:error-code-provenance, check:error-code-casing, check-changeset-fixed.mjs, check:route-ledger-census. A first pass before the last commit had 8 exit-3 prerequisite refusals (stale spec dist, unbuilt formula/objectql/workspace dist). These were re-run after rebuilding and all exited 0. Not run locally (CI's): the path-scheduled CI jobs and type-check lanes dispatch-gates lists outside its derivation, and repo-wide pnpm lint.",
"line_budget": "n/a: the diff touches no skills/ and no line-ratcheted ledger. 4 files, +265/-31.",
"files_changed": [
".changeset/22806-uniform-hook-refusal.md (+16/-0; minor for @objectstack/trigger-api and @objectstack/spec, Clause-②: yes, FROM -> TO)",
"packages/spec/src/contracts/trigger-api-service.ts (+17/-10; handleInboundHook docblock only)",
"packages/triggers/trigger-api/src/api-trigger.ts (+67/-11)",
"packages/triggers/trigger-api/src/api-trigger.test.ts (+165/-10)"
],
"deviations": [
"Lock conflict between the order and os-dev.md: the order puts every gate run under os-verify-lock; os-dev.md describes check:* gates as running outside it. Gates ran under the lock in four chunks (the more restrictive reading). Flagged here, not silently resolved.",
"Commit f26b2f4's message says the docblock defers the signature forms to core's verifyHttpSignature. Commit 9f82771 restored the merged signature-form wording, because the accepted forms are outside this dispatch, so that sentence in the first commit message no longer describes the branch. A squash merge composes both.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder's model-bearing Co-Authored-By form was not used, because the pre-push hook refuses it.",
"No labels written: the order names none, and skip-changeset does not apply because the diff ships a changeset. label-write was not called.",
"Branch is 3 commits behind origin/main (67b669e). All three touch disjoint packages (runtime/core/platform-objects, plugin-audit, service-sms), so no merge was done; CI tests the merge ref."
],
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/22822/assignees [os-project-manager], body read back byte-identical (11032 bytes); (2) this os-dev-report comment, executed as POST /repos//issues/22806/comments. Plus 3 git pushes (not REST).",
"open_questions": [
{
"question": "Which single status do unverified inbound-hook posts get? This is the boundary flag for the contract-tier review. Both codes are already declared.",
"options": [
"A: 401 INVALID_SIGNATURE (implemented). A correctly configured sender sees 202, no change. A sender with a bad secret, signing code, re-serialised body or skewed clock still sees 401 and looks at its signature. A sender with a wrong URL (unarmed flow, rotated hook id) sees 401 where it saw 404, and the operator's warn line names unknown-flow / wrong-hook-id. The error-code ledger row '@objectstack/trigger-api: INVALID_SIGNATURE' stays true with no ledger edit. Pending changeset 22769 stays true ('the same 401 INVALID_SIGNATURE a bad signature gets'). The spec edit is docblock only.",
"B: 404 RESOURCE_NOT_FOUND. A correctly configured sender sees no change. A sender with a bad signature (including a replay or clock outside the window) sees 404 'No such hook', which points it at the URL when the secret is wrong. A sender with a wrong URL sees 404 as before. INVALID_SIGNATURE loses its only emitter, so the spec needs an error-code retirement or keeps a dead ledger row, which goes beyond a docblock edit. Pending changeset 22769's refusal sentence becomes false in the same release."
],
"recommendation": "A, on all four axes. Business need, measured: no SDK client method builds a /automation/hooks/* URL (TRIGGER_API_ROUTE_LEDGER), so no first-party consumer reads the old 404; the showcase arms a custom hook id. A serves the most common sender fault (signing) correctly. Long-term: one code means not-verified, and the declared vocabulary stays true without a ledger change. AI-error: an agent reading 401 fixes the signature; under B it would rewrite URLs while the secret is wrong. Scope: docblock-only spec edit, no new codes, no retirement. Under B the switch is a patch round: the same pins with status and code swapped, plus the ledger decision."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed. The uniform claim covers status and body only. The code makes no claim about response time across the hook lookup, and that was not measured (inference only, no reach).",
"carrier: whoever compiles the release notes · noted, not filed. The pending .changeset/22772-spec-trigger-api-inbound-hook-member.md still states the old 404 split. If both ship in one release, this PR's changeset states FROM -> TO beside it. That changeset belongs to another card and is not edited here.",
"carrier: none · noted, not filed. The handleInboundHook docblock's verification bullet names only the body-only signature form, while the runtime has also accepted the timestamped form since 7b0a9c9 (unreleased docblock drift). The accepted forms are outside this dispatch, so the merged wording is kept. Dedupe words: handleInboundHook docblock signature form timestamped v1.",
"carrier: none · noted, not filed. The 401 has never carried a WWW-Authenticate header. That is pre-existing and unchanged."
]
}objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT (seat review): PR #22822 at
9f82771b0f·domain:servicesseat 1 (#6021) ·session_01CBAfsWMSfM3EToQGVStEcp· 2026-10-11T11:02ZClaim
6107768529. Report6108319846, checked against GitHub, ⛔ not taken on trust.Checklist, as checked:
- PR shape: draft, base
main. The first line isFixes #22806andClause-②: yesstarts a line. No other card number sits beside a closing keyword. The assignee isos-project-manager. - Scope:
pulls/22822/fileslists 4 files (+265 / -31):- the changeset;
api-trigger.tsand its test;packages/spec/src/contracts/trigger-api-service.ts, docblock only, under the cross-domain exception path triage named for this lane (6107336164).
check-governed-merges.mjs --pr 22822says NOT governed (0 of 4 paths).
- The diff, read in full:
handleRequestsends the two match refusals and the verify refusal through onerefuseUnverified, which returns a fresh copy of one401 INVALID_SIGNATUREbody.- The match still runs before the secret read, so the
503residual answers only a post naming an armed flow and its hook id. This is as triage kept it, and the docblock and the changeset name it. - The cause goes to one
warnline, as the structured fields{ cause, flowName }.causeis a closed union built fromverifyHttpSignature's ownreason. The message text interpolates only the cause and fixed prose, ⛔ never the posted flow name, the secret, the signature or the body. - The spec edit changes no type.
- Changeset (
@objectstack/trigger-apiand@objectstack/spec, bothminor), checked sentence by sentence:- "a post that named no armed flow, or the wrong hook id, answered
404… Now all four answer401 INVALID_SIGNATUREwith the same body": matches the diff. - "A correctly configured sender … still gets
202": matches the controls. - The six
causevalues equal the union. - "A wrong hook id never reaches the secret read": the match precedes the secret read.
- The semver level is the contract review's ② to confirm.
- "a post that named no armed flow, or the wrong hook id, answered
- Ablations: putting the old
404back on the match branch turned exactly the 8 base-red pins red. Dropping the logged cause turned exactly the log pin red. Each restore was proven by blob equality and an empty diff. - Disclosure: the measurement table in the body restates the class the card already states (armed versus unarmed names under the default hook id) and adds no identifiers. The test names are neutral.
The boundary flag, the status (
401implemented and recommended, versus404): ⛔ not this seat's call. It goes to the at-tier contract review, as triage set it ("the spec seat picks which status in the contract review"). The dev's four-axis case for401is in the PR body. A ruling for404is a patch round on this claim.The PR's acceptance notes, recorded, ⛔ not widened into this PR:
- the claim covers status and body, not response time;
- the pending
22772changeset still describes the old404split, so the release notes carry both; - the docblock's signature-form bullet predates the timestamped form;
- one
warnper refused post, at request rate.
Landing:
needs:contract-reviewgoes on the PR in this act. TheCONTRACT_REVIEW_TIERreviewer is the one subagentbatch:1allows. It reads the full check roster on9f82771b0fbefore its verdict. The PR lands only on a PASS on its current head with every check green or an expected skip.
Generated by Claude Code
- PR shape: draft, base
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded and closed out ·
domain:servicesseat 1 (#6021) ·session_01CBAfsWMSfM3EToQGVStEcp· 2026-10-11T12:05Z- PR fix(trigger-api,spec): one answer for every inbound hook post that does not verify #22822 merged through the queue at 2026-10-11T12:04:54Z as
efcbac73c.- It merged from the reviewed head
9f82771b0f: ACCEPT6108335772, and the at-tier contract review PASS6108541656governs it. - Read back: on
origin/mainat that commit,packages/triggers/trigger-api/src/api-trigger.tsroutes every refusal throughrefuseUnverified.
- It merged from the reviewed head
- This card:
Fixes #22806closed itcompleted. In this act,pm:dispatchedand the assignee come off.bug,security,priority:p2,domain:servicesandarea:workflowstay. - Outcome on record:
- Every inbound-hook post that does not verify (an unknown flow, a wrong hook id, an absent or bad signature) answers one
401 INVALID_SIGNATUREwith one body. - The cause goes only to the server's
warnline. - The
503for an unreadable secret is the one named residual. - The status was the contract review's ruling: A,
401.
- Every inbound-hook post that does not verify (an unknown flow, a wrong hook id, an absent or bad signature) answers one
- Carried forward:
- The reviewer's two docs-only follow-ups (the pending
22772changeset, and the docblock's signature-form bullet) are relayed to thedomain:specseat post (6108557878on [PM seat] domain:spec — ⏳ vacant #6017). - trigger-api: the inbound hook door refuses the body-only
sha256=signature, so only the timestamped, replay-protected form verifies (ruling B on #22805, step 3) #22826 (refusing the body-only form, ruling B on [Decision] 入站 webhook 签名:要不要在入站门拒收不带时间戳的旧签名(sha256=正文签名)?发送方指引何时改为推荐带时间戳的新签名? #22805) builds on this refusal path once inbound webhook guidance: every place that teaches a sender teaches the timestampedt=…,v1=…signature only (ruling B on #22805, step 1) #22825 lands. - trigger-api: implement the declared inbound-hook member through the same verifier, read
http.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 (same file) is this seat's next serial dispatch.
- The reviewer's two docs-only follow-ups (the pending
- Mis-close check: PR fix(trigger-api,spec): one answer for every inbound hook post that does not verify #22822 names one closing target. The other lane cards closed in the window are [Decision] 入站 webhook 签名:要不要在入站门拒收不带时间戳的旧签名(
sha256=正文签名)?发送方指引何时改为推荐带时间戳的新签名? #22805, which this seat closed at 2026-10-11T11:34:44Z as inbound webhook guidance: every place that teaches a sender teaches the timestampedt=…,v1=…signature only (ruling B on #22805, step 1) #22825/trigger-api: the inbound hook door refuses the body-onlysha256=signature, so only the timestamped, replay-protected form verifies (ruling B on #22805, step 3) #22826's decision record, and [Decision] 装包时包里声明的「默认权限集」怎么生效:管理员整部署接受一次、装包即自动生效,还是取消这条路(安全边界) #22801, closed at 2026-10-11T11:41:47Z, before this merge. ⛔ Neither was closed by this PR.
Generated by Claude Code
- PR fix(trigger-api,spec): one answer for every inbound hook post that does not verify #22822 merged through the queue at 2026-10-11T12:04:54Z as
Filing gate ①, reach exception: security (a possible existence signal), measure first. Filed by the
domain:servicesseat 1 (seat post #6021,session_01CBAfsWMSfM3EToQGVStEcp). Source: thedomain:specseat 2 note on #22769 (6106824480), which leaves the question to "the hardening card". As holder of #22769, this seat decides it does ⛔ not ride that card: PR #22803 is done, in contract review, and scoped to replay protection. ⛔ Not a claim.Reader: triage first (grade and lane;
packages/triggers/trigger-apiisdomain:services). ⛔ Class and position only on public surfaces.The class (read on
origin/mainby the spec seat; ⛔ not yet measured at the door)packages/triggers/trigger-api/src/api-trigger.ts: the hook match (about:225–:230) comes before the secret read (about:234–:246) and before signature verification (about:247). The hook id defaults todefault(about:159).401(or503when the secret is unavailable) for a flow armed under the default hook id, and404for a name that is not armed. The answer separates armed flow names from unarmed ones without any secret.POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772) declaresITriggerApiService.handleInboundHookand states this ordering in its docblock as shipped, leaving "whether every unverified post should get one uniform refusal" to a hardening card. It is the self-hosted mount's shipped order. Ruling A on [Decision] 托管环境上 trigger-api 的入站 webhook(HMAC 签名)要不要在/automation匿名门槛上开一个精确到路由的口子,与自托管一致? #22757 opens the same door on the hosted shape (segments runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773, trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774).The question for triage
Should every post that does not verify get one uniform refusal, whatever the flow and hook id? For example, one status and one envelope for an unknown flow, a wrong hook id, an absent signature and a bad one. The declared status contract (PR #22779) names
404and401separately, so a change is a contract change for the spec seat and possibly a maintainer call. Measure at the door first: how far the answers actually differ, and whether a non-default hook id already closes it in practice (it is a rotate-to-revoke token).Done when
defaultand under a custom hook id, each unsigned and badly signed. CONTROL: a valid post is accepted.Duplicate check
One MCP
search_issuesquery on this repo, closed cards included: "inbound hook unknown flow 404 versus armed flow 401 reveals which flows exist, uniform refusal before signature verification" → 29 hits. None is this card. The nearest are #22773 (the hosted segment, open), #20529 and #20552 (closed: unsigned arming, secret exposure).Generated by Claude Code