· Paul Crossland
One Placeholder, Two Opposite Credential Failures
Fresh browser-agent bugs show why secret injection and redaction need separate, reconciling coverage proofs.
A browser agent can protect a credential in one direction and expose it in the other. Before an action, a placeholder may survive inside a typed parameter instead of becoming the intended value. After an action, two domain-scoped credentials with the same placeholder name may collapse into one redaction entry, leaving the other value visible to model history and logs.
One failure breaks an authorized action; the other can disclose the real secret. A single “secret replacement” label hides that distinction.
The practical thesis is: agent credential handling is a bidirectional protocol: injection must resolve one placeholder to one authorized value at one approved sink, while redaction must cover every distinct value across every model-visible or persistent channel; a run is safe only when those two independently built inventories reconcile.
This is defensive guidance for credentials that an operator is authorized to use. It does not justify automating login without permission, crossing an access boundary, or retrying around a denial. Login requirements, challenges, rate limits, payment surfaces, and source restrictions remain stop or review outcomes.
Fresh evidence from both directions
Current Browser Use changes expose two opposite gaps:
| Primary source | Date | What it contributes |
|---|---|---|
| Browser Use issue 5568 | 2026-08-28 | Reports that secret substitution traversed strings, dictionaries, and lists but not tuples. A placeholder inside a tuple-valued Pydantic field reached the action unresolved. The reporter explicitly observed action failure, not disclosure of the underlying value. |
| Browser Use pull request 5570 | 2026-08-28 | Adds tuple traversal while preserving container type, plus top-level and nested regression cases. It merged on August 28; a merge is not proof that every installed release contains it. |
| Browser Use issue 5592 | 2026-08-29 | Reports the redaction side flattening domain-scoped data into one placeholder -> value map. Reusing password for two domains caused last-write-wins loss, so one real value remained visible in message history, extracted content, and logs. |
| Browser Use pull request 5593 | 2026-08-29 | Proposes retaining every distinct value per placeholder, flattening those values into the redaction matcher, ordering matches longest first, and ignoring empty values. It is open, so it is design evidence rather than released behavior. |
Recent Better Fetch posts already cover credential classes, browser principal handoffs, auth-state boundaries, request-graph policy, and reviewable agent actions. The repeated angle would say to scope credentials by domain and redact logs. The new information surplus is the two-map contract: destination-aware injection and channel-wide redaction deliberately have different cardinality and scope, so neither can be inferred from the other.
One placeholder participates in two maps
Suppose an approved run stores a password value for each of two owned test domains.
The injection question is narrow: for this exact action, destination, principal, and parameter path, which one value may replace <secret>password</secret>? Zero means unresolved and must fail before dispatch. More than one means ambiguous and must also fail before dispatch.
The redaction question is broader: which configured values must never appear in a prompt, model response, browser observation, exception, trace, transcript, or ordinary log? The answer includes both domain-specific passwords. A secret can be echoed by a page, included in a tool error, copied into agent memory, or returned after the active tab changes. Its original destination scope does not make disclosure safe on another channel.
That creates two intentional shapes:
- Injection index:
(principal, task, destination, action, parameter path, placeholder) -> one authorized secret reference. - Redaction inventory:
secret reference -> every distinct non-empty value representation that protected channels must recognize.
Flattening injection to a global placeholder map risks using the right name with the wrong site's value. Flattening redaction to one value per placeholder risks dropping coverage when names repeat. Placeholder names are labels, not identities.
Typed arguments are part of the security boundary
The tuple bug is a reminder that an action schema is not “JSON-like” merely because most examples use dictionaries and lists. Pydantic models, custom tools, and language bindings may preserve tuples or introduce unions, sets, nested models, optional values, aliases, and custom serializers. A recursive helper that silently returns an unsupported container can turn a validated model into an uninspected secret path.
Do not solve this with an ever-growing sequence of convenient type checks alone. Define the canonical action representation accepted at the dispatch boundary. Walk every declared field path in that representation, record placeholder occurrences, and reject unsupported or opaque nodes when they can carry text. Revalidate the hydrated model before execution.
Most importantly, resolve just in time. The model and planner should see placeholders. The dispatcher should hydrate only fields authorized for the current destination and action. Logs, traces, approval panels, and tool results should continue to use secret references or placeholders—not a serialized copy of hydrated arguments.
An unresolved placeholder is not harmless. Do not send it to the website and let an authentication failure decide what happened. It can reveal internal key names, trigger lockout or retry behavior, and make a credential incident look like a source failure. Fail locally with secret_unresolved and send no request.
Redaction coverage is not injection success
A successful authorized login proves only that injection found a value. It says nothing about whether that value later appeared in model-visible text.
Build the redaction matcher from every distinct, non-empty configured value, not from the last value stored under each human-readable name. Handle overlapping values longest first in one pass so masking a short value does not corrupt a longer one. If the same value belongs to multiple labels, use a stable generic marker or an internal secret reference; do not pretend the text itself identifies which credential supplied it.
Exact-value replacement is still only one layer. Browsers and sites may transform a value through encoding, truncation, formatting, or partial echo. Rather than guessing every transformation, keep hydrated data tainted inside the dispatcher where possible, prohibit secret-bearing objects from crossing into model and telemetry APIs, and allow only explicitly reviewed transformations at approved sinks. Redaction is the final containment layer, not permission to pass raw values through every component.
Issue a secret-flow receipt
Record metadata that proves coverage without recording the credential:
- run, principal segment, tool, action, destination-policy rule, browser target generation, and dispatcher version;
- secret reference and version, placeholder label, authorized parameter path, and scope decision;
- canonical container paths visited, placeholder occurrences found, resolved, unresolved, and rejected;
- distinct configured-value count, redaction-matcher entry count, duplicate-value count, and placeholder-name collision count;
- channels scanned: prompt, model input, tool result, page observation, exception, transcript, trace, and log;
- redaction matches by channel and a post-redaction coverage verdict;
- dispatch outcome, source policy outcome, and final verdict:
resolved_and_covered,unresolved_blocked,ambiguous_blocked,redaction_incomplete,sink_denied, orunknown.
Do not put raw values or ordinary hashes of low-entropy passwords in telemetry. If an incident system needs equality joins, use restricted secret references or a keyed digest managed separately from crawl logs.
A synthetic secret-flow test
Run this matrix without real credentials and against owned fixtures:
- Give two domains the same placeholder name and different synthetic values. Put both values in one simulated tool result; require both to be masked.
- Collide a legacy global key with a domain-scoped key. Require complete redaction while injection still selects only the destination-authorized value.
- Place placeholders inside strings, dictionaries, lists, tuples, nested typed models, optional fields, and an intentionally unsupported container. Require full traversal or a pre-dispatch rejection.
- Use an empty configured value, duplicate values, and overlapping values such as a token that is a prefix of another. Require bounded, non-corrupting redaction.
- Attempt to hydrate a valid placeholder for the wrong destination, action, or parameter path. Require zero source requests.
- Leave one placeholder unresolved. Confirm the dispatcher fails locally and the retry controller does not classify it as an origin authentication problem.
- Make the owned page and tool adapter echo the synthetic value through an error, browser text, extraction result, and transcript. Require coverage on every channel before persistence or model delivery.
- Cancel and retry after hydration. Confirm no raw arguments survive in queued work, agent memory, traces, or the next browser session.
- Return a login, challenge, denial,
429, or payment boundary. Preserve that outcome and prohibit credential substitution from broadening the approved task.
Run the test against the exact agent framework, schema library, browser tool, model adapter, and logging stack in production. A merged upstream fix can close one tuple path while custom action serializers or downstream telemetry reopen the same class of failure.
The operator decision rule
When a credentialed browser action fails or secret text appears where it should not, inspect the directions separately. If a placeholder remained, stop before the network boundary and repair canonical traversal or destination resolution. If the correct value was injected but later became visible, contain the run, identify every exposed channel, follow the credential owner's rotation and incident process, and repair redaction inventory coverage. Do not print the value again to prove the leak.
Then reconcile the counts. Every placeholder occurrence must have exactly one authorized resolution or an explicit block. Every distinct protected value must have coverage on every model-visible and persistent channel. Unknown container paths, dropped collisions, and unscanned channels make the run ineligible even if the browser task succeeded.
A placeholder is useful because it lets an agent reason about a credential without seeing it. That abstraction holds only when the route into the approved sink and the routes back into untrusted text are measured independently. Keep two maps, prove both, and credential handling becomes a reviewable fetch boundary instead of a string-replacement assumption.