---
title: "Privacy"
description: "「Privacy」：yokoten 使用指南的一章。"
canonical: https://yokoten.app/zh-tw/docs/guide/privacy
lang: en
source: https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/guide.md
ref: "v0.1.2"
---

> For AI agents: the documentation index is at https://yokoten.app/llms.txt. To set up yokoten in a project, follow https://yokoten.app/agents.md.

> 這一頁還沒有繁體中文版，下面是英文原文。

# Privacy

Rule 11 has a check. It scans for personal email addresses and IP addresses, in CI and
on every commit. On your own machine it also reads `~/.config/yokoten/private-marks`:
your name, your city, your domains, your server names. That file stays outside the
repo, because a list of what must not be published can't itself be published. A hit
is reported by file and line; the mark itself is never printed. The hit names the kind
of mark, such as `matches your git name (a private mark yokoten added by itself)` or
`matches a word you added to your private marks`. Its fix says how to change the mark:
`yokoten marks off git-name` for a kind yokoten adds, `yokoten marks remove` for your own.

`YOKOTEN_PRIVATE_MARKS` names a second marks file. The check reads both files and uses
every mark in them. So the setting can only add marks, never remove them. When
`~/.config/yokoten/private-marks` is there but can't be read, the check fails, whatever the
setting says. `yokoten marks add` and `install` still write to the file in your home folder.
The machine allow file (below) is read only from `~/.config/yokoten`, never from beside the
setting's file.

An agent can't move the home folder either. `HOME=/tmp/x git commit` would send the commit
check to look for marks in another folder, find none, and pass. So the guard refuses a
command that sets `HOME` or `USERPROFILE` (in any case) for git, gh, yokoten, or a shell
or `eval` that runs them, also after `env` or `sudo`. It refuses a setting for the rest of
the line too: `export`, `declare`, `read`, `printf -v`, a `for` loop or `${HOME:=x}`.
Before another program the setting passes: `HOME=$(mktemp -d) npm test` keeps a test away
from your files. Reading `$HOME` is fine.

Unsetting `HOME` or `USERPROFILE` is refused.
So is `env -u HOME` before git, gh, yokoten or a shell.
`env -u HOME npm test` still passes.

A worker cannot set `HOME`, `USERPROFILE` or `XDG_CONFIG_HOME` before `git commit`,
`yokoten check`, or a shell that runs them. `env` and `export` count the same way.
A worker cannot unset those names before the commit check.
In a desk, the runner commits for the worker.

A worker cannot run a shell script from a file (`bash script`, `sh script`).
The guard cannot read that file, so it refuses.
`bash -c` stays allowed, because the script text is in the command.
An option that may name a file, such as `--rcfile`, is refused too.

When the marks file under `$HOME` is missing, or it holds no marks, the check reads the file in the real home.
The real home comes from the password database, not from `$HOME`.
A missing file there is not an error.
A file it cannot read stops the check.
A file it can read is used, so a changed `$HOME` does not skip those marks.
With no `HOME`, the check still reads the marks file in the real home.

A mark of letters and spaces, such as a name or a city, is found as whole words. Only a
letter next to it hides it: `sam` is found in `hi Sam`, `sam_dev`, `@sam` and `sam97`, but
not in `sample`. A backslash escape such as `\n`, a URL-encoded `%2F`, `%5C` or `%20`, and a
change from a small letter to a capital (`githubSam`, `SamDev`) count as edges too. The
words of a longer mark may have spaces, `-`, `_` or `.` between them, or nothing, so `sam
example` is found in `sam-example` and `samexample`. `redact` hides a mark by the same rules.
Any other mark, such as a domain, an address or a number, is found anywhere, even inside a
word. So is a name in a script that joins words or particles onto a name: Chinese,
Japanese, Korean, Thai, Arabic or Hebrew.

The project's own public address, such as `support@` at its domain, is found too. Nothing
is let through on its own. If the address is public on purpose, add it to
`privacy.allow` in yokoten.json.

The marks file is for the whole machine, so a private project's name belongs in it. With no
setup, a mark never blocks the repo it names: when a mark is the name of the repo's origin
remote, `check`, the commit hook and the guard for `gh` posts skip that one mark there.
`Quiet Vale` names `someone/quiet-vale`, `Quiet_Vale` and `quiet.vale`: case aside, and `-`,
`_`, `.` and spaces each count as a break between words. A break is never dropped, so
`Li Na` does not name `lina`. It must be the whole name, never a part. Every other mark still
applies, and so does that one in every other repo. yokoten skips nothing when it can't tell the
repo: two remotes, no origin, a post to another repo, an origin address with `?` or `#`, a
host address whose path is not just `owner/name`, a remote helper (`ext::`, `gcrypt::` or any
`<transport>::`), or a folder or host on this machine as origin (`localhost`, `127.x.x.x`, `::1`). Only
origin counts; in CI too, origin is the repo the job checked out. The commit check trusts origin
at commit time, so a later push to another remote is not checked. `doctor` says how many
marks are skipped because they name the repo, never which. To turn this off, set
`"privacy": { "skipOwnName": false }` in yokoten.json. For every repo on this machine, put
`{ "skipOwnName": false }` in `private-marks-allow.json` (below); it needs no `repos` list. The steps below are for the
cases this can't see. A project's own repo can also list the name in
`privacy.allow` in its yokoten.json. A mark is let through when an entry there is the same string, upper or
lower case aside; a part or a longer string does not count. It is let through only in
that repo, by `check`, the commit hook and the guard for `gh` posts. Every other repo
still stops it. `doctor` says how many of your marks the project allows, never which.

A repo with no yokoten.json can do the same without one, and so can a repo that must not
name yokoten in public. Run `yokoten marks allow <mark> --here` in it. The allow is kept
on this machine, in `~/.config/yokoten/private-marks-allow.json`, for that repo only,
by its `origin` remote (host and path). Nothing is written into the repo. A repo with no
`origin` can't have this allow; it can use `privacy.allow` instead. A renamed remote is
another repo, so the mark is stopped there again. A port other than the usual one is part of
the name, and a local path is the folder it points to. `git@host:path` and
`https://host/path` name the same repo, as the forges treat them.

yokoten allows nothing when it can't be sure where a post or a push goes, and `doctor` says
why. That is when `origin` is not the only remote, gh has a repo of its own (`gh repo
set-default`), `remote.pushDefault` or a `branch.*.pushRemote` is set, or `origin` has fetch
or push addresses that name different repos, as git rewrites them with `insteadOf`. It is
also when two entries match. A `gh` post gets neither allow, this one or `privacy.allow`, when
it may go to another repo: `-R` or `--repo`, an issue or pull request URL, `GH_REPO` or
`GH_HOST`, and every `gh api` or `gh gist`. The post must also be the only command on the
line. Outside quotes, only letters, digits, spaces and `_ . / : , = + @ % -` count as plain;
any other character, such as `$`, `{`, `*`, `\`, `;` or `>`, gets no allow. Inside double quotes,
`$`, `` ` `` and `\` get no allow; single quotes may hold anything. One heredoc is fine when its
end word is a name in single quotes, such as `<<'EOF'`; its body may then hold anything. One `cd` within
the same repo before it is fine. Anything else on the line could change where gh posts, so the post is
checked against every mark. `doctor` says
where an allow comes from: this machine or `privacy.allow`. Only a mark in the marks file can
be allowed. To take it back, run `yokoten marks allow --remove <mark> --here`.

Some files must name your account. So the forge login that yokoten added to the marks is
fine in a CODEOWNERS owner, an `@` mention in `.github/`, an author or owner field of
`package.json` or a plugin manifest, and a LICENSE copyright line. It is found everywhere
else. A mark you wrote yourself is found everywhere.

The IP check covers IPv4 only, private ranges such as `10.x` and `192.168.x` included;
IPv6 is not checked. Localhost and the documentation ranges (`192.0.2.0/24` and its two
siblings) are fine. Browser version strings such as `Chrome/120.0.0.0` are not
addresses, and neither is a retina image name such as `logo@2x.png`. An address after
`http://` or in a path such as `api/` is still caught.

The same check catches secrets: GitHub, npm, Slack, Stripe, Google, OpenAI and
Anthropic tokens, AWS access key ids, and PEM private keys. It also matches the real
value of a secret-looking variable in your environment. Each hit says its grade:
`sure` fails, and `likely` and `unknown` warn. A hit names the kind of secret, never
the secret. Deleting it is not enough, since git keeps it: rotate it.

For a false hit, start a comment on that line with `yokoten-ignore secret: why it is safe`. Use the finding's rule name instead of `secret`.
Or write `secret: why it is safe` in `.yokotenignore`; an ignored hit stays listed, and a reasonless ignore fails.

A `sure` secret and a private-mark hit can't be ignored on their line: only
`.yokotenignore` lets them through, and only the lead may change that file. On a pull
request, `yokoten check --pr` shows each ignore the pull request adds as a notice, so the
reviewer sees it.

The tool-call guard runs the same check on text an agent is about to post with `gh`,
plain or through `yokoten bot -- gh`:

- `gh issue` `create`, `comment`, `edit` and `close`; `gh pr` `create`, `comment`,
  `edit`, `review`, `merge` and `close`; `gh release` `create` and `edit`.
- The text in `--title`, `--body`, `--notes`, `--subject` and a close `--comment`.
- The files that `--body-file`, `--notes-file` and `-F` name, after any `cd`. The guard runs
  before the command, so a file the same command writes is not there yet. It reads one
  shape: the whole command is `cat > body.md <<'EOF' && gh … --body-file body.md`, then the
  body and `EOF`. The path is plain and the same on both sides, and the delimiter is a name
  in single quotes, so the shell keeps the body as written. The guard checks the body, and
  the file already there, if any. A heredoc word with a `\` at a line end is refused.

  Any other way, a file on disk is read only when the command is one plain line: words
  joined by `;` or `&&`. Outside quotes there is no `\`, `$`, backtick, `<`, `>`,
  `|`, lone `&`, `(`, `)`, `[`, `{`, `}`, `#`, `*` or `?`. Inside `"…"` there is no `$`,
  backtick, `\` or `!`. A lone `gh … --body-file - < body.md` is fine too. A `~` word is
  only a `cd` folder or the file's path. It is `~` alone or starts with `~/`, outside quotes.
  After a `cd`, a path with `..` is not read. Each command beside gh
  must be one of these, which write no file:

  - `true`, `:`, `echo`, `popd`; `printf` with no argument that starts with `-v`; `test`
    or `[` without `-v`.
  - `set` with flags from `-e`, `-u`, `+e` and `+u` (alone or joined, as `-eu`), and
    `-o pipefail` or `+o pipefail`. A bare `set` doesn't count.
  - `cd` or `pushd` with one folder.
  - A plain assignment, but not to a name the shell uses, such as `PATH`, `HOME` or `PS4`.
  - `export`, or a command's own prefix, of only `GH_REPO`, `GH_HOST`,
    `NODE_USE_ENV_PROXY` and `TZ=UTC`.
  - `gh pr|issue|repo|run view|list|checks` without `--web` or `-w`.
  - `git rev-parse`, `git config --get <name>` and `git remote get-url`.
  - Any of these through `yokoten bot --`.

  Otherwise, a file the guard can't find, or one the command may change, stops the
  command: write the file in one command, then post it in the next, with its full path.
- A body from stdin: a heredoc, a `<<<` here-string, or a `< file`.
- `gh api` fields and `--input`, on a call that writes: a POST, PATCH or PUT, or a GraphQL
  mutation. A read, such as a search or a GraphQL query, passes.

A hit stops the command with the same fix as the commit check. A public address goes in
`privacy.allow`. The guard does not see text built when the command runs:
`$(cat notes.md)`, a variable, or a pipe from another program. It reads files of at most
1 MB, and lets a bigger one through. It reads the command, not your shell: a function or
alias from your shell profile that wraps `cd` or `echo` is not seen.

## What an agent reads

The check above keeps your details out of the repo. An agent also reads command output,
and that output goes into the transcript and to the model. It can't be taken back. So
yokoten hides your details before some commands run.

- **`yokoten redact`** reads text on stdin and writes it with placeholders. It hides
  secrets (`<secret>`), private email addresses (`<email>`), your git user.name and
  your Linux and Windows user names (`<owner>`), your home folder (`~`), the hosts of
  private remotes (`<host>`) and your private marks. A mark that looks like a domain
  becomes `<host>`; any other mark becomes `<mark>`. It finds a mark as the check does (see above).
  Without a private-marks file, it still hides the patterns and what git knows. If it
  can't read your marks file or can't hide a piece, it prints no more of the output.
  It says so in one line on stderr and exits 1.
- **In Claude Code, the guard filters plain reads.** These commands print account
  details: `git log`, `git show`, `git shortlog <revision>`, `git blame` and `git for-each-ref`,
  with any format; `git cat-file -p`; `git remote -v`, `get-url` and `show`; `git config --list` and reads of
  `user.*` and `remote.*`, such as `git config user.email`; `gh auth status`; GET calls through `gh` on traffic and user
  paths; `npm whoami`; `wrangler whoami`. When the whole command is one of these, with
  nothing around it, the guard changes it to
  `set -o pipefail # yokoten-redact`, then `( <command> ) 2>&1 | <node> <yokoten> redact`.
  If redact cannot run, the output is hidden, and one line on stderr says so.
  The exit code stays the command's; it is 1 when only redact failed. A command with `&&`, `;`, a pipe, a redirect, a
  substitution, a prefix such as `sudo`, or an option that writes a file, is left as it
  is.
- **Other agents run the command as it is.** Their hooks can't change a command.
  `doctor` and the session start say so once. To stop such commands there instead, set
  `"privacy": { "output": "stop" }` in `yokoten.json`. Then the guard stops the command
  and shows the filtered command to run.
- **git hides you at the source.** `install` asks before it points git's global
  `mailmap.file` at `~/.config/yokoten/mailmap`, outside every repo. Then `git log` and
  `git show` print `owner` for your emails. With no terminal, or on a no, `install`
  leaves your git config as it is and prints the command to run. If the file is there, `install` only adds the lines it
  lacks. If you set your own `mailmap.file`, `install` keeps it and tells you which lines
  to add.
- **Marks fill themselves.** `install` and `doctor` add what this machine knows: your
  git name (`user.name`, unless it is an address, a placeholder such as `your name`, or a
  shared name such as `test`), your git email (unless it is a noreply address), the login
  `gh` uses, and the hosts of private remotes. A git name that is your login is added
  once, as the login. They only add lines, and never print a value. Unlike the login, the
  git name is found in a LICENSE line and an author field too: you may hide your real name
  behind a noreply address and the mailmap. When the file has a mark, of any kind, the
  `private-marks` check is ok. Only a file with no marks is missing. `doctor` then offers
  the kinds it can't find, such as your city and your domains, as an optional note. Only
  you can add those, and your real name if git does not have it.
- **More control: `yokoten marks`.** Alone, it counts your marks by kind, never the values.
  `yokoten marks add` adds one of your own: type it in the terminal, where it is not shown,
  or pipe it in. It is never a word on the command line, which your shell history keeps.
  `yokoten marks remove` takes one out the same way. `yokoten marks off git-name` (or
  `git-email`, `forge-login`) stops `install` adding that kind and removes the marks it
  added for it. The choice is kept in `~/.config/yokoten/machine.json`. To go back to the
  default, run `yokoten marks on git-name`: it adds the name again. Marks you added
  yourself are never touched.

The wrapped command needs bash. On Windows, run agents in Git Bash or WSL; PowerShell has
no `pipefail`.

What is not hidden, said plainly:

- **Commands that change or send something.** `git push`, `fetch`, `pull` and `clone`,
  and `wrangler deploy` or `login`, run as they are. A push error can show a private
  host. Rewriting them would stop a worker that runs without prompts.
- **Files an agent reads.** `cat CODEOWNERS`, or the agent's own file reader, shows the
  file as it is.
- **The environment.** `env` and `printenv` are not filtered: they print secrets of
  every kind. Don't run them in an agent session.
- **Browser pages and screenshots.** Don't read account pages as text.

A hook can't know every command. When output shows your details, open an incident, and
give the command a row in `ACCOUNT_PRINTERS` (`src/rules/guards.ts`).

## What an agent's environment holds

Your shell may hold tokens and keys. An agent that sees them can print them, push with them,
or send them out. So yokoten has one function that prepares a clean environment for an
agent, for every runtime (#613). It ships now, with `yokoten worker env` to show its result.

`yokoten desk` starts an agent with this environment (see [The desk](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md#the-desk)). An agent
started any other way gets the whole environment of the session that starts it. So start
the lead's session from a shell without tokens.

What the function keeps and drops:

- **It keeps** the names every program needs: `PATH`, `HOME`, `LANG`, `LANGUAGE`, `LC_*`,
  `TERM`, `COLORTERM`, `TZ`, `TMPDIR`, `TEMP`, `TMP`, `USER`, `LOGNAME`, `SHELL`, and
  `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_CACHE_HOME`, `XDG_STATE_HOME` and
  `XDG_RUNTIME_DIR`. On Windows it also keeps `SystemRoot`, `USERPROFILE`, `APPDATA`,
  `PATHEXT`, `ComSpec` and the like.
- **It keeps the `YOKOTEN_*` names that make the guard stricter:** `YOKOTEN_ROLE`,
  `YOKOTEN_FIX_TASK`, `YOKOTEN_PRIVATE_MARKS` and a few settings. The bot's key and app id,
  `YOKOTEN_RELEASE_APPROVAL` and `YOKOTEN_LEAD_ONLY_OK` never pass from your shell. The lead
  gives those on purpose.
- **It drops every other name.** A name with `TOKEN`, `SECRET`, `KEY`, `PASSWORD`, `AUTH`,
  `CREDENTIAL` or `PRIVATE` in it is dropped too. There are two exceptions:
  `YOKOTEN_PRIVATE_MARKS`, which is only a path, and a runtime's own login names.
- **It drops a value shaped like a key:** GitHub, GitLab, Hugging Face, AWS, Google, Slack,
  JWT, `sk-` keys, private keys, and a URL with a password in it. It also drops a value
  longer than 32 KiB, as too long to check.
- **A vendor's own login:** each runtime file names the login variable of its vendor,
  such as `ANTHROPIC_API_KEY` for Claude Code. Only that runtime's agent gets it. General
  credentials never count as a login: `GH_TOKEN`, `GITHUB_TOKEN`, AWS keys, and for Gemini
  CLI `GOOGLE_API_KEY` and `GOOGLE_APPLICATION_CREDENTIALS`. A Vertex AI setup needs the
  lead to pass those on purpose.

To see what an agent would get from your shell, run `yokoten worker env`. Add a runtime,
such as `yokoten worker env claude-code`, to count its login name too. It lists the names
and counts what it drops. It never shows a value.

## Time zones

A commit also records the time it was made, with your time zone, and a time zone
narrows down where you live. So the commit check asks for UTC: `init` sets `TZ=UTC` for
agent sessions, and for your own commits it tells you how.

---

上一章: [Managing agents like a team](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md)
下一章: [What it doesn't do](https://yokoten.app/zh-tw/docs/guide/what-it-doesnt-do.md)
