- Key hygiene. The plugin config carries only a credential reference
(
apiKeyEnv, an env-var-style name), never the API key; a config value that starts withtic_is rejected at boot. The value is resolved per call through the harness credential service (launch environment, then$DSH_HOME/.credentials.yaml, then the project and user.envfiles) or, without a service, the launch environment. It is validated astic_live_…/tic_test_…printable ASCII, lives only in the request closure, and is never resolved at boot, stored on an object, logged, rendered to the model, or included in an error message./rta keyruns without the model and is registered withrecordInput:false, so the pasted key is not recorded in the session log and is never shown to the model. A value supplied by the launching environment is read-only:/rta keyrefuses to overwrite it and/rta key clearremoves only the credential-file entry, reporting what still supplies the key afterwards. - Approval gates. Credit-spending tools (
rta_avatar_create,rta_loop_set,rta_clips_set,rta_session_mint) always go through dsh's approval service, whatever the config says. Write-free tools (rta_asset_remote,rta_avatar_update,rta_avatar_delete) ask by default and run unprompted only withwriteApproval:false.readOnly:truedenies every write at the gate and again at execute time. The gate never force-allows: reads, and free writes underwriteApproval:false, are passed down the harness'stools/pre-executechain so a later policy plugin still sees them, and for an approval ask a downstream deny wins. Where no one can answer an ask — headless without an approval service, orDSH_PERMISSION_MODE=danger-full-access— the write is denied, not waved through. - Redaction. Every error message, rendered tool output, approval reason and
command result is passed through a redactor that removes the request's exact
key, any
tic_live_/tic_test_shaped token and anyBearervalue. Upstream error bodies are parsed for their message and code; the raw response text is never surfaced. - Bounded docs fetch.
rta_docsandrta_quickstartfetch only the closed set of public realtimeavatar.ai pages named insrc/facts.ts(plusllms.txtandopenapi.json), unauthenticated, with a timeout and an output cap. No arbitrary URL can be fetched through them. - Joinable credentials stay out of the transcript by default.
rta_session_mintwithholds the participant token unlessincludeToken:trueis passed explicitly; with it, the token is returned and rendered in the chat text. - Bounded inputs. Ids are validated before they enter a request path,
remote asset URLs must be absolute http(s), transcript webhook URLs must be
https, and the wire schemas' size limits are enforced locally.
rta_session_mintcapsmaxSessionSecondsby config; request bodies over 1 MB are refused before they leave the process. - A release leak gate. Every published tarball is scanned by
scripts/leak-gate.mjsbefore it ships (prepublishOnlyand CI run it). Two kinds of rule: generic public patterns (hex and UUID ids, key-shaped strings, private hosts, environment names, local filesystem paths, integration claims the plugin does not make) and a list of private vocabulary held only as salted digests, so the gate file never spells the words it guards against. The docs snapshot underskills/gets a single exact-phrase allowance for public text that happens to match a pattern; everything else is scanned strictly.
- A key that carries write scopes (
avatars:write,realtime:write) can spend credits through this plugin once an approver says yes — or, for the three write-free tools, oncewriteApproval:falseis set. Give the plugin a key with only the scopes it needs, set a per-key spend limit in the dashboard, and usereadOnly:truefor an agent that should only look. - The web composer keeps its draft in browser localStorage while you type
/rta key tic_…, before the command runs. If that matters on the machine you use, exportREALTIME_AVATAR_API_KEYin the launching shell instead of pasting the key into the composer. - Cancelling
rta_session_mintabandons the HTTP request only. A reservation the platform has already created may exist without the tool ever seeing its id; it expires on its own, andrta_usageshows it once settled. - A
tic_test_key is not a sandbox. It spends the same credits as atic_live_key; only the free Sandbox plan is free. - The plugin cannot protect a key that other processes can read from the
environment or the
.envfile you put it in, or a key a user pastes into chat by hand instead of using/rta key. - The leak gate checks what the package ships, not what the live documentation
says;
npm run sync-docsruns the same gate over the fetched text before it writes a snapshot.
Open a GitHub security advisory or an issue at https://github.com/theinfluencecompany/dsh-realtimeavatar/issues with a minimal reproduction. Any path that exposes the API key or the participant token, or that lets a credit-spending tool run without approval, is treated as critical and fixed first.