
Web agents are genuinely useful, until they hit a login screen.
If your agent can't get behind the auth wall, it can't reach your admin dashboards, supplier portals, or distributor sites. And if it can't get there, you're still doing the work yourself. Which kind of defeats the point.
The standard workarounds all have the same problem.
Put the password in the prompt. Works. Until your credential lives in the context window, in your logs, in your traces, and in whatever a prompt injection decides to do with it.
Write a hard-coded login script. Great until the portal adds SSO, shuffles its markup, or throws a redirect. Now you're maintaining per-site brittle scripts; the exact thing you were trying to avoid.
Wrap it in cleanup guardrails. Let the AI log in, then strip the credential afterward. In theory: elegant. In practice: the cleanup logic is exactly what doesn't run when the flow crashes.

The common thread in all of these: the AI has the credential. Every mitigation is damage control around that fact.
That's the question we started with. Not "how do we safely give the AI a password?" but whether it needed one in the first place.
Password managers solved this a long time ago. You don't copy passwords out of 1Password and paste them into forms. The manager detects the field and fills it. The secret never hits your clipboard.

Same idea applies here. Your web agent is actually really good at the hard part: finding login forms, handling redirects, navigating SSO flows and cookie banners. Typing the password is the easy part. And it's the part that doesn't need AI.
So we separated them. The agent handles the where. The platform handles the what. The password never enters the model's context.
Two participants. No shared state about the secret.

TinyFish Web Agent navigates the page. It identifies form fields, handles cookie banners, SSO steps, and redirect chains. It knows a credential exists for this run, but it never knows the username, password, or TOTP seed. Its job is spatial: get to the right field on the right page.
The platform holds the mapping between run-scoped credential labels and actual secrets in your vault. When the agent signals it's found a password field on a matching domain, the platform resolves the credential and injects it at the browser level outside the model's context, excluded from model-facing logs and traces.
The credential exists in the browser runtime just long enough to fill the field. The model can't reference what it never received.
Connect your existing password manager, 1Password or Bitwarden, using standard API credentials (service account tokens or read-only API keys). TinyFish doesn't store master passwords. Secrets are resolved at fill time and discarded. We keep only what's needed to resolve approved credentials at runtime.
Per-run scoping: Each run gets an explicit credential set chosen before execution. An export job gets what it needs. A monitoring scrape gets nothing. A run can't request credentials that weren't approved for it. The model can request a fill for an approved label, but it can't go browsing the vault.
curl -X POST https://agent.tinyfish.ai/v1/automation/run \
-H "X-API-Key: $TINYFISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-service-login-url.com",
"goal": "Log in and export last month'\''s report",
"credentials": ["supplier-portal"]
}'Domain matching: Credentials are bound to domains the same way your password manager binds them. A credential for app.example.com fills on that domain and its subdomains. It does not fill on totally-not-example.com even if the page renders an identical form. Matching is based on the browser's actual URL, not page content or visual appearance.
TOTP: If a credential includes a TOTP seed, we generate the code at fill time and inject it through the same browser-level path. The agent sees the MFA prompt, signals the platform, code is filled — the model never sees the seed or the generated value.

curl -X POST https://agent.tinyfish.ai/v1/automation/run \
-H "X-API-Key: $TINYFISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-service-login-url.com",
"goal": "Log in and export last month'\''s report",
"use_vault": true
}'Multiple credentials per run work for workflows that touch more than one service; a supplier portal and an inventory system in the same session, for example.
Vault prevents the model from seeing raw secrets. That's the thing it does. It's one layer, not the whole story.
It does not prevent misuse of an authenticated session post-login, data exfiltration from pages the agent visits, abuse of a compromised target site, or exposure of session cookies. Those risks need separate controls like run scoping, browser isolation, output review, audit logs.
We're not claiming we've solved authentication security. We're saying the model doesn't need the password, and we've built the plumbing so it doesn't get one.
A few things we're actively working on and want your input on:
More vault providers. 1Password and Bitwarden cover a lot of ground. What providers are you actually using? Please reach out and let us know!
Non-browser credentials. The current architecture is built around form fills. API keys, SSH keys, and other credential types are a different problem shape and one we're thinking about.
Edge cases. What happens when the vault is unreachable mid-run? When domain matching is ambiguous? When the login flow changes and the agent can't find the field? We handle these today, but we want to hear about the weird ones.
Broader MFA coverage. TOTP works. Hardware keys, push-based MFA, and SMS codes each have different constraints. This is on the roadmap, but we need more input.
Connect your vault, run a real login flow, and see for yourself that TinyFish Web Agent + Vault means the web agent never touches the credential.
Get your API key → Join the Discord come tell us what's broken, what's missing, or what you're building.
No credit card. No setup. Run your first operation in under a minute.