What a read hides#
A viewer can read the whole configuration tree, and the configuration tree contains
WireGuard private keys, IPsec pre-shared secrets and password hashes. So nine read
endpoints are wrapped in redactSecrets, which replaces the value of any known secret
leaf with the literal string [redacted] for a principal below a stated role. It is a
wrapper on the route table rather than a line inside each handler, because
/api/config, /api/config/commands, /api/wireguard and /api/vpn/ipsec all reach
the same secrets by different code paths and the next read endpoint somebody adds would
have missed the check.
{
"interfaces": {
"wireguard": {
"wg0": {
"address": "10.0.0.1/24",
"private-key": "[redacted]",
"peer": {"laptop": {"public-key": "kZ…="}}
}
}
}
}public-key is left alone. The match is on whole field names, not substrings.
The endpoints that wear it#
| Endpoint | Redacted below |
|---|---|
GET /api/config | admin |
GET /api/config/commands | admin |
GET /api/vpn/ipsec | admin |
GET /api/vpn/openvpn | admin |
GET /api/wireguard | admin |
GET /api/drift | admin |
GET /api/history/diff | admin |
GET /api/fleet/{id}/config | admin |
GET /api/staged | operator |
/api/staged is the odd one, deliberately: the operator who staged a key typed it a
moment ago, and a Commit Bar that shows them [redacted] for their own change is not a
preview. A viewer has no such claim on it.
GET /api/config/raw is not in this list. It is the whole-configuration download,
secrets included — the backup export — so instead of being redacted it is
admin-only.
The leaf names#
Fifteen names, matched case-insensitively and with surrounding quotes stripped:
private-key pre-shared-key pre-shared-secret
preshared-key psk plaintext-password
encrypted-password password passphrase
shared-secret-key secret key
api-key auth-key authentication-keyRedaction keys on the leaf name, not on a path, because these names are stable
wherever they appear in the tree — a new VyOS feature that stores a pre-shared-key
under a node nobody has documented yet is still covered.
Four of them are also ordinary English words#
key, secret, password and psk are unambiguous as leaf names in a tree, and
ambiguous loose in a line of text. Matching them anywhere turned the router's message
no key found into no key [redacted]. So in text they are honoured only on a line
that is plainly configuration: one that starts with set or delete, or whose last
field is quoted, which is what a configuration line looks like and what a sentence does
not.
Two ways it is applied#
The wrapper buffers the handler's response body — headers still go straight out — and then rewrites it.
| The body is | What happens |
|---|---|
| Valid JSON | Decoded, walked, and re-encoded. Every map key that is a secret leaf has its value replaced, at any depth, inside arrays too. Every string value is additionally passed through the text rule below. |
| Anything else | Treated as text: line by line, field by field. |
The text rule replaces everything from the field after the secret name to the end of the line, preserving the line's indentation:
set interfaces wireguard wg0 private-key [redacted]What it does not do#
The one place the agent blanks a secret outside this wrapper is the settings endpoint:
GET /api/admin/settings clears the stored licence key before answering, and
PUT /api/admin/settings preserves it across a write rather than letting a settings
save drop it.
Checking it on your own router#
# As a viewer:
curl -sk "$R/api/config?path=interfaces/wireguard" -H "Authorization: Bearer $VIEWER_TOKEN" \
| grep -o '"private-key":"[^"]*"'
# "private-key":"[redacted]"
# As an admin, the same call returns the key.See also#
- Role matrix — which role each endpoint compares against.
- Endpoint index — the Redacts column.
- Files and directories — where the secrets the API never returns actually live, with modes.
- Audit entries
- Roles and planes
Checked against#
agent/security.go (redactSecrets, secretLeaves,
bareSecretLeaves, redactAny, redactText, isQuoted, captureWriter),
agent/main.go (routes),
agent/admin.go (handleGetSettings, handleUpdateSettings),
agent/security_test.go,
docs/security.md.