Privacy
Anything that leaves the local machine is publishing. PR descriptions, issue
bodies, commit messages and their trailers, the contents of any committed
file (yes, including .gitignore comments, AGENTS.md, README), code review
comments, and content sent to external services can be cached, indexed, and read
by anyone — and stay visible even after you "delete" them. Treat every
outward-facing string as permanent and public.
Confirm before any data leaves the machine for a remote host
Treat the network boundary as a gate, not just the publish step. Before any
action sends data to a host that is not loopback and not on the local
network — i.e. anything beyond localhost/127.0.0.1/::1, the private ranges
10.*, 172.16–31.*, 192.168.*, link-local, and *.local/mDNS names — stop
and ask the user first, unless they have already, in this session, explicitly
asked for that specific action.
This is broader than PRs and commits. It includes, for example:
git push, gh pr|issue|api, and anything else touching GitHub/GitLab/etc.
curl/wget/fetch that upload or POST data to a public URL
- Uploading files or images to image hosts, pastebins, gists, transfer/CDN services
- Publishing packages (
npm publish, pip upload, container registries)
- Calling external/cloud APIs, webhooks, or remote MCP servers
- Any "share this" / "send this somewhere" that resolves to a public host
Why the gate exists: once data reaches a third-party server the user doesn't
control, it can be logged, cached, indexed, retained, and read by others —
permanently, even after deletion — and it cannot be pulled back. The
destination, the payload, and the fact that it was sent all leave the user's
hands the moment it crosses the boundary. That is the user's call to make, not
yours — and "the payload looks harmless" is not a reason to skip the ask.
Make the confirmation clear and specific
A vague "OK to push?" is not enough. The ask must let the user decide with full
information, so spell out all four:
- What data/content is being sent — name the file, the diff, the text.
- Where it's going — the remote host/service by name (e.g.
github.com,
img.shields.io), and that it is outside the local network.
- Why it needs to leave the machine for this task.
- What it means — it becomes effectively public/retained and can't be
reliably un-sent.
Template:
⚠️ This will send to <remote service / host>, which is outside
your local network. Once it's there, that service may store, cache, or index it,
and it can't be reliably deleted. Want me to go ahead?
The only bypass is prior authorization: if the user said "push it", "open the PR",
or "upload the screenshot to X", that specific egress is covered — don't re-ask
for the thing they just told you to do. But authorization for one destination or
payload does not extend to another: pushing to GitHub ≠ uploading an image to
a third-party host. Confirm each new destination separately.
The core rule: never describe what was hidden
If something is excluded for privacy (gitignored, redacted, kept local), do
not name it in any public artifact. The exclusion itself is sensitive.
Saying "I excluded settings.json, start-agent.sh, and
.agent/settings.local.json" leaks the exact filenames, paths, and existence
of the private files — handing a reader the map you were trying to withhold.
- ❌ "Personal files (
settings.json, start-agent.sh, .agent/settings.local.json) are gitignored and excluded."
- ✅ "Local/personal config is gitignored." — or say nothing at all.
- ✅ Better: don't mention the exclusion in the public artifact; report it to the user in chat instead, where the detail is useful and stays private.
The distinction that matters: chat with the user is private; the PR is public.
Details that help the user (specific filenames, paths, IPs, what was excluded
and why) belong in your reply to them — never copied into the published text.
This applies to committed files, not just PR/commit prose:
- A committed
.gitignore must list a pattern to ignore it, but keep its comments
generic (# local/personal — gitignored); don't enumerate the personal
filenames in editorial comments or repeat them in AGENTS.md/README/docs.
- Better still: a file that only exists on your machine (e.g. a personal
launcher script) doesn't belong in the committed
.gitignore at all — ignore it
via .git/info/exclude (local, never committed) so its name is never published.
- ❌ committed
AGENTS.md: "Keep machine-local files (settings.json,
start-agent.sh, .agent/) out of git."
- ✅ committed
AGENTS.md: "Machine-local config is gitignored; committed source of
truth lives under Agents/." — names nothing personal.
Never put these in outward-facing text
- Absolute home/user paths (
/home/<user>/..., /Users/<user>/...)
- Private/LAN IPs and hostnames (
192.168.*, 10.*, internal DNS names)
- Other projects, clients, or repos unrelated to the current one
- Names of gitignored / redacted /
.local.* / .env* files
- Tokens, keys, credentials, emails, session URLs (obvious, but scan anyway)
- Machine-specific or harness-specific config (permission modes, local aliases)
- Agent session URLs / IDs in commit trailers (
Claude-Session: https://claude.ai/code/session_..., Codex-Session: ...) — never commit these
- Harness/model-build detail bolted onto trailers or generated-by text (
(1M context), (fast mode), internal model IDs, local sandbox/permission details)
Commit trailers — the one safe shape
Commit messages may get auto-appended trailers, and they are committed history
the same as the message body. Claude, Codex, and other agent tools can all leak
through this channel. Watch for these:
Session trailers such as Claude-Session:, Codex-Session:,
OpenAI-Session:, or any other session URL/ID. A session URL/ID is a private
identifier for the conversation — it is on the never-publish list above.
Strip it from every commit. It must never reach a pushed branch.
Co-Authored-By: with extra parenthetical or harness details. If a tool
appends a co-author trailer, the only acceptable shape is a neutral agent/model
name + the normal noreply address, nothing else:
- ✅
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ✅
Co-Authored-By: Codex <noreply@openai.com> if that is the tool's normal neutral identity
- ❌
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ❌
Co-Authored-By: Codex (sandbox: workspace-write, model build: internal) <noreply@openai.com>
- ❌ any
Claude-Session:, Codex-Session:, OpenAI-Session:, or session-URL line
Generated-by footers that include session links, local paths, internal model
identifiers, sandbox settings, or execution environment details. Remove the
private detail; if attribution is needed, keep it generic.
Before committing, read the trailers and generated-by footers, not just the
message body. These ride along on every commit, so one leaked session URL
becomes one-per-commit across a branch.
Before publishing, do a leak pass
Before gh pr create/edit, gh issue ..., git commit, posting a review
comment, or sending content to any external service, re-read the exact text and
grep your own draft for: home paths, private IPs, other project names, and the
names of anything you deliberately excluded. If a detail is only useful to the
user, move it to chat.
Also, before committing, run the pass over the parts that aren't the message body:
- Commit trailers —
git log <base>..HEAD --format='%(trailers)' (or just read
them): no Claude-Session:, Codex-Session:, other session trailers, or
session URLs; co-author lines use the exact safe shape.
- Committed files you're adding/editing — does any
.gitignore comment,
AGENTS.md, README, or doc name an excluded personal file or describe what
was withheld? git grep -nE 'start-(claude|codex|agent)|settings\.json|\.env|/home/|/Users/' -- <staged paths>.
If you already leaked
Fix it immediately, then tell the user:
- PR/issue body or title:
gh pr edit <n> --body ... / gh issue edit.
- Review/issue comment: edit or delete it (
gh api for comment endpoints).
- Commit message or trailer: amend if unpushed; if pushed, warn the user that
history rewrite + force-push is required and that the old content may persist in
forks, caches, and the provider's reflog. A leak in one commit is
git commit --amend; the same trailer across many commits needs a
git rebase reword (or git filter-repo --message-callback) over the range.
- Treat any leaked secret as compromised — rotation, not just deletion, is the
real fix.
Why this skill exists
An agent listed three gitignored personal filenames in a public PR description
while explaining that they'd been excluded — defeating the exclusion. The
exclusion was correct; advertising it in a public artifact was the leak.
A later incident leaked two more ways at once: every commit on a pushed branch
carried an agent session URL and a Co-Authored-By: ... (1M context) trailer,
and a committed AGENTS.md + .gitignore comment spelled out machine-local
filenames. The message bodies and the PR description were clean — the leaks
were entirely in trailers and committed-file prose, the parts that are easy to
skip on a leak pass. This applies equally to Claude, Codex, and any other agent
that writes commit text, docs, comments, or outbound payloads.
The same principle extends to the network boundary itself: sending data to any
third-party host the user didn't ask you to contact is a leak waiting to happen,
no matter how innocuous the payload looks. The user, not the agent, decides what
is allowed to leave their machine — so ask first, clearly, every new destination.
1---2name: privacy3description: Prevent Claude, Codex, or any other agent from leaking private, local, or sensitive information into outward-facing artifacts (PR/issue/commit text, comments, public files, external services). Use before writing anything that leaves the local machine — especially PR descriptions, commit messages, issue bodies, and review comments. Also requires clear, explicit user confirmation before sending any data to a remote host outside the local network.4---56# Privacy78Anything that leaves the local machine is **publishing**. PR descriptions, issue9bodies, commit messages **and their trailers**, the **contents of any committed10file** (yes, including `.gitignore` comments, `AGENTS.md`, `README`), code review11comments, and content sent to external services can be cached, indexed, and read12by anyone — and stay visible even after you "delete" them. Treat every13outward-facing string as permanent and public.1415## Confirm before any data leaves the machine for a remote host1617Treat the **network boundary** as a gate, not just the publish step. Before any18action sends data to a host that is **not** loopback and **not** on the local19network — i.e. anything beyond `localhost`/`127.0.0.1`/`::1`, the private ranges20`10.*`, `172.16–31.*`, `192.168.*`, link-local, and `*.local`/mDNS names — **stop21and ask the user first**, unless they have already, in this session, explicitly22asked for that specific action.2324This is broader than PRs and commits. It includes, for example:2526- `git push`, `gh pr|issue|api`, and anything else touching GitHub/GitLab/etc.27- `curl`/`wget`/`fetch` that **upload** or POST data to a public URL28- Uploading files or images to image hosts, pastebins, gists, transfer/CDN services29- Publishing packages (`npm publish`, `pip upload`, container registries)30- Calling external/cloud APIs, webhooks, or **remote** MCP servers31- Any "share this" / "send this somewhere" that resolves to a public host3233Why the gate exists: once data reaches a third-party server the user doesn't34control, it can be **logged, cached, indexed, retained, and read by others —35permanently, even after deletion** — and it cannot be pulled back. The36destination, the payload, and the *fact that it was sent* all leave the user's37hands the moment it crosses the boundary. That is the user's call to make, not38yours — and "the payload looks harmless" is not a reason to skip the ask.3940### Make the confirmation clear and specific4142A vague "OK to push?" is not enough. The ask must let the user decide with full43information, so spell out all four:44451. **What** data/content is being sent — name the file, the diff, the text.462. **Where** it's going — the remote host/service by name (e.g. `github.com`,47 `img.shields.io`), and that it is **outside the local network**.483. **Why** it needs to leave the machine for this task.494. **What it means** — it becomes effectively public/retained and can't be50 reliably un-sent.5152Template:5354> ⚠️ This will send **<what>** to **<remote service / host>**, which is outside55> your local network. Once it's there, that service may store, cache, or index it,56> and it can't be reliably deleted. Want me to go ahead?5758The only bypass is prior authorization: if the user said "push it", "open the PR",59or "upload the screenshot to X", that specific egress is covered — don't re-ask60for the thing they just told you to do. But authorization for one destination or61payload does **not** extend to another: pushing to GitHub ≠ uploading an image to62a third-party host. Confirm each new destination separately.6364## The core rule: never describe what was hidden6566If something is excluded for privacy (gitignored, redacted, kept local), do67**not** name it in any public artifact. **The exclusion itself is sensitive.**6869Saying "I excluded `settings.json`, `start-agent.sh`, and70`.agent/settings.local.json`" leaks the exact filenames, paths, and existence71of the private files — handing a reader the map you were trying to withhold.7273- ❌ "Personal files (`settings.json`, `start-agent.sh`, `.agent/settings.local.json`) are gitignored and excluded."74- ✅ "Local/personal config is gitignored." — or say nothing at all.75- ✅ Better: don't mention the exclusion in the public artifact; report it to the user **in chat** instead, where the detail is useful and stays private.7677The distinction that matters: **chat with the user is private; the PR is public.**78Details that help the user (specific filenames, paths, IPs, what was excluded79and why) belong in your reply to them — never copied into the published text.8081This applies to **committed files**, not just PR/commit prose:8283- A committed `.gitignore` must list a pattern to ignore it, but keep its comments84 generic (`# local/personal — gitignored`); don't enumerate the personal85 filenames in editorial comments or repeat them in `AGENTS.md`/`README`/docs.86- Better still: a file that only exists on *your* machine (e.g. a personal87 launcher script) doesn't belong in the committed `.gitignore` at all — ignore it88 via `.git/info/exclude` (local, never committed) so its name is never published.89- ❌ committed `AGENTS.md`: "Keep machine-local files (`settings.json`,90 `start-agent.sh`, `.agent/`) out of git."91- ✅ committed `AGENTS.md`: "Machine-local config is gitignored; committed source of92 truth lives under `Agents/`." — names nothing personal.9394## Never put these in outward-facing text9596- Absolute home/user paths (`/home/<user>/...`, `/Users/<user>/...`)97- Private/LAN IPs and hostnames (`192.168.*`, `10.*`, internal DNS names)98- Other projects, clients, or repos unrelated to the current one99- Names of gitignored / redacted / `.local.*` / `.env*` files100- Tokens, keys, credentials, emails, session URLs (obvious, but scan anyway)101- Machine-specific or harness-specific config (permission modes, local aliases)102- **Agent session URLs / IDs in commit trailers** (`Claude-Session: https://claude.ai/code/session_...`, `Codex-Session: ...`) — never commit these103- **Harness/model-build detail bolted onto trailers or generated-by text** (`(1M context)`, `(fast mode)`, internal model IDs, local sandbox/permission details)104105## Commit trailers — the one safe shape106107Commit messages may get auto-appended trailers, and they are committed history108the same as the message body. Claude, Codex, and other agent tools can all leak109through this channel. Watch for these:110111- **Session trailers** such as `Claude-Session:`, `Codex-Session:`,112 `OpenAI-Session:`, or any other session URL/ID. A session URL/ID is a private113 identifier for the conversation — it is on the never-publish list above.114 **Strip it from every commit.** It must never reach a pushed branch.115- **`Co-Authored-By:` with extra parenthetical or harness details.** If a tool116 appends a co-author trailer, the only acceptable shape is a neutral agent/model117 name + the normal noreply address, nothing else:118119 - ✅ `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`120 - ✅ `Co-Authored-By: Codex <noreply@openai.com>` if that is the tool's normal neutral identity121 - ❌ `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`122 - ❌ `Co-Authored-By: Codex (sandbox: workspace-write, model build: internal) <noreply@openai.com>`123 - ❌ any `Claude-Session:`, `Codex-Session:`, `OpenAI-Session:`, or session-URL line124- **Generated-by footers** that include session links, local paths, internal model125 identifiers, sandbox settings, or execution environment details. Remove the126 private detail; if attribution is needed, keep it generic.127128Before committing, read the trailers and generated-by footers, not just the129message body. These ride along on *every* commit, so one leaked session URL130becomes one-per-commit across a branch.131132## Before publishing, do a leak pass133134Before `gh pr create/edit`, `gh issue ...`, `git commit`, posting a review135comment, or sending content to any external service, re-read the exact text and136grep your own draft for: home paths, private IPs, other project names, and the137names of anything you deliberately excluded. If a detail is only useful to the138user, move it to chat.139140Also, before committing, run the pass over the parts that aren't the message body:141142- **Commit trailers** — `git log <base>..HEAD --format='%(trailers)'` (or just read143 them): no `Claude-Session:`, `Codex-Session:`, other session trailers, or144 session URLs; co-author lines use the exact safe shape.145- **Committed files you're adding/editing** — does any `.gitignore` comment,146 `AGENTS.md`, `README`, or doc *name* an excluded personal file or describe what147 was withheld? `git grep -nE 'start-(claude|codex|agent)|settings\.json|\.env|/home/|/Users/' -- <staged paths>`.148149## If you already leaked150151Fix it immediately, then tell the user:152153- PR/issue body or title: `gh pr edit <n> --body ...` / `gh issue edit`.154- Review/issue comment: edit or delete it (`gh api` for comment endpoints).155- Commit message or trailer: amend if unpushed; if pushed, warn the user that156 history rewrite + force-push is required and that the old content may persist in157 forks, caches, and the provider's reflog. A leak in *one* commit is158 `git commit --amend`; the same trailer across *many* commits needs a159 `git rebase` reword (or `git filter-repo --message-callback`) over the range.160- Treat any leaked secret as compromised — rotation, not just deletion, is the161 real fix.162163## Why this skill exists164165An agent listed three gitignored personal filenames in a public PR description166while explaining that they'd been excluded — defeating the exclusion. The167exclusion was correct; advertising it in a public artifact was the leak.168169A later incident leaked two more ways at once: every commit on a pushed branch170carried an agent session URL and a `Co-Authored-By: ... (1M context)` trailer,171and a committed `AGENTS.md` + `.gitignore` comment spelled out machine-local172filenames. The message *bodies* and the PR description were clean — the leaks173were entirely in trailers and committed-file prose, the parts that are easy to174skip on a leak pass. This applies equally to Claude, Codex, and any other agent175that writes commit text, docs, comments, or outbound payloads.176177The same principle extends to the **network boundary itself**: sending data to any178third-party host the user didn't ask you to contact is a leak waiting to happen,179no matter how innocuous the payload looks. The user, not the agent, decides what180is allowed to leave their machine — so ask first, clearly, every new destination.