About redaction and trust
Five safeguards protect the Log:
- Redaction. Secrets are removed and home paths rewritten before a line is written, and again before it is sent or read.
- Provenance. Every schema 2 and 3 header says how far its attribution can be trusted, in
meta.provenance. - A trusted destination. An unattended run takes its destination from the default branch only, so a pull request cannot redirect telemetry.
- Credentials from the environment. Server credentials come from environment variables, never from the committed config.
- Proven producers. Tests prove every declared event has a real producer, and every family is written only from the code that owns it.
How it works
Redaction runs at two boundaries: when a line is written, and when a stored line is sent to a server or read back. If one boundary misses a pattern, the other catches it. It walks every string in the whole event, header included. It is pure: the home directory is passed in.
What is redacted
Patterns are applied in this order:
| # | Matches | Becomes |
|---|---|---|
| 1 | A GitHub token (gho_, ghp_, ghs_, ghu_, ghr_, github_pat_) | <redacted> |
| 2 | A 40-hex GitHub token, only after token, GITHUB_TOKEN, PAT, api_key or api_token and a : or = | Keyword kept, value <redacted> |
| 3 | Bearer <token> | <redacted> |
| 4 | An AWS access key AKIA… | <redacted> |
| 5 | A Slack token xox[baprs]-… | <redacted> |
| 6 | A Stripe key: sk_, pk_ or rk_ followed by live_ or test_ | <redacted> |
| 7 | An Anthropic key sk-ant-…, or an OpenAI-shaped sk-… | <redacted> |
| 8 | An npm token npm_… | <redacted> |
| 9 | A JWT eyJ….….… | <redacted> |
| 10 | A PEM private-key block | <redacted> |
| 11 | Basic auth in a URL, https:/ | https:/ |
| 12 | A secret, password, passwd, api_key, access_key, client_secret or private_key assignment of 16 or more characters | Keyword kept, value <redacted> |
| 13 | A string starting with your home directory | ~ plus the rest |
Other fields are safe by design:
meta.machineis a sha256 of the hostname, never the name.gate·checkedstores a digest of the check's inputs (input_), never the inputs, and a structuredfingerprint reasoncode, never the check's message.driver_is an error code or class name, never a message.exited. error_ class dispatch_never stores the vendor's message.failed. usage_ limit operation.and custom-event refusals never carry values.field_ names handoffcarries no evidence or transcript.requested_is cleaned the same way as the public pause comment.decision - A finding's
descriptionis redacted and capped at 1000 characters.
Provenance
meta.provenance says how much to trust a line's attribution. From most to least trusted:
| Value | Meaning | Set by |
|---|---|---|
parent_ | The parent that started this exact child vouches for it | Role dispatch, for its own dispatch-side events |
env_correlated | An environment variable the worker controls named a role or task | The header, when VINAYA_ROLE or VINAYA_TASK is set |
self_reported | Text the writer reported about itself; proves no authorization | Planned |
unavailable | No basis | The header, by default |
Rules:
- No silent upgrade. The header never raises provenance on its own. Only a caller that passes a value can assert a stronger one, and the normal write path passes none.
- Branch-derived Issues stay untrusted. An Issue number taken from the checked-out branch (or
GITHUB_HEAD_REFin CI) forcesunavailable, even whenVINAYA_ROLEis set. - The forge confirms the branch. An Issue number taken from the branch is checked with the forge, and a number the forge denies is never recorded. If the forge does not answer, the branch's number stays, still
unavailable. - Task tools never guess.
vinaya task-turns the branch lookup off, so its events keeptools serve issue: null. - Roles come from the environment only.
subject.rolecomes only fromVINAYA_ROLE; a caller cannot declare its own.meta.actor_idis an unchecked claim. - Old CLIs are low trust. Readers treat any line from a CLI older than
0.33.0as low trust; see Telemetry.
A destination a pull request cannot change
An unattended process is every dispatched role's child process, and any run with VINAYA_ set. It honours a working-tree logs.folder or logs.url only when the default branch declares the identical value. Otherwise it falls back to the default folder, never to "no destination".
- For a server, the default branch's own
headersare used, so a pull request that edits onlyheadersis caught too. - With no local
logs, the default branch's value applies. logs.foldermust be absolute and outside the repository, for every caller.vinaya doctortreats a CI host as unattended whateverVINAYA_says.UNATTENDED - Not held to this rule:
logs.events(not a destination) andlogs.readHeaders(this machine's read credential for a url that is already approved).
The logs destination is one of the keys read from the default branch for an unattended run; see Vinaya keys and Trust.
Credentials from environment variables
${VAR_NAME}inlogs.headersandlogs.readHeadersis filled in from the sending process's own environment when the event is delivered, never from a worker's environment.- An unset variable becomes an empty string. The server then refuses authentication; nothing crashes.
- If any referenced variable is missing or empty, there is no credential. CI then records nothing, and
vinaya log selftestnames the variable. vinaya doctor's[log-credential]reports, for each variable, whether it is set, never its value. Printed findings strip the header value, the credential without its scheme, each referenced variable's value, and any credentials in a URL or its query.
Proven producers
Tests refuse a change that would let an event or a family drift:
| Test refuses | Example |
|---|---|
| A declared event with no producer | A family and event pair in the schema that no producer claims and that is not listed as an exemption; or a producer that claims an event its code does not write |
| A family written from the wrong place | A file writing a family it does not own (read from the code itself); or an owner entry naming a file that no longer writes it |
| A second writer of the queue | Any file other than the Log sink opening the retry-queue path |
| An unlisted entry point | A hook, workflow step, agent-runtime hook or check with no inventory entry; an entry whose path is gone; a route whose command lacks its token. |
| A leaking test | A test producer that could reach the Vinaya repository's own destination |
Events exempt, kept in the schema only so older lines still read: effect · attempted, effect · observed, and forge_write · validated, refused and written.
The producers are role dispatch, round assessment, the dev-review loop driver, the check runner, the effect executor, the broker, the custom-event emitter, the task-tools cancel and resume handlers, and pause and resume.
Configuration
| Key or variable | Effect |
|---|---|
logs.folder, logs.url | The destination, held to the default-branch rule |
logs.headers, logs.readHeaders | Credentials, as ${VAR} |
VINAYA_, VINAYA_ROLE | Make a caller unattended |
VINAYA_ROLE, VINAYA_TASK | Produce env_correlated provenance |