Password Reveal
Shows a secret the user is entitled to see, masked until they reveal it, or generates a strong password in the browser.
≈ 1,067 tokens per use (tool 678 · guidance 359 · typical payload 30)How token costs are estimated
Example: Password Reveal
What's the guest Wi-Fi password?
Guest Wi-Fi password
PasswordNetwork: Acme Guest
When to use
- The user asks for a secret they're entitled to see and a tool returned it: a shared Wi-Fi password, backup codes, an API key they just created, a door code.
- The user wants a new strong password. Use
kind: "generated"so it's made in their browser and the agent never sees it.
When not to use
- The agent hasn't checked that the user may see the secret. Check access first; never reveal someone else's credentials.
- The agent doesn't have the secret from a tool result. It never invents, guesses or reconstructs one.
- The value isn't secret (a booking reference, an order number). Show it as text.
- The user wants a guest network as a scannable code. Use the QR Code.
Security rules
These are enforced in the component, not left to the agent:
| Rule | How |
|---|---|
| Masked by default | The card opens showing a fixed 10-dot mask. While masked, the secret is not in the DOM at all, and the mask has the same length for every secret, so it doesn't hint at the real length |
| Reveal is temporary | Reveal (aria-pressed) shows the secret for revealSeconds (default 30) with a visible "Hides in 28 s" countdown, then hides it. It also hides as soon as the tab is hidden (visibilitychange) |
| Nothing leaks | The secret never goes into results, analytics, aria-live text, document.title, URLs or a title attribute. Announcements are fixed text ("Copied", "Hidden again") |
| Results are actions only | { action: "revealed" | "copied" | "rotated-request" | "dismiss" }, never the value |
| Marked private | The revealed value sits in a monospace element with data-private (honored by session-replay and analytics tools that support it), translate="no" (no machine translation), spellcheck="false", autocomplete="off" and password-manager ignore hints |
| One-time secrets | With oneTime, the first reveal ends for good: once it hides, the component drops the value and shows "This code was shown once." |
| Generated locally | kind: "generated" makes the password in the browser with crypto.getRandomValues, uniformly (rejection sampling, never Math.random). The agent never sees it |
| Clipboard clears (best effort) | After a copy, the component waits clearClipboardAfter seconds (default 60). If the browser already lets the page read the clipboard without prompting and the clipboard still holds the secret, it writes an empty string. Otherwise it leaves the clipboard alone and tells the user to clear it themselves |
Best effort means best effort. Most browsers don't grant clipboard read without a prompt, so on most devices the component can't clear the clipboard and says so ("Copied. Clear your clipboard when you're done."). It never prompts for clipboard access and never overwrites something else the user copied since. Clipboard history tools and synced clipboards can keep a copy regardless.
Keep the secret out of the model. The masking protects the screen, not the transcript. Where you can, have the host's reveal_secret tool fetch the secret server-side and pass value to the UI only, rather than having the model copy it into the tool call. Don't persist value in chat history you replay to the model, and drop it from stored history for one-time codes.
How the agent should use it
- Pass the value exactly as the tool returned it.
valueis data-bound: it comes from a tool result, never from model free text. Never repeat it in the reply, a summary or another tool call. - For a new password, use
kind: "generated"withgenerateoptions (length 12–64,symbols,numbers,uppercase,excludefor characters the target site rejects). Don't put a password the model made up invalue. - Set
oneTimefor recovery and one-time codes,rotatablewhen the agent can issue a replacement, andexpiresAtwhen the secret lapses (shown as "Expires in 23 h"). - Put where to use it in
hint(the network, the door, the service), never the secret. - On
rotated-request, issue a new secret and show a new card. The old card stays hidden. - Lifecycle: when the secret itself lapses (a temporary door code, a short-lived token), set
expiresAton the call and pass the same value as theexpiresAtprop, so the card shows "Expires in 23 h" and the Renderer turns it inert afterwards. When a rotation replaces it, mark the old call superseded by the new one.fetchedAtisn't needed.
Interaction
| Control | Does | Reports |
|---|---|---|
| Reveal (eye icon) | Shows the secret for revealSeconds; press again to hide early | revealed |
| Copy | Copies the secret (codes one per line) and schedules the best-effort clear | copied |
| Regenerate (icon) | Makes a new password with the current options (generated only) | nothing |
| Length, Uppercase, Numbers, Symbols | Change the options and regenerate (generated only); the strength meter shows "Strong · ~126 bits" (length × log2 of the character set, rounded down) | nothing |
| Request new | Closes the card and asks for a replacement (rotatable only) | rotated-request |
| Done (✕ icon) | Hides the secret, forgets it and closes the card | dismiss |
Icon-only controls have full accessible names ("Reveal Guest Wi-Fi password") and fixed-text tooltips, never the secret. The kind (Password, Codes, …) is read to screen readers; one-time cards add one line, "Shown once. Save it before it hides."
Codes (kind: "codes") are revealed as a numbered two-column grid. revealed and copied can repeat; rotated-request and dismiss end the card.
Installation
npx shadcn@latest add https://your-site.com/r/password-reveal.json
Usage
import { tool } from "ai"
import { passwordRevealTool } from "@/components/genui/password-reveal.schema"
const tools = {
[passwordRevealTool.name]: tool({
description: passwordRevealTool.description,
inputSchema: passwordRevealTool.inputSchema,
}),
}onResult receives an action, never the secret:
{ action: "revealed" } // or "copied", "rotated-request", "dismiss"Only rotated-request needs the agent's attention; the others are for your analytics.
With LangGraph
Push it from a graph node with ui.push({ name: "reveal_secret", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer. Fetch the secret inside the node and put it in props only, not in the message the model reads.
Restoring from history
Pass the stored result back as result. dismiss and rotated-request restore the closed card, and any result on a oneTime secret restores it as already shown. The secret is never shown again from history, and onResult isn't called.
<PasswordReveal
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
label | string | What it is: "Guest Wi-Fi password" |
kind | "password" | "code" | "codes" | "api-key" | "pin" | "generated" | codes is a list; generated makes one locally |
value | string | string[]? | Data-bound. The secret, a list for codes. Omit for generated |
hint | string? | Where to use it. Never the secret |
revealSeconds | number | 5–300. Default 30 |
oneTime | boolean | Gone after the first reveal. Default false |
clearClipboardAfter | number | Seconds, 0–600; 0 turns it off. Default 60 |
generate | { length, symbols, numbers, uppercase, exclude? }? | For generated. Length 12–64, default 20; classes default on |
rotatable | boolean | Shows "Request new". Default false |
expiresAt | string? | Data-bound. ISO 8601; shown as "Expires in 23 h" |
Host props: status (streaming, ready, consumed, dismissed, rotation-requested), onResult, result and className.