Skip to content

Loopback HTTP API publishes the action token and does not pin Host #34

Description

@ascendedent

Summary

The Token Meter server binds 127.0.0.1:8722 with no authentication. Mutation POSTs are gated by X-Token-Meter-Action, but that token is generated once at process start and then returned in ordinary GET JSON (/state, /session, /updates/status, /menubar).

GET handlers do not check Host or Origin. POST origin checks are skipped when the Origin header is absent:

origin = self.headers.get("Origin") or ""
if origin and (urlparse(origin).hostname or "") not in ("localhost", "127.0.0.1", "::1"):
    ...

Impact

Anyone who can speak to loopback can read session metadata, costs, project names, and the live action token, then call:

  • POST /session/delete (move discovered .jsonl logs to Trash)
  • POST /agent-access/toggle (add/remove the tokenmeter MCP entry in Codex/Claude)
  • POST /capability/toggle and /capability/disable-unused (rewrite Claude/Codex plugin settings)
  • POST /updates/install (fetch + reinstall)

That includes every local UID on a shared machine (curl 127.0.0.1:8722/state). DNS rebinding is also in play because there is no Host allowlist: GET is enough to exfiltrate the token and dashboard JSON. Classic HTML-form CSRF is mostly blocked by the JSON content-type requirement; local processes and rebinding pages are not.

Where

  • token_meter/web/server.py (bind address)
  • token_meter/app.py (_ACTION_TOKEN, software_update_status, do_GET / do_POST)
  • menubar_software_update() also copies the token into /menubar

Suggested fix

  1. Reject any request whose Host is not 127.0.0.1, localhost, or ::1 (with optional port).
  2. Do not put the action token on unauthenticated GET JSON. Prefer a SameSite=Strict cookie, or a one-time bootstrap that is not readable cross-origin.
  3. Require a loopback Origin on browser POSTs; do not treat a missing Origin as trusted.
  4. Add Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY so the dashboard cannot be clickjacked.

Happy to send a patch against a fork if useful.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions