8000 feat: per-token and per-session domain policies by adeelahmad · Pull Request #9 · adeelahmad/Tether · GitHub
[go: up one dir, main page]

Skip to content

feat: per-token and per-session domain policies - #9

Merged
adeelahmad merged 2 commits into
mainfrom
claude/policies-sppank
Oct 11, 2026
Merged

adeelahmad merged 2 commits into
mainfrom
claude/policies-sppank

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

What

Before: one global --allow-domains list and one shared token. Every client could see and drive every tab, and the allowlist only checked the URL a client asked for, so a redirect, a click or page script could still leave it.

After: policies come in layers, and every face enforces them.

  • Global: --allow-domains, plus the new --deny-domains, plus the policy file's top level.
  • Per token: --policy-file defines named tokens. Each has its own allow/deny rules, an admin flag and a scripts permission. --token is still accepted and becomes the admin token default.
  • Per session (tab): set with POST /tether/v1/tabs {"policy":…}, GET/PUT /tether/v1/tabs/{id}/policy, or WebDriver tether:options.policy.

A URL must pass global ∩ requesting token ∩ owning token ∩ session rules, and deny always wins. Restricted tabs also get a guard inside the browser, built on the DevTools Fetch domain. It pauses every top-level document request and fails the ones the policy forbids with ERR_BLOCKED_BY_CLIENT, so redirects, link clicks and location = are covered too. As a second line of defence, the core re-checks the URL each request finally landed on.

Tab ownership: a non-admin token only sees the tabs it opened. It also gets its own default tab per site and cannot adopt existing tabs. Anything it doesn't own looks like 410 tab_gone. GET /tether/v1/whoami reports the calling token and its permissions.

Why

Per-session and per-agent policies were on the v1 roadmap and are needed for the first release (the MCP face will rely on per-agent tokens).

How tested

  • make check equivalents: gofmt, go vet (incl. integration tag), golangci-lint (0 issues), go test -race ./...
  • make test-integration against real headless Chromium. The new TestPolicies drives a restricted token and checks that script navigation, link clicks and redirects are blocked, that another token gets 410 on the agent's tab through the API and the proxy, that session deny rules work, and that admin browsing is unaffected. I confirmed the test fails when the browser-side guard is disabled.
  • Docs updated: new docs/POLICIES.md, README, SECURITY.md (defaults and threat model), ARCHITECTURE.md, WEBDRIVER.md, CHANGELOG, and examples/policy.json (which a test keeps valid)

Security

  • No new way to reach the browser without auth. A policy file's tokens turn on authentication and satisfy the off-loopback token rule. Tokens are compared in constant time against every credential. Only token names are logged, never the secrets.

Known limits (documented): the rules govern top-level navigation. Subresources and in-page fetch() are not restricted, so "scripts": false is recommended for agents.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UfsJJgC4Jp8VqckdcChH4W


Generated by Claude Code

Extend the single global --allow-domains list into layered policies:

- internal/policy: domain rules (allow/deny, *.sub, *), principals and a
  JSON --policy-file defining named tokens with their own rules, an admin
  flag and a scripts permission. --deny-domains adds a global denylist.
- Auth attaches the token's principal to the request context; --token is
  the admin "default" principal.
- Tab ownership: non-admin tokens only see their own tabs, get their own
  per-site default tabs, and cannot adopt existing tabs.
- core.RulesPolicy checks global ∩ requester ∩ owner ∩ session rules,
  re-checks the final URL, and guards restricted tabs in the browser via
  the DevTools Fetch domain so redirects, clicks and page script obey the
  same rules.
- API: session rules on tab create, GET/PUT /tabs/{id}/policy, /whoami.
  WebDriver: tether:options.policy, ownership on every command, and
  Execute Script gated by the scripts permission.
- Docs: docs/POLICIES.md, SECURITY.md, ARCHITECTURE.md, README, example
  policy file. Integration test drives a real Chrome as a restricted token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfsJJgC4Jp8VqckdcChH4W
@adeelahmad adeelahmad self-assigned this Oct 11, 2026
Resolves conflicts with #7 (console logs): Tab gets both Guard and Logs,
the API keeps the policy routes and adds /tabs/{id}/logs, and docs list
both features. Also makes TestConsoleLogs tolerate a late favicon 404
between the two WebDriver log reads (it asserted the second read was
empty; it should assert no entry repeats).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9CEjRcHsJmgWdtz6ZTKsk
@adeelahmad
adeelahmad merged commit 5ec625d into main Oct 11, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0