KeeperHub / KeeperHub/keeperhub
validate_workflow passes a write-contract node whose signer routing is unset
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 24
- Forks
- 93
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 266
Description
### Before filing
- [x] I searched open and closed issues for this behaviour.
- [x] I confirmed it still happens on the current `staging`, not only on an older checkout.
- [x] This is one problem, not several. (Several means several issues.)
- [x] This is not a security vulnerability (those go through private reporting).
Closest existing issues, checked and distinct: #2399 is read-side caller selection; #2014 is `workflowType` inference. #2004 is the nearest relative — same class of fault ("accepted and ignored"), different surface — and I think this belongs next to it rather than inside it, since that one is bounded to `simulate` on `/api/execute/*`.
### Reason: reproduction
```shell
# Workflow 1pyjp0c15z2h558jld8pn — one web3/write-contract node whose config is:
# {
# "actionType": "web3/write-contract",
# "abiFunction": "release",
# "network": "11155111",
# "contractAddress": "0x599869cef2e4c52e2c9074caaf8f9fb0cb191776",
# "integrationId": "xv7x4qalziyodoir6wyu4", <-- set
# "functionArgs": "[\"{{@trigger-1:HTTP.depositId}}\"]",
# "abi": "[{\"type\":\"function\",\"name\":\"release\", ...}]"
# }
# ...and NO web3Connection key at all.
# via the hosted MCP server, org-scoped kh_ key:
validate_workflow { "workflowId": "1pyjp0c15z2h558jld8pn" }
validate_workflow { "workflowId": "1pyjp0c15z2h558jld8pn", "deepCheck": true }
```
### Reason: what happened
Both tiers pass it:
```json
// fast tier
{ "ok": true, "result": { "valid": true, "nodeCount": 2 } }
// deepCheck: true
{ "ok": true, "result": { "valid": true, "nodeCount": 2 } }
```
No error, no warning. The node has no sender routing, and nothing says so.
This is confirmed in source, not only observed as a black box. At `staging` `f8c8f18`, neither tier references either field:
```
grep -c web3Connection lib/mcp/validate-workflow.ts -> 0
grep -c integrationId lib/mcp/validate-workflow.ts -> 0
grep -c web3Connection lib/mcp/validate-workflow-deep.ts -> 0
grep -c integrationId lib/mcp/validate-workflow-deep.ts -> 0
```
### Reason: what you expected, and what told you to expect it
I expected at least a warning that sender routing is unset.
Two sources in this repo told me `web3Connection` is the field that decides the signer:
1. `docs/plugins/web3.md:492` and `docs/api/workflows.md:194`, identically: *"Web3 Connection | `web3Connection` | Sender routing: `"default"` (org policy), `"eoa"` (force the Turnkey EOA), or `"safe:"`."* And `docs/api/workflows.md:173` carries `"web3Connection": "default"` in the canonical write-contract example.
2. `lib/safe/signer-resolver.ts:340` — *"Per-node Web3 Connection field, as persisted on `WorkflowNode.config.web3Connection`"* — and `resolveSigner` reads only that field.
So a node that sets `integrationId` instead has no routing. `integrationId` is also a real key elsewhere — `lib/mcp/workflow-schema-constants.ts:74` documents it as *"ID of the database integration"* — which is why it is reachable by mistake rather than obviously wrong, and why nothing complains.
### Reason: what it costs
An agent-authored workflow validates clean while its signer is decided by fallback rather than by anything in the workflow, and the gap is only observable at execute time.
I want to be precise about severity rather than overstate it: my organisation has exactly one web3 integration, so the fallback resolves to the wallet I intended and the misconfiguration is invisible. **I cannot demonstrate a wrong-wallet broadcast, because I have no second wallet to mis-route to.** That is the shape of the cost — it is latent until an org adds a second wallet or a Safe, and then it surfaces as a signing question at broadcast rather than a validation question at authoring time. `validate_workflow` exists to move exactly that class of question earlier.
### Where you saw it
Production (app.keeperhub.com)
### Version or commit
Production MCP requests on 2026-09-12. Source read at `staging` `f8c8f18` (2026-09-12).
### Scope: what this covers, and what it does not
**Covers** a validation rule for nodes whose sender is resolved through `web3Connection`.
**Checked and consistent:** the two docs tables agree with each other, the MCP tool descriptions, and `signer-resolver.ts`. The behaviour is missing only from the validator — I am proposing no change to routing, to defaults, or to `signer-resolver.ts`.
**Siblings — my main open question.** I have deliberately raised this as one rule rather than one node type, because the same config is signer-routed across more than write-contract. `isWriteActionType` covers `web3/write-contract`, `web3/batch-write-contract` and `*protocol-write*`; and `NON_CALLDATA_MUTATING_ACTION_TYPES` in `lib/mcp/action-type.ts` lists `web3/approve-token`, `web3/transfer-funds`, `web3/transfer-token` and the Tempo writes with the comment that they *"do genuinely broadcast a signed transaction from the org wallet."* I have not verified which of those `resolveSigner` is invoked for, so I would rather you name the set than have me guess at it and fix one surface out of four.
**Explicitly not covered:** whether the node schema should reject `integrationId` on a web3 write node outright. That is more breaking, and it can ship independently of a warning, so by the one-issue test it is a separate issue. Happy to file it if you want it.
**Also not covered:** `execute_contract_call`'s simulate path, which behaves correctly — a would-revert dry run returns `wouldRevert: true`, and it decodes custom errors whenever the caller's ABI carries their definitions.
### Plan: what should happen next
Additive, in the existing idiom of `lib/mcp/validate-workflow.ts`:
1. A new code in `lib/mcp/validate-workflow-codes.ts` — `MISSING_SIGNER_ROUTING: "missing-signer-routing"`. Additive, per that file's note that adding a code is safe and renaming is not.
2. A pure `runSignerRoutingCheck(workflow, warnings)`, called from `validateWorkflow` in spec order, flagging a signer-routed node with no `web3Connection` and setting `parameterPath` to the node's config. Where `integrationId` is present it is worth naming in the message, since that is the specific confusion — but the rule is "routing is unset", not "`integrationId` is banned".
3. Tests alongside the existing validator tests.
**Changes nothing a caller depends on** if it lands as a warning: `valid` stays `true`, and only the `warnings` array grows — which the codes file already says agents tolerate.
**On severity, I may be wrong and would rather be told.** I have drafted it as a warning so that workflows relying on the fallback today do not start failing validation. An error is defensible too, and so is gating it behind `deepCheck`. Per ISSUES.md I will treat a triage comment as the plan and build against that.
The rule and its tests are already written against `f8c8f18`, so I can have the pull request up the day this is accepted.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with lib/mcp/validate-workflow.ts, validate-workflow-codes.ts, and the existing validator tests; read lib/mcp/action-type.ts and lib/safe/signer-resolver.ts to identify which action types use signer routing. Confirm the maintainer’s decision on warning severity and the covered node set, then add the validation rule and tests so the expected warning, parameter path, and unchanged valid result are demonstrated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, blockchain
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 67/100