Skip to content

Secrets

Kelly Ferrone edited this page Sep 12, 2026 · 4 revisions

Secrets

A secret is a named set of keys — nextcloud with username and password, say — that an operator put on the server. A caller can use one and can never see one. No tool, resource or report returns a value.

That is the whole point. A password an agent holds is in its transcript, its tool call, and whatever its host logs. A password it merely names is in none of them.

Putting secrets on the server

Set SECRETS_DIRS to a directory — or several, separated like PATH. Inside, one directory per secret and one file per key, which is exactly the shape Kubernetes mounts a Secret in:

/secrets/
└── nextcloud/
    ├── username
    ├── password
    └── .meta.yaml        # optional

The file name is the key and the file's contents are the value. .meta.yaml describes the secret without changing it:

description: Admin login for the homelab Nextcloud
allowed_urls:
- https://nextcloud.example.com

Unset SECRETS_DIRS means the feature is off, and list_secrets says so rather than returning an empty list that looks like an empty catalogue.

What a caller sees

list_secrets — or the secret://secrets resource — returns names, keys and sites. Never values.

Field Means
keys what may be named as key
allowed_urls the only sites it may be typed on, as exact origins
restricted: false no sites declared: usable anywhere
allowed_urls_rejected its site list is broken, so it cannot be used at all until an operator fixes it

Binding one

Give write a value_from instead of text:

write(css="#password", value_from={"secret": {"name": "nextcloud", "key": "password"}})

The result carries "value_from": "secret" and no value. The same thing goes in a flow step's params — see Flows — where the step's line in the report reads text=<hidden>.

Three rules, each refused rather than guessed at:

  • Only write takes a secret. It is the one action that types a value into a field and does nothing else with it. Other actions accept a value_from naming a parameternavigate takes one for its url — and that is not the same permission: a secret in a URL is in the browser history, in the referer of every request the page then makes, and in the Grid's logs, none of which this server can scrub.
  • Exactly one source, and never text as well.
  • Navigate first, in its own step. A write binding a secret may not also take a url, because the site check reads the page the browser is on at the moment of typing — a write that navigated would be checked against the page it was leaving.

Allowed sites

A restricted secret is typed only when the browser is on one of its allowed_urls, compared as an exact origin: scheme, host and port. https://nextcloud.example.com.evil.test is not a match, and neither is plain http://. A refusal names the page the browser was on and the sites it would have accepted.

A parameter or a secret?

A parameter is for what varies between runs; a secret is for what must not be seen. An email address is a parameter. Its password is a secret.


Flows · write · Deployment

Clone this wiki locally