Skip to content

Bind caller identity to call context, not request arguments #1443

Description

@asim

Foundation for the micro/xyz services. Partially landed in 1f6289f5.

The problem

Account identity travelled as an account_id request argument, so it was only as trustworthy as the caller. Nothing in the call path enforced it — it was enforced by a wrapper at each boundary where untrusted input entered, and there were two independent implementations:

  • apps/sdk_service.go — the app SDK over HTTP
  • agent/native.go:injectAccount — the agent's tool calls, guarding against prompt injection in tool content

Both correct. That was the point: the same rule implemented twice, in two packages, because the argument guaranteed nothing on its own. A third caller — a service calling another service — would have needed a third copy, and the failure mode of forgetting is silent cross-account access to mail, index, images, events.

There was no live hole: every other service.Call site is first-party Mu code building typed requests.

Done (1f6289f5)

  • service.WithAccount(ctx, id) / service.AccountFrom(ctx) — identity on go-micro call metadata. Verified to propagate over the in-process transport before anything was built on it. An empty id clears inherited identity, so a guest request cannot borrow the previous caller's account.
  • CallDynamic is the single enforcement point. It discards args["account_id"] and re-stamps from context, so no dynamic caller can scope a call to someone else by naming them.
  • The SDK boundary drops its own scrub and just sets the context.
  • Tests cover: forged account_id overwritten, guest cannot claim an account, identity reaches the handler when the caller passes nothing.

The agent still sets the request field as well as the context, deliberately — its tool calls are dispatched by go-micro straight to the service rather than through CallDynamic, so the field is what today's handlers read.

Remaining

  • Migrate the account-scoped handlers (mail, index, images, events) to read service.AccountFrom(ctx) instead of the request field.
  • Once migrated, stop setting the field in injectAccount and drop AccountID from those request structs, so the value has one source.
  • Account-scoped storage for services, built on the context identity. The go-micro store Mu wires is flat (internal/service/service.go:95, store.NewFileStore into $HOME/.mu/store) — no namespace, no owner, no encryption, unlike internal/userdb. This is what the personal services need (reminders, ledger, notes, habits, contacts) and it must be settled before the service contract in micro/xyz Add full markets page at /markets with extended market data and persistent caching #7, or shipped services need their data migrated.

Blocks: micro/xyz #7 · related #1444, #1445

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions