Repository navigation
Add credential fill, vaults, TOTP and 1Password tokens, with LLM tools - #142
Open
AHarmlessPyro wants to merge 12 commits into
Open
AHarmlessPyro wants to merge 12 commits into
AHarmlessPyro wants to merge 12 commits into
Conversation
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
marked this pull request as ready for review
October 14, 2026 00:46
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
NikhilShahi
approved these changes
Oct 14, 2026
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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dbwbLvQGyzwkxNetvqf3v
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds browser credential fill, vaults and typed secrets to the sync and async clients, plus
fill_credentialtool 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
credentialsecret type from #1537 is gone. A login is plain secrets that have allowed origins, usually grouped in a vault (USERNAME,PASSWORD, and optionallyOTPfrom atotpsecret). 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?})totp: a base32 seed or anotpauth://totp/URI. The parsing mirrors vm-service'sparseTOTPSeed: 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,digitsandperiodare 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?})andlist({type}).Secret.typeis a plain string.create_credentialand object values are removed.Vaults (new
client.vaults, by ID or exact name)list,get,delete.create({name, description?, source?, entries?})sourceis{type: "stored"}or{type: "1password", token_secret, vault}.StoredEntry(name, version)orOnePasswordEntry(item, section, field).update(vault, {description?, source?: {token_secret?, vault?}, entries?: {ENV: entry | None}}), whereNoneremoves an entry.source(master today) reads as stored.Sandboxes
vaulton create (image launches),execandprocesses.start.secretswins on a clash.name,secret_idandversion, and gainsecret_type,reference,token_secret,sourceandoverrides.startup_secretsacross 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?})version,page_id,originandfields[selector, outcome, secret_id, version, field, expires_at].credential_filledandcredential_fill_refused.Tools:
CredentialFillTool/fill_credential(OpenAI strict, Anthropic, Gemini)credential,vault,fields[{selector, field}],page_id,page_urlandframe_url.Noneand""count as absent, and input that isn't an object returnsinvalid_requestJSON.README: the secrets, vaults and credential fill section is rewritten. It covers 1Password vaults, error codes and the tool loops.
Validation
USERNAME/PASSWORD/OTP, and a session with[{"vault"}];CREDENTIAL_PAGE_AMBIGUOUSwithpage_id, and logged in.parseTOTPSeedon 125 edge cases. The only differences are deliberate refusals: impossible lengths and embedded newlines.op_tokensecrets, 1Password vaults, and sandboxvault/startup_secrets. They're covered by unit tests.Depends on browserctrl #1552 and #1573 being deployed for
op_token, 1Password vaults andtotp. 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 (vaulton create/exec/process start,startup_secretson create, merge with per-processsecrets).Secrets gain typed values (
secret,totp,op_token), browser allowed origins, richerupdate/metadata, local TOTP seed validation, andset_allowed_origins.Browser sessions can attach secrets or vaults at create via
credentials, then type values into pages withsessions.credentials.fill()(outcomes only—never secret values). Shared validation lives incredentials_common.py.LLM tooling adds
CredentialFillToolplusgemini_tool_definitionon existing tools via newtools/gemini.pyschema 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.