Skip to main content

Browser Auth Profiles

AILANG browser sessions can start already logged in, from a named, versioned profile — without any AI model ever receiving a password, a canonical profile, or the authority to change one.

This guide is for the operator who creates and rotates those profiles. If you just want to run a browser eval anonymously, see Browser Sessions instead.

Production use is blocked on two P0 follow-ups

Authenticated sessions are not yet cleared for production use against real accounts. Two designs must land first:

  • M-BROWSER-EGRESS-BOUNDARY — destination policy is not enforced below the browser. Playwright origin flags are application controls, not network containment: redirects, DNS, WebSockets, service workers, and browser subprocesses can all reach destinations the allowlist names.
  • M-BROWSER-ARTIFACT-DATA-POLICY — screenshots, video, HAR, console output, and downloads from an authenticated session are not yet classified, ACL'd, or given a retention policy.

Until both ship, --egress-ack is required to create a profile at all. It records an explicit, audited operator decision to proceed without enforcement. Use dedicated throwaway accounts on systems you own.

The threat model in one paragraph

A saved browser state is a credential, not an artifact. Cookies, local storage, IndexedDB tokens, and virtual passkeys can impersonate the account just as well as the password that produced them. Hiding the password while mishandling the state buys nothing. AILANG therefore treats profile material as secret-class throughout: it is sealed at rest, materialized to an owner-only file for exactly one run, destroyed on every exit path, and never serialized into a prompt, tool argument, result, log, trace, or artifact manifest.

A valid authenticated session necessarily grants the browser authority to act as that account. This design narrows and audits that authority; it cannot make a compromised session harmless.

How the pieces fit

you + your password manager ← the password stops here
│ (headful, recording off)

storage-state.json ← captured by you, never by a model
│ ailang browser-profile bootstrap

profile registry ← sealed AES-256-GCM, 0600 under a 0700 tree
│ resolve → preflight → lease → materialize

disposable 0600 file ← exactly one run, destroyed afterwards


AI + Playwright MCP ← sees an authenticated browser and nothing else

The model has browser action authority for the declared origins and the task duration. It has no vault access, no profile-read access, no way to obtain a hosted context ID, and no way to request persistence.

Creating a profile

1. Use a dedicated, least-privilege account

Never import your everyday browser profile. Create a separate automation account that:

  • has no access to a personal inbox, password vault, billing, or admin surface;
  • can be revoked and rotated without disrupting a human;
  • is permitted by the site's terms to be automated.

2. Capture the state yourself, headful

Sign in by hand, so your password never enters AILANG:

npx -y @playwright/mcp@0.0.79 --isolated --save-storage ./crm-state.json

Complete any CAPTCHA or 2FA in that window. Recording must be off. This session is not an eval and must never be compared against one.

3. Publish it

ailang browser-profile bootstrap crm-readonly-eu \
--state-file ./crm-state.json \
--provider local-playwright \
--origins https://crm.example.com \
--account-class readonly \
--max-concurrent 1 \
--egress-ack

Then delete your copy — the registry holds the only sealed one:

rm ./crm-state.json

Policy flags, and what "unset" means

Every policy field fails closed when unset. That is deliberate: a profile that forgot to declare something must refuse, not guess.

FlagUnset meansNotes
--originsrequiredExact origins only — scheme://host[:port]. No paths, no wildcards. A subdomain is a different origin.
--account-classreadonlyreadonly, mutable, or privileged. The username is never stored, only the class.
--max-concurrent1Set to 1 for any site that invalidates simultaneous logins.
--allow-artifactsallow nothingDeny-by-default. An unset artifact policy denies the session outright; an explicit empty list is a real "allow nothing" decision.
--allow-human-takeoverdenied
--expiresno expiryRFC3339. After it passes, the profile fails closed with browser_auth_profile_expired.
--egress-ackrequiredRecords that you accept an unenforced egress boundary.

Running an authenticated eval

ailang eval-suite --agent \
--benchmarks crm_readonly_fixture \
--browser-provider local-playwright \
--browser-profile crm-readonly-eu@latest

latest is resolved to a concrete version before the run starts, and the concrete version is what gets banked — so a result always records the identity that actually ran, not a moving reference.

Ordinary eval runs are always read mode. They load an immutable snapshot, cannot persist anything back, and discard their state. Authenticated runs also disable trace and video recording, because private page content is as sensitive as the login state.

What lands in the result

FieldExampleWhy it is safe
auth_profile_aliascrm-readonly-euOperator-chosen name
auth_profile_versionv7Never latest
profile_hashsha256:0f1e2d3c4b5a6978Truncated, domain-separated digest of the stored material — identifies a version, does not reveal one
auth_lease_idlease-01HZX…Opaque correlation id; grants nothing
auth_moderead

Cookies, storage state, sealed blobs, and hosted context IDs appear in none of them.

Rotation and revocation

Rotating a profile

Automated refresh needs a reviewed, site-specific login adapter implementing auth.LoginAdapter. AILANG deliberately ships no generic, model-authored login step: a general "fill in the login form" capability driven by a model is exactly what this design refuses to build. Until an adapter exists for your site, re-bootstrap with a freshly captured state under a new version:

ailang browser-profile bootstrap crm-readonly-eu --version v2 \
--state-file ./crm-state-v2.json --origins https://crm.example.com --egress-ack

Versions are immutable. Publishing v2 records v1 as its rollback pointer, and pinned references to v1 keep working.

Revoking

ailang browser-profile revoke crm-readonly-eu@v1 --reason "credential rotation"
Revocation is not enough on its own

Revoking stops AILANG from using that version. It does not end sessions the website already issued. A full incident response is:

  1. ailang browser-profile revoke <alias@version> --reason "..."
  2. Sign out all sessions in the site's own security settings
  3. Rotate the account password and any OTP seed
  4. ailang browser-profile purge <alias@version> to destroy the stored material
  5. ailang browser-profile audit to establish which runs used it, and when

purge refuses to run on a version that has not been revoked — the ciphertext is kept until then so an incident can still be investigated.

Key management

The default local key protector writes a key file next to the data it protects:

~/.ailang/browser-profiles/
keys/local.key ← 0600, the KEK
profiles/<alias>/ ← 0700
v1.material ← 0600, AES-256-GCM sealed
v1.meta.json ← 0600, safe metadata only
audit.jsonl ← 0600, append-only
materializations/ ← disposable, swept by `gc`

This is local development and single-host operation only. Anyone who can read the host can read the profiles. For anything shared or durable, supply a KMS-backed auth.KeyProtector — the interface is unchanged, so nothing else moves. Which protector a deployment uses (macOS Keychain, a 1Password item, Cloud KMS, Secret Manager) is a decision for deployment review.

For Cloud Run, the shape is: profile material in a private bucket with CMEK, the KEK in Cloud KMS, and the service account granted cloudkms.cryptoKeyVersions.useToDecrypt only. The service account — not the model, and not the eval harness — is the principal that can unwrap.

Account pools

One account is acceptable only when all of these hold:

  • it is a dedicated, least-privilege automation account;
  • tasks are read-only or serialized;
  • the site permits automation and its concurrent-session behavior is understood;
  • compromise can be contained by revoking sessions and rotating the account;
  • no personal inbox, vault, billing, or admin surface is reachable.

For parallel work that changes server-side state, use a pool of distinct accounts. The broker leases one profile per worker and records the account class, never the username. Exhaustion is a deterministic browser_auth_lease_conflict — the pool never silently hands two workers the same account, because that is the collision the pool exists to prevent.

Housekeeping

ailang browser-profile inspect # safe metadata for every profile
ailang browser-profile inspect --json # same, machine-readable
ailang browser-profile audit --limit 100 # who used what, when, and the outcome
ailang browser-profile gc --max-age 1h # sweep materializations a crash left behind

Run gc at boot on any host that runs authenticated evals. A crash between decrypt and destroy leaves plaintext on disk; the sweep removes it and records a structured audit event for each one.

Failure categories

All of these are stable and queryable from banked results:

CategoryMeaningRetry?
browser_auth_profile_not_foundUnknown alias or versionNo — fix the reference
browser_auth_profile_expiredPast the declared expiryNo — refresh first
browser_auth_profile_revokedPermanently retiredNo
browser_auth_lease_conflictHeld by another run, or pool exhaustedYes, after a wait
browser_auth_scope_deniedOrigin, egress, takeover, or provider mismatchNo — fix the policy
browser_auth_refresh_requiredLogin expired, 2FA, or post-login check failedNo — rotate
browser_auth_materialize_failedDecrypt, envelope, or filesystem failureSometimes
browser_auth_writeback_deniedAn ordinary run tried to persistNo — this is the guard working
browser_auth_artifact_policy_deniedArtifact class not permittedNo
browser_auth_cleanup_failedPlaintext may remain on diskInvestigate immediately

What this does not do

  • Give a model a password, OTP seed, recovery code, or vault access.
  • Import or automate your normal Chrome profile.
  • Protect profiles from a fully compromised runtime principal.
  • Let model-authored JavaScript implement login or refresh.
  • Solve CAPTCHAs or bypass a site's terms and security controls.
  • Persist arbitrary run changes into canonical state.
  • Contain browser network traffic — see the egress warning at the top.