Skip to content

Add credential fill, vaults, TOTP and 1Password tokens, with LLM tools - #142

Open
AHarmlessPyro wants to merge 12 commits into
mainfrom
credentials
Open

AHarmlessPyro wants to merge 12 commits into
mainfrom
credentials

Conversation

@AHarmlessPyro

@AHarmlessPyro AHarmlessPyro commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Adds browser credential fill, vaults and typed secrets to the sync and async clients, plus fill_credential tool definitions for OpenAI, Anthropic and Gemini. It matches browserctrl master (#1572 fill on plain secrets, #1551 vaults) and the open hyperbrowserai/browserctrl#1552 (1Password vaults, op_token) and hyperbrowserai/browserctrl#1573 (totp). It mirrors node-sdk#131.

The credential secret type from #1537 is gone. A login is plain secrets that have allowed origins, usually grouped in a vault (USERNAME, PASSWORD, and optionally OTP from a totp secret). Values are encrypted locally and typed into pages by the platform, so neither the caller's code nor an LLM ever sees them.

API (sync and async; params as dicts or models)

Secrets

  • client.secrets.create({name, value?, description?, type?: "secret" | "totp" | "op_token", allowed_origins?, metadata?})
    • Values are checked locally per type before anything is created.
    • totp: a base32 seed or an otpauth://totp/ URI. The parsing mirrors vm-service's parseTOTPSeed: spaces and hyphens are ignored, = padding is allowed only at the end, and lengths 1, 3 or 6 past a multiple of 8 are refused. In a URI, algorithm, digits and period are range-checked, and HOTP is refused.
    • op_token: takes no allowed origins.
  • add_version(secret, value) checks the value against the secret's stored type.
  • set_allowed_origins(secret, origins | None), update(secret, {description?, metadata?}) and list({type}).
  • Secret.type is a plain string.
  • create_credential and object values are removed.

Vaults (new client.vaults, by ID or exact name)

  • list, get, delete.
  • create({name, description?, source?, entries?})
    • source is {type: "stored"} or {type: "1password", token_secret, vault}.
    • Entries are StoredEntry(name, version) or OnePasswordEntry(item, section, field).
  • update(vault, {description?, source?: {token_secret?, vault?}, entries?: {ENV: entry | None}}), where None removes an entry.
  • Env names, 1Password name parts, the 512-byte reference limit and the entry kind against the source are checked locally.
  • A vault with no source (master today) reads as stored.

Sandboxes

  • vault on create (image launches), exec and processes.start. secrets wins on a clash.
  • Bindings have optional name, secret_id and version, and gain secret_type, reference, token_secret, source and overrides.
  • The handle keeps startup_secrets across refreshes.

Sessions

  • credentials: [str | {"secret": ...} | {"vault": ...}] on create.
  • client.sessions.credentials.fill(session_id, {credential? | vault?, fields: [{selector, field?}], page_id? | page_url?, frame_url?})
    • It returns version, page_id, origin and fields[selector, outcome, secret_id, version, field, expires_at].
    • The request is checked locally, and the timeout is at least 30 s.
  • The event log types gain credential_filled and credential_fill_refused.

Tools: CredentialFillTool / fill_credential (OpenAI strict, Anthropic, Gemini)

  • It has the same inputs as node: credential, vault, fields[{selector, field}], page_id, page_url and frame_url.
  • None and "" count as absent, and input that isn't an object returns invalid_request JSON.
  • Results and errors never contain values.

README: the secrets, vaults and credential fill section is rewritten. It covers 1Password vaults, error codes and the tool loops.

Validation

  • Local checks: 1041 tests pass on Python 3.12 (including the mypy and Pyright typing tests) and 1037 on 3.8. Ruff is clean.
  • End to end: all checks pass against a local stack (master + #1573):
    • secrets with origins;
    • TOTP create, with bad seeds refused locally;
    • a vault with USERNAME/PASSWORD/OTP, and a session with [{"vault"}];
    • vault and single-secret fills; the typed TOTP code matches a locally computed one;
    • the ambiguous-tab retry and origin refusal;
    • a real login, the event log, and async calls.
  • Model loop: one gpt-5-mini loop filled by vault, recovered from CREDENTIAL_PAGE_AMBIGUOUS with page_id, and logged in.
  • TOTP parsing: compared with vm-service's parseTOTPSeed on 125 edge cases. The only differences are deliberate refusals: impossible lengths and embedded newlines.
  • Not live-tested, because the local stack doesn't have #1552: op_token secrets, 1Password vaults, and sandbox vault/startup_secrets. They're covered by unit tests.

Depends on browserctrl #1552 and #1573 being deployed for op_token, 1Password vaults and totp. Everything else works against master today.

🤖 Generated with Claude Code

https://claude.ai/code/session_019dbwbLvQGyzwkxNetvqf3v


Note

High Risk
Introduces secret types, vault resolution, sandbox secret delivery, and browser credential fill—security-sensitive paths where misconfiguration or validation gaps could leak credentials or fill the wrong page.

Overview
Adds vaults (client.vaults) to group secrets or 1Password fields into named env-variable sets, and wires them into sandboxes (vault on create/exec/process start, startup_secrets on create, merge with per-process secrets).

Secrets gain typed values (secret, totp, op_token), browser allowed origins, richer update/metadata, local TOTP seed validation, and set_allowed_origins.

Browser sessions can attach secrets or vaults at create via credentials, then type values into pages with sessions.credentials.fill() (outcomes only—never secret values). Shared validation lives in credentials_common.py.

LLM tooling adds CredentialFillTool plus gemini_tool_definition on existing tools via new tools/gemini.py schema conversion.

README and CLAUDE.md document credential fill, vaults, 1Password vaults, and provider tool loops.

Reviewed by Cursor Bugbot for commit b4dd1e9. Bugbot is set up for automated code reviews on this repo. Configure here.

AHarmlessPyro and others added 4 commits October 13, 2026 16:24
Credentials are secrets of type "credential" whose value maps field names
to strings. The value is checked locally, serialized as compact UTF-8 JSON
and sealed with the encryption context the API returns, like any secret.

- Secret gains type and metadata (browser.allowedOrigins); create accepts
  type, metadata and a mapping value; list filters by type; update sends
  metadata with null kept, so a member can be cleared.
- secrets.create_credential() and secrets.set_allowed_origins().
- add_version() checks the value's shape against the secret's type.
- sessions.create() takes credentials; the session lists attached ones.
- sessions.credentials.fill() validates the request locally and posts it
  with a timeout of at least 30 seconds.
- A str secret value that isn't valid UTF-8 (a lone surrogate) now raises
  invalid_secret_value instead of UnicodeEncodeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CredentialFillTool exposes sessions.credentials.fill() to a model with
the session ID bound by the application. The model picks the credential
by name, the fields and their selectors, and optionally a page ID or an
iframe URL; it never sees values. runnable() and async_runnable() return
the fill result as compact JSON, or the error's code, message,
retryable flag and server details as JSON so the model can react, for
example by choosing a page after CREDENTIAL_PAGE_AMBIGUOUS.

The definitions come for OpenAI (strict), Anthropic and Gemini. Gemini
schemas are an OpenAPI subset, so tools/gemini.py converts the JSON
schemas (nullable instead of a null type, no additionalProperties), and
every existing tool gains a gemini_tool_definition too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Secret.type is now a str defaulting to "secret", so a new type doesn't
break list() or get(). Requests still accept only the known SecretType
values. The README's LLM samples use gpt-5, claude-sonnet-5-5 and
gemini-3.5-flash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AHarmlessPyro
AHarmlessPyro marked this pull request as ready for review October 14, 2026 00:46
AHarmlessPyro and others added 3 commits October 13, 2026 20:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FzcEMZLVjoiueoQFxG6F7S
… the README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FzcEMZLVjoiueoQFxG6F7S
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FzcEMZLVjoiueoQFxG6F7S
AHarmlessPyro and others added 5 commits October 14, 2026 05:39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dbwbLvQGyzwkxNetvqf3v
The credential secret type is gone: a secret is fillable once it has
allowed origins, and a login is usually several secrets grouped in a
vault. Secrets take type totp (checked locally as the platform reads
seeds) or op_token, and allowed_origins on create. client.vaults manages
stored and 1Password vaults; sandboxes and processes take vault next to
secrets, and report each binding's source, type and 1Password reference.
Sessions attach secrets or vaults, fills take credential or vault with
page_url, and fill_credential lets a model choose either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dbwbLvQGyzwkxNetvqf3v
Strip one leading BOM, then Go's whitespace; uppercase per character so
ß and ff don't become base32; refuse stray % outside the query, trailing
newlines in digits or period, and malformed userinfo; accept userinfo
before totp as Go does; read query pairs as Go's ParseQuery. The fill
tool answers input that isn't an object with an invalid_request error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dbwbLvQGyzwkxNetvqf3v
@AHarmlessPyro AHarmlessPyro changed the title Add website credentials, credential fill and LLM tools Add credential fill, vaults, TOTP and 1Password tokens, with LLM tools Oct 14, 2026
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