Add remote HTTP approval provider - #1058
Conversation
There was a problem hiding this comment.
Important
The HTTP approval path needs complete request binding and resilient event-channel behavior before it is safe to merge. The new security and tunnel paths also need direct tests.
Reviewed changes in the single commit adding remote HTTP approvals and forwarding their pending notifications to remote tunnel clients:
- HTTP approval provider: Posts request metadata, polls same-origin status URLs, validates response shapes, and verifies Ed25519 decision proofs.
- CLI configuration: Adds approval endpoint and public-key options, Bearer-token configuration, provider selection, grant integration, and child-environment credential stripping.
- Remote notifications: Adds an authenticated tunnel event connection and broker-side notification fan-out.
- Documentation and release metadata: Documents the service protocol and records a minor
varlockrelease.
⚠️ Security-sensitive paths have no direct coverage
None of the new HTTP provider, signature-verification, or approval-event behavior is exercised by this PR. Existing proxy tests pass, but they cannot detect malformed response acceptance, an incorrectly constructed signed payload, URL/origin regressions, or Node/Bun event-channel lifecycle failures.
Technical details
# Add direct protocol and tunnel coverage
## Affected sites
- packages/varlock/src/proxy/approval-http.ts:1-267 — entirely new security-sensitive provider without tests
- packages/varlock/src/proxy/tunnel.ts:951-1025 — entirely new event client without tests
- packages/varlock/src/proxy/tunnel.ts:405-410 — new event server branch without tests
## Required outcome
- Exercise accepted and rejected signatures, every signed request field, nonce and expiry handling, lifetime parsing and clamping, URL scheme and status-origin restrictions, redirects, malformed responses, polling, and timeout behavior.
- Exercise authenticated event delivery and cleanup in both Node and Bun paths, plus unsupported brokers and post-ready disconnects.azure/gpt-5.6-sol | 𝕏
| req.expiresAt, | ||
| req.ruleId ?? null, | ||
| req.grantKey ?? null, | ||
| req.maxDurationMs ?? null, |
There was a problem hiding this comment.
The approval protocol signs path and the existing grantKey, but neither includes the query string or request headers. Since secrets can be substituted in both surfaces, a remote approver cannot see that context and an each: request session/duration approval for GET /resource?action=read can auto-approve GET /resource?action=delete; include the relevant request surfaces in the approval context and exact-request grant identity before signing.
Technical details
# Bind reusable approvals to the complete request
## Affected sites
- packages/varlock/src/proxy/approval-http.ts:117-132 — the signed payload omits query and headers
- packages/varlock/src/proxy/runtime-proxy.ts:1356-1365 — the provider receives `t.pathOnly`, while `t.requestTarget` contains path plus query
- packages/varlock/src/proxy/approval.ts:121-127 — `each: request` computes its grant key without query or headers
## Required outcome
- Give the approval service enough safe request context to distinguish security-relevant query/header changes.
- Ensure an `each: request` standing grant cannot match a future request whose query or relevant headers differ from the approved request.
- Include the added context in the signed decision payload.|
|
||
| // Keep an authenticated event channel open before starting the child so an | ||
| // approval URL cannot be missed if its first request is immediate. | ||
| const approvalEvents = await subscribeTunnelApprovalNotifications({ |
There was a problem hiding this comment.
This awaits the auxiliary event socket unconditionally before spawning the child. A new client against any pre-feature broker gets its events message rejected and aborts even though bootstrap and data tunneling succeeded; negotiate event support or degrade notification support without breaking otherwise valid remote runs.
Technical details
# Preserve remote tunnel compatibility
## Affected sites
- packages/varlock/src/cli/commands/proxy.command.ts:2037-2048 — event subscription failure tears down startup
- packages/varlock/src/proxy/tunnel.ts:405-413 — a broker without event support closes the connection
## Required outcome
- Do not make an optional notification capability a mandatory dependency for the existing remote data path.
- Provide explicit capability negotiation so a supported HTTP-approval broker can still require successful notification setup before starting the child.| clearTimeout(timer); | ||
| reject(new Error('approval event channel closed before it was ready')); | ||
| } else if (!closed) { | ||
| opts.onError?.(new Error('approval event channel closed')); |
There was a problem hiding this comment.
After readiness, a close only emits a warning and permanently removes this client from the broker hub. Idle WebSocket timeouts or transient disconnects therefore leave the child running, but its next approval link is lost and the request waits until expiry; reconnect and resubscribe for the child lifetime, or terminate/degrade in a way that cannot silently strand approvals.
Technical details
# Recover the approval event channel
## Affected sites
- packages/varlock/src/proxy/tunnel.ts:1007-1022 — post-ready errors and closes have no recovery path
- packages/varlock/src/proxy/approval.ts:81-96 — notification fan-out has no replay buffer
- packages/varlock/src/cli/commands/proxy.command.ts:2040-2041 — the caller only prints the error while the child continues
## Required outcome
- Keep notification delivery available after transient or idle disconnects for as long as the proxied child runs.
- Avoid losing a notification during reconnection, or fail the affected run promptly instead of waiting for approval expiry with no actionable URL.|
You are seeing into the future :) This is exactly what we are building up towards. First approvals on the device itself (especially on Mac with a fingerprint reader) - then cross device with a blind relay hosted on cloudflare as you were playing with. Plan is that this will be a hosted service we provide, along with some identity and device management. But could potentially make it open - so will think that through with this PR in mind. |

Hey together,
thanks a lot for building Varlock. Makes my life a lot easier when working with secrets and agents. And also thanks a lot @theoephraim for quick reactions (cloudflare/workers-sdk#15256). Hope it gets merged soon.
But now to the roadblock I hit. Lately I am using remote dev machines, which means my normal secret approval flows with varlock dont work there. So i gave codex some homework to research and it found you guys (of course) already thought about the problem but the final implementation is not done yet.
So I gave it some thought and had codex work on it. Reviewed everything and tested it.
Would love to get your thoughts on it, if this is going in the right direction I am happy to give this some more work as this is currently one of my biggest pains.
I did not yet add any tests, as I wanted to hear your thoughts. But will happily add some if this is going in the right direction.
Below some thought I wrote together with codex:
Remote HTTP approval provider
Varlock already supports request-bound approvals, lifetimes, and grants. This change makes that approval mechanism usable when the operator is not at the broker terminal.
What we added to Varlock
ApprovalProvider.HttpApprovalAuthinterface, with Bearer authentication as the initial CLI implementation.ApprovalDecisionVerifierinterface, with Ed25519 as the initial implementation.ApprovalPendingNotificationcontaining the approval URL and safe request metadata.proxy run --urlcan display that notification remotely.The existing terminal provider remains the default.
HTTP approval flow
When an existing
require-approvalrule matches:ApprovalRequestto the configured service.approvalUrland broker-facingstatusUrl.Security decisions
The approval URL may be visible to the agent, so possession of it cannot authorize a decision. Operator authentication belongs to the approval service.
The Bearer token and Ed25519 key have separate roles:
The signature binds the request method, verified host, path, body hash, nonce, expiry, rule, grant scope, secret names, and selected lifetime. A decision therefore cannot be reused for a different request or widened beyond what was approved.
Errors, redirects, malformed responses, invalid proofs, nonce mismatches, and timeouts fail closed. HTTPS is required outside loopback, and the status URL must use the configured service origin.
Remote notification
The existing tunnel continues carrying proxied traffic. We add a separate authenticated event connection that carries only pending-approval notifications.
The remote client can display the approval link but cannot submit a decision through the tunnel. Decisions still travel directly between the broker and approval service.
What we reuse unchanged
ApprovalRequestandApprovalProvidermodel.once,session, anddurationlifetimes.require-approvalbehavior.Reference service
We also built a separate Cloudflare Worker to validate the protocol. It stores pending approvals in Durable Objects, authenticates the operator with WebAuthn, and signs approved decisions with Ed25519. It is one possible implementation of the generic HTTP approval contract.