Skip to content

Host: strip client identity, hand the principal to the daemon over loopback, enforce the public route policy #3266

Description

@vkuprin

Part of #3264. ADR 0021 §5, §6, §10 item 6. Design D2 and D4 in the umbrella.

Blocked by: #3265

Purpose

A remote caller must not choose who it is, reach operator-only routes, name paths on the Host machine, or make the allocator download components. The daemon needs a trusted principal on every request so the lease side can record a requester that survives restarts.

Required behavior

  • The front-end drops every identity a client claims: the x-agent-device-tenant and x-agent-device-principal headers, params.meta.tenantId, params.flags.tenant, and params.tenant / params.tenantId on lease methods.
  • After authentication it adds x-agent-device-principal: <principal> to the outbound loopback request, using the principal from the service credential.
  • The daemon accepts that header only on HTTP requests that passed the daemon-token check. It validates the value with the tenant format, keeps it as daemon-internal state, and pins the request tenant to it with tenant session isolation. It never reads a principal from the body. With an auth hook configured, the header is refused with a typed reason.
  • /admin/* is not served (404). The policy runs through an admitRpc hook the proxy calls after authentication.
  • The front-end refuses these with HTTP 403, code UNSUPPORTED_OPERATION and a typed details.reason, never by matching error text:
    • host-admin-refused for lease.allocate on the host-allocated macos-app backend;
    • host-path-refused for inputs naming a Host path, batch steps included: the shared HOST_PATH_INPUT_KEYS declaration, positionals each command's schema names as paths, and path install sources without an upload. Only the client's exact temp locations for screenshots and recordings are accepted, and an upload id exempts only install and reinstall;
    • host-component-download-refused for allowDownload anywhere in a request;
    • host-script-refused for replay and test.
  • Anonymous GET /health returns only { ok, service: "agent-device-host", rpcProtocolVersion }. With the credential it returns the full payload, upstream included.
  • Plain proxy keeps its header allowlist, which never forwards x-agent-device-principal.

Completion conditions

One test per spoofing case from ADR 0021 §10 item 6:

  • a client principal header is replaced by the server principal;
  • a client tenant header is dropped;
  • body tenant fields are dropped and the daemon sees the server principal as the tenant;
  • /admin/leases and /admin/human-control/holds are not served;
  • macos-app allocation is refused, whatever its case or spacing;
  • Host paths hidden in URLs, upload ids, batch steps, trace/push positionals, launchConsole and planted /tmp files are refused;
  • a path install source and a Host-path flag get host-path-refused;
  • allowDownload gets host-component-download-refused;
  • anonymous health is minimal;
  • plain proxy does not forward x-agent-device-principal.

Dependencies

#3265 for the front-end process. The principal contract is shared with #3269. It is proposed in comments on #3264 and stays a proposal until the lease side agrees.

Example

POST /rpc                              (from the remote worker)
authorization: Bearer <service token>
x-agent-device-tenant: someone-else    (dropped)

POST http://127.0.0.1:<port>/rpc       (forwarded by Host)
authorization: Bearer <daemon token>
x-agent-device-principal: host-svc-3f9c2a1b

Activity

  1. added 2 commits that reference this issue on Oct 7, 2026
    3169801
    42741ec
  2. thymikee commented on Oct 7, 2026

    @thymikee
    Member

    On hold: we are integrating Simlock first and will revisit ADR 0021 / remote Host implementation afterwards. See the sequencing decision.

    The remote identity and public route policy are deferred until the Simlock integration works end to end. On resumption, we will settle the principal contract and the policy enforcement location against the actual integration and daemon admission rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions