Autofill API for secure browser automation

As a process automation operator, I need the extension to fill a login by resource UUID, so that credentials never sit in, or are exposed to, automation processes like Python scripts, Selenium browser automation, or an AI agent’s context.

Q1. What is the problem that you are trying to solve?
Credentials are usually stored in .env files or other plaintext on disk: data science notebooks, data pipelines, and browser automation alike. That was always a weak spot, and AI makes it worse.

Securely managing, sharing, and auditing credentials across data science and process automation teams is close to impossible. AI coding tools read all project files into their context, including the env-file, and send them to the model provider. Prompt injection, the number one LLM risk per OWASP, can exfiltrate every secret with ease.

The structural fix is to separate three layers:

  • orchestration: the automation tool or AI agent that decides what to do
  • execution: the browser that does the work
  • security: the password manager that holds the secret

For browser automation the passbolt extension is the ideal security layer, but one piece is missing: a way for an automation tool to have the extension fill a login without ever holding the password. Today there is no compliant option, since any tool keeps the password in an env-file, or even hard-coded, which fails OWASP, NIS-2, and GDPR Article 32.

The non-compliance goes away when an automation tool triggers a fill by resource UUID, the extension fills the login directly, and the credential never enters the automation tool or an AI agent’s context.

We already have this working end to end. The gif below shows the before and after: the password moves out of the env-file and into the vault, and the automation only triggers the fill, it never sees the secret.

before-after-passbolt-gateway

Q2 - Who is impacted?
Anyone running automation against systems protected by passbolt: data scientists and data pipelines, RPA and ETL, scheduled logins, and browser-based tests. Teams adopting AI agents feel it most, since that is where the env-file risk is sharpest. The API is opt-in and off by default, so it changes nothing for anyone who does not enable it.

Q3 - Why is it important and/or urgent?
It closes a gap that, as far as we can tell, no one has closed yet. We are not aware of any password manager that lets browser automation fill a login without the automation tool ever holding the password, so passbolt could be the first to offer it.

It matters because there is currently no compliant way to give automation a login. Plaintext credentials in config are the number one exposure for AI agents per OWASP, they fall under NIS-2 for many EU companies, and once they pass through a US-hosted model they raise a GDPR Article 32 and Schrems-II question. The mechanism to avoid all of that simply does not exist today.

This turns the extension into the security layer for browser automation, so secrets stop living in env-files. The risk grows as AI adoption grows, so it gets more pressing over time, not less.

Q4 - What is your proposed solution? (optional)
A small, opt-in, signed message API that makes the extension the security layer. The orchestration tool triggers a fill and sees only a status, while the extension holds and uses the secret.

The design we prototyped:

  • off by default; nothing works until the user enrolls a caller’s public key from the options page
  • each request carries a per-request ECDSA P-256 signature, so the caller proves it holds a private key the extension never sees
  • a single-use nonce and a short time window prevent replay
  • the resource UUID is not a capability: resolution runs through the signed-in user’s own session, and the fill happens only when the tab’s address already matches the resource’s stored URL, using your existing suggestion matcher
  • two actions only: extension_status for discovery, and autofill_by_uuid for the fill, with an honest filled or no-form status
  • every call is written to a local audit log, and inbound calls are rate-limited
  • the transport is a content-script relay; the externally_connectable surface ships inert
  • it activates only against a Pro-edition server, reusing your existing edition check, so the community default is unchanged. The tiering is of course your call

The vault must already be unlocked. We do not propose programmatic unlock here, since that sits in tension with the zero-knowledge model.

Note: Due to forum restrictions I add the examples as replies.

A worked example, with Selenium and KNIME

  1. start a Chrome WebDriver with passbolt still locked
  2. auto-authenticate in passbolt, then log into a website by credential UUID
  3. log out of passbolt

260612-passbolt-knime-automation

The extension options page

Enrolling the automation tool (KNIME) in passbolt

This touches credential handling, so we want your view before any PR. We have a working prototype, a written design and threat model, and an OWASP and NIS-2 mapping, all of which we are glad to share, and we are happy to route this through whatever security-review process you prefer.

Questions for the team:

  • Is an API like this something you would consider upstream?
  • How would you like it discussed: this thread, a GitHub issue, or a more formal RFC?
  • Any early concerns about the security model we should address first?

Additional References:

Hello,

Thanks for taking the time to put this together.

I think you’re addressing an interesting problem. Storing secrets in .env files, configuration files, CI variables, etc. has never been ideal, and the rise of AI-powered tooling makes the situation worse because these systems often ingest large portions of a codebase or execution context by default.

I think you need to be more clear around the security properties. The strongest benefit here is that secrets remain outside the automation definition and outside the context available to AI assistants and agents. That’s valuable. However, once a secret is actually injected into a page, it becomes available to whatever is driving the browser, so I would be careful not to position this as complete runtime isolation.

My first reaction is less about the use case and more about the security model. We’d need to spend time understanding some of the edge cases and attack scenarios. For example, how does this behave under replay attacks? Are there confused-deputy risks where one application can trick the extension into retrieving or injecting credentials intended for another? I also noticed some cryptographic choices that sit apart from our existing OpenPGP-based stack.

More generally, this would mean adding a way to interact with an unlocked passbolt session, and therefore expanding risks. This would become part of our long-term security surface area. That’s something we’re very conservative about.

I also wonder if the underlying problem could be solved with a much simpler approach. I guess keyboard shortcuts are not cutting it in headless unattended mode? Nonetheless I’d be interested in understanding whether the proposed API is actually necessary, or whether a simpler (or newer browser standard?) mechanism could address most of the use cases with fewer security implications.

Finally, for me, a key factor in deciding whether this is something we’d consider supporting more broadly would be seeing genuine demand from the community. If multiple users are running into the same problem and the existing workflows don’t address it adequately, that’s a much stronger signal than a single integration, however well thought out it may be.

I hope this is useful rather than discouraging, as you have spent a lot of time on this, so I wanted to give you a honest “what you can expect from us” answer. Sometimes the best security feature is the one we don’t have to build.

Hi remy,

apologize for the delayed reply. We took extra care to address your points as best as we can. Also, thank you, genuinely. Your feedback is quite the opposite of discouraging. We were actually looking forward to having the idea scrutinised. Let me go through your points one by one.

Primary benefit & isolation promise

Correct. The strongest point is that the credential never enters the automation nor the context an AI agent sees. The orchestrator (KNIME) picks a credential by resource UUID and gets back a status string but never the secret.

Passbolt’s own content script performs the fill, exactly as it does for a manual user. Our node, we’ve developed a passbolt extension for KNIME with a free CE tier, never sees a field value. I’ve attached a sequence diagram of the full handshake. On your point about over-promising, we scope the claim to that boundary and no further.

To be clear. Once the secret is filled into the DOM it becomes accessible by whatever drives the browser. There is no 100% prevention of extraction past that point, and we don’t claim one.

What we address is the weakest link in the chain. Everything before a credential is handed to the automation, the browser, or the agent context. The secret stays out of the automation tool, out of logs, out of the AI/agent context, and out of any hard coded automation.

Our own up-front research surfaced the same limit you point to, the page-DOM reachability of a filled credential. No browser reliably prevents it, because same-origin scripts can read input.value. The only thing that keeps the secret out of the DOM entirely is browser-internal nonce injection, which no extension can do. That is covered in a 2024 paper, Passwords Are Meant to Be Secret.

Moving off shared secrets with passkeys or WebAuthn is the real fix. Though, that is not “here” yet but on the backlog. FIDO’s own Passkey Index puts passkeys at 26% of sign-ins, and only on the platforms that already rolled them out. The long tail of business login forms has none. Most authentication is still password-based.

Security model: replay and confused-deputy

Both were front of mind from the start.

  • Replay: every request carries a single-use nonce and a timestamp checked against a ±2-minute window (with a clock-skew offset computed once per session against the extension’s clock). A captured envelope cannot be replayed.
  • Confused-deputy: a request can only ever cause a fill of a resource that matches the current page’s origin. The extension re-runs the same URI / parent-domain suggestion match (resource.isSuggestion(tab.url)) as a hard gate at fill time, so a page cannot trick the extension into injecting a credential intended for a different origin. The per-request signature also binds each call to a specific enrolled KNIME installation (via its key_id), so one application cannot impersonate another’s enrollment. We’d genuinely welcome you poking at the edge cases here. It is the part we most want a second pair of eyes on.

Cryptographic choice (why not OpenPGP)

You noticed the signature uses ECDSA P-256 rather than OpenPGP. We understood this is a crucial point for you, since it sits next to your OpenPGP stack. The signature only proves a request came from the KNIME you enrolled. It does not touch how a user signs in to Passbolt.

The choice itself was practical, for two reasons.

  1. Cross-browser compatibility.
  2. Less code overhead.

On the first, P-256 is the common ground that every browser vendor supports. Ed25519 only started landing recently, and unevenly. We focused on Chrome, but we did not want to overcomplicate it, so we prioritised cross-browser support over a newer curve.

On the second, the extension checks the signature with the browser’s built-in crypto, and that built-in crypto speaks P-256, not OpenPGP. So we ship no extra crypto library and the signed message stays tiny.

It sits beside your stack and checks who is calling. It does not replace or change anything in Passbolt.

Surface expansion of an unlocked session

We share that concern. That is why the capability is off by default and only reachable after a manual, mutual enrollment:

  1. KNIME generates an ECDSA P-256 keypair and exports the public key.
  2. The operator pastes that public key into the extension’s options (“KNIME Automation” card). The extension verifies it and returns a key_id.
  3. That key_id must be entered back into KNIME.

Only when all three line up, and re-verified on every single signed request, is a call accepted by the Passbolt browser extension. Every request is also logged in the extension’s “Recent automation attempts” audit log. Revoking the key in options instantly disables that installation.

A simpler solution

We tried several simpler routes first.

  • Quick Access via keyboard shortcuts. These can’t be triggered hands-off. No in-browser mechanism fires a chrome.commands shortcut, and chrome.action.openPopup() needs a focused window. That path is attended-only and doesn’t survive headless or unattended automation, which is the whole use case. We even registered a global shortcut, but that was a dead end too. At best it drives one window or tab, which badly bottle-necks parallel automations.
  • Brute force. Reaching into the shadow DOM and enumerating iframes proved too unreliable. On session resumption, re-authenticating in Passbolt that way was practically impossible.
  • BiDi (W3C) and Chrome CDP. The transport we found most reliable, secure, scalable and sustainable is the standard one. WebDriver BiDi, the W3C automation protocol. It is still a Working Draft, but it is co-developed by the browser vendors. We fall back to Chrome’s CDP only where BiDi isn’t available yet. So the mechanism rides the emerging standard rather than a bespoke API.

User Demand

Fair point. Our own vantage point is data-science and automation. KNIME with the AF Utilities, Palladian and Selenium extensions, increasingly with LLM steps in the pipeline. That is partly our own view.

Though the real benefit is the growth potential. Data scientists and automation engineers are a growing cohort. As AI-agent pipelines spread, they pull in the wider organisation too. Security, compliance, IT.

Functional Accounts

One related topic we haven’t seen raised officially as a feature request. To isolate credential usage per project or agent, we use “functional accounts”, addressed via plus-addressing (screenshot below).

These gate access on a per-project and per-agent basis. Credentials are shared with the functional account, never granting access to the owner account that holds them. We’ve exchanged with Smruti about this over email.

“One more thing”

We haven’t waited on the browser side for everything. We recently shipped a separate mechanism that bridges Passbolt and Python scripts in KNIME (knime-passbolt-py).

There the secret stays inside the KNIME JVM and is passed to the script over loopback with a short-lived bearer token, in a buffer that’s zeroed after use and never serialized. A different exposure model from the autofill node, but the same intent. Minimise where the cleartext exists. Happy to share details if useful.

Cheers
Mike

Hi remy,

we hope our reply was sufficinetly informative. Since it is rather abstract, we decided to publish the code in our repo as a fork for you and others to have a look:

https://github.com/passbolt/passbolt_browser_extension/compare/master...DataNautics-GmbH:passbolt_browser_extension:datanautics/automation-api

We’d love to your point of view on this.

Best
Mike