Log masking#
A run's logs are masked on its machine, before they leave it. Every
credential value the run received, and common shapes of tokens and keys,
become *** in what you read in the console, with astra, or through the
API. Masking catches a secret printed by accident; it is not a way to keep a
secret from code that wants to send it somewhere.
Where masking applies#
Everything that carries a run's output off its machine is masked the same way:
| Output | Where you see it |
|---|---|
| A worker's log | The run's Logs tab, astra astraeus logs, GET $API/tasks/<worker>/logs |
| The log kept after a container is removed | The same places, marked archived in the API |
| The last lines of a failure | The failure panel, last_failure.log_tail |
| An agent run's answer | The file written to a drive (outputs.answer_path) and the receipt's digest of the output |
Masked text becomes ***. Nothing else in the output changes: line breaks,
order and the rest of each line stay as they were. Logs are cut to size
(the last 900 KiB, the last 50 lines of a failure) after masking, so a cut
never leaves half a value showing.
What is masked#
The run's own credentials, always#
Every value the run received from Astraeus:
- the files of every credential the run references
(
secret_refs), whether mounted or turned into environment variables — sealed secrets included, which reach a run as credentials too; - the environment variables set from them, as the container got them;
- the run's workload identity (
identity: true): its token and its key; - an interactive run's own access token.
Each value is also matched:
- line by line, for a multi-line value (a key file, a certificate);
- base64-encoded, standard and URL-safe, with and without padding, of the
value and of the value followed by a newline (what
base64 < fileprints); - percent-encoded, as in a URL or a form (
%40,+for a space).
Values shorter than 4 characters are not masked: they occur inside ordinary words, and masking them would shred the log and give the value away by where the masks fall.
This layer cannot be turned off. A run that asks for it
(log_redaction: {values: false}) is refused when it is created:
log_redaction.values: The run's own credential values are always masked in
its logs; only patterns can be turned off.
Common credential shapes, by default#
Values the run did not get from Astraeus but prints anyway:
| Shape | Example (masked part in bold) |
|---|---|
AWS access key ids (AKIA…, ASIA…) |
AKIAIOSFODNN7EXAMPLE |
GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_, github_pat_) |
ghp_… |
OpenAI-style keys (sk-…, sk-proj-…, sk-ant-…) |
sk-proj-… |
Hugging Face tokens (hf_…) |
hf_… |
Slack tokens (xoxb-, xoxp-, …) |
xoxb-… |
JWTs (eyJ….eyJ….…) |
eyJhbGciOi… |
Authorization: headers, Bearer, Basic or Token |
Authorization: Bearer … |
password=, secret=, token= pairs and their JSON form, including prefixed keys (DB_PASSWORD=, client_secret=, access_token=, "password": "…") |
DB_PASSWORD=… |
| Private key blocks (PEM, OpenSSH, PGP) | every line between -----BEGIN … PRIVATE KEY----- and -----END …----- |
sk-, hf_ and xox prefixes are masked only when what follows looks
random (it has a digit, or both upper and lower case), so ordinary names such
as sk-learn-model-selection-helpers stay. max_tokens= and num_tokens=
are not secrets and are not masked.
See it work#
This run prints three secrets it did not receive from Astraeus, and one ordinary line:
{
"metadata": {"name": "mask-demo"},
"spec": {
"task_template": {
"image": "busybox:1.36",
"command": "sh",
"args": ["-c", "echo 'DB_PASSWORD=hunter2-prod'; echo 'Authorization: Bearer abc123.def456'; echo 'aws key AKIAIOSFODNN7EXAMPLE'; echo 'max_tokens=512'"],
"restart_policy": "Never",
"requested_resources": {"cpu_cores": 1, "memory_bytes": 536870912}
}
}
}
Runs → New run → Edit as JSON, paste mask-demo.json, Start
run. Open the run's Logs tab.
The log reads:
Turn the patterns off#
A run whose output legitimately looks like credentials (a tool that prints test fixtures, a security scanner, a tokenizer's vocabulary) can turn the patterns off. Its own credential values are still masked.
| Field | Type | Default | Description |
|---|---|---|---|
log_redaction.patterns |
boolean | true |
Mask the common credential shapes above. |
log_redaction.values |
— | — | Not a setting. false is refused: the run's own credential values are always masked. |
log_redaction goes on the worker template (task_template, or a worker
group's), next to secret_refs. See the run specification.
Limits#
- Only the forms listed are recognised. A value that is hashed, split across lines or prints, reversed, JSON-escaped, or embedded part-way into a longer base64 text is not masked. Patterns catch only the shapes they know.
- The machine's own files are not masked. The container runtime's log files on the machine (Docker's, containerd's) keep the original output. Masking is of what leaves the machine, not of its disk; anyone with root on the machine reads the original.
- A line is shown once it ends. A line ends at a newline or a carriage
return. The last, unfinished line of a running worker is held back (up to
64 KiB) until it ends, so the start of a value is never shown before the
rest can be recognised. A progress bar that redraws one line with
\r(tqdm, pip,curl) is therefore shown one frame late: the frame being drawn appears when the next one starts. - Interactive sessions are not masked. What a notebook or another interactive run shows you live through the console, such as a cell's output, travels through the session itself, not the log, and is not masked. Only the worker's log is.
- After the machine's agent restarts, a run's values are read again from its credential files: a value rotated before the restart is then masked by the patterns only. The kept log of a run removed while the agent was down is masked by the patterns only.
- Masking does not stop a run from using a value. Code in a run can read its credentials and send them anywhere it can reach. Bound that with the run's outbound network policy.