Habit · loads whenever its situation comes up, at any step
secrets
about 780 tokens
secrets as it installs, each line coloured by where its words came from.
- 1 file
- 1 edit
- 2 sentences left out
- 1 source
- last changed 2026-10-17
Replaces airgap-secrets (retired 2026-10-08).
The text
description: "Use when a .env file, a key, a token or a credential is in reach: read for configuration, written into client-side code, staged in a commit, built into a container image, or possibly leaked. A commit or an image with no secret in reach does not call for it."
greenlineOpens the habit: a secret in reach; none enters a recordsecrets is the habit for the moment a secret is in reach: a key, a token, a credential or a `.env` file that the work reads, writes, stages in a commit or builds into an image, or a credential that may have leaked, at whatever step the work is in. A commit staged or an image built with no secret in reach does not load it. It writes no record of its own, and nothing it reads enters a record. Secrets or security is a floor in AGENTS.md: found before the mode is proposed, it raises the proposal to the mode that floor names, and found later it is reported.
Freddie Northamairgap-secrets/SKILL.mdA credential that reaches your transcript is burned. Nothing later un-burns it.
**No literal credential may appear in tool output, in a diff, or in a commit.**
## An ignore file guards one boundary only
A secret can be ignored by one tool and shipped by another. `.gitignore` keeps a
key out of history and does nothing about a container build context, which the
daemon receives whole before any build step runs.
Check each boundary separately, and say which you checked:
| Version control | `.gitignore`, staged-diff scan |
| Image build context | `.dockerignore` |
| Client bundle | build-time environment allowlist |
| Logs and errors | redaction at the logger |
**A key that one guard covers and the others do not is still loose.**
## Read out of the file, never into your shell
grep -oE '^[A-Z_][A-Z0-9_]*=' .env # which variables exist
grep -c '^[A-Z_][A-Z0-9_]*=.\+' .env # how many hold a value
Freddie Northamairgap-secrets/SKILL.md`cat .env` is a violation. So is sourcing the file and echoing lengths or
prefixes, which classifies a live secret in your output.
Report the location, never the match. A scan that prints the line it found has
put the secret in your output.
gitleaks protect --staged --redact # exits non-zero, prints nothing usable
Without a scanner, grep quietly and name the file alone:
git diff --cached --name-only | while read -r f; do
git show ":$f" | grep -qE '(AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{36}|xox[baprs]-|-----BEGIN [A-Z ]*PRIVATE KEY|://[^/[:space:]:]+:[^/[:space:]@]+@)' \
&& echo "SECRET SUSPECTED: $f"
## On a leak, revoke first
1. **Revoke the credential.** Nothing else counts until this is done.
Cleaning first leaves the secret one commit back and still valid. **Do not say
"remediated" until you confirm the revocation.**
- The report names every boundary checked, not just version control.
- Environment handling shows names and classes, never values.
- A staged-diff scan appears before each commit, naming files only.
- On a leak the first action is revocation, and the agent says so.
SKILL.md as installed, byte for byte SHA-256 c3f30f98af295aee7ddf4b712b2af248e51772e67aaea6857918deb1ed459ce8
The edits
Red is upstream's text; green is what greenline shipped in its place.
What the edit kinds mean
- method
- changes what the method does, under a recorded ruling
- correction
- fixes an error
- scope
- fits the text to this skill
- pointer
- re-points a reference
- smaller edits
- rename, relocate or adjust tool details
-
Upstream: grep -oE '^[A-Z_][A-Z0-9_]*=(sk_live|sk_test|pk_|whsec_|SG\.|AKIA|-----BEGIN)' .env
```
Shipped: grep -oE '^[A-Z_][A-Z0-9_]*=(sk_live|sk_test|pk_|whsec_|SG\.|AKIA|-----BEGIN)' .env | sed -E 's/=.*/=<live-class value>/'
```
The probe prints a label in place of a live secret's value
Full reason
the secret-class probe pipes its matches through a substitution that replaces the value with the label <live-class value>, so the output names the variable and the class and never the matched bytes; the explanatory sentence states the same (J-8, 2026-09-15)
What greenline wrote
The full reason for every passage greenline wrote, in the order of the text.
SKILL.md:6 rename
The habit's title is its roster name, secrets, which R22 gives the habit airgap-secrets became.
SKILL.md:8 method
One opening paragraph in the habit's own voice: the situation the block's rows key it by, a secret in reach, which a commit or an image with none does not meet, since QA runs loaded it with no secret in reach (J-60, the fourth family); that it writes no record and no secret enters one, and the floor secrets raise a proposal to. R1
SKILL.md:39 correction
the secret-class probe pipes its matches through a substitution that replaces the value with the label <live-class value>, so the output names the variable and the class and never the matched bytes; the explanatory sentence states the same (J-8, 2026-09-15)
Credits: Freddie Northam
What it left out
Freddie Northam
Freddie Northam, airgap-secrets/SKILL.md: 2 of 38 sentences left out
- # Airgap secrets left out: The title becomes the roster name, secrets (R22). replaced by line 6 of SKILL.md
- 16 sentences taken
- The third command matters most: it names the credential class each variable
holds, and a live key in a development environment is the common real fault. left out: the secret-class probe pipes its matches through a substitution that replaces the value with the label <live-class value>, so the output names the variable and the class and never the matched bytes; the explanatory sentence states the same (J-8, 2026-09-15) replaced by line 39 of SKILL.md
The ledger entry
greenline's ledger logs how each skill was made and changed, with a reason for each entry.
2026-10-17 2026-10-17-secrets-only-with-a-secret-in-reach
Loads only with a secret in reach, not on every commit
The habit loads only with a secret in reach, a commit or an image with none not calling for it, since QA runs loaded it with no secret in reach.
- Authorised by
- 2026-10-17 The operator's card of 2026-10-15 on the QA series, "One fix job after the fifth family", with the delegation of 2026-10-16 whose words finalise the fix job's slices: a start or a habit loads only in the situation its row names, architecture-survey at an orientation and never as one where a change record exists, secrets only with a secret in reach and ponytail before code the request leaves open; a start is named in the record only when it ran; the survey never opens its report; a session with nothing to resume says so and asks for the request; diagnose ranks its hypotheses before the first probe and keeps them ranked; and ponytail builds only on a measured need and reads simplify.md before a refactor
Rulings, R1 to R2
- The operator's card of 2026-10-15 on the QA series, "One fix job after the fifth family", with the delegation of 2026-10-16 whose words finalise the fix job's slices: a start or a habit loads only in the situation its row names, architecture-survey at an orientation and never as one where a change record exists, secrets only with a secret in reach and ponytail before code the request leaves open; a start is named in the record only when it ran; the survey never opens its report; a session with nothing to resume says so and asks for the request; diagnose ranks its hypotheses before the first probe and keeps them ranked; and ponytail builds only on a measured need and reads simplify.md before a refactor
- J-59, on the operator's words of 2026-10-09 ('okay then implement all of this'): a gist of at most twelve words on every account whose reason is stated; no text, measure or method changes
2 earlier entries
2026-10-09 2026-10-09-secrets-gists
Every account gets a short gist for the website
2026-10-08 2026-10-08-secrets-recipe
Airgap-secrets renamed secrets, with an opening on when it applies
Pages it touched: SKILL.md