Privacy
Source: docs/guide.md · v0.1.2
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 issuecreate,comment,editandclose;gh prcreate,comment,edit,review,mergeandclose;gh releasecreateandedit. -
The text in
--title,--body,--notes,--subjectand a close--comment. -
The files that
--body-file,--notes-fileand-Fname, after anycd. The guard runs before the command, so a file the same command writes is not there yet. It reads one shape: the whole command iscat > body.md <<'EOF' && gh … --body-file body.md, then the body andEOF. 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 lonegh … --body-file - < body.mdis fine too. A~word is only acdfolder or the file’s path. It is~alone or starts with~/, outside quotes. After acd, a path with..is not read. Each command beside gh must be one of these, which write no file:true,:,echo,popd;printfwith no argument that starts with-v;testor[without-v.setwith flags from-e,-u,+eand+u(alone or joined, as-eu), and-o pipefailor+o pipefail. A baresetdoesn’t count.cdorpushdwith one folder.- A plain assignment, but not to a name the shell uses, such as
PATH,HOMEorPS4. export, or a command’s own prefix, of onlyGH_REPO,GH_HOST,NODE_USE_ENV_PROXYandTZ=UTC.gh pr|issue|repo|run view|list|checkswithout--webor-w.git rev-parse,git config --get <name>andgit 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 apifields 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 redactreads 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 blameandgit for-each-ref, with any format;git cat-file -p;git remote -v,get-urlandshow;git config --listand reads ofuser.*andremote.*, such asgit config user.email;gh auth status; GET calls throughghon traffic and user paths;npm whoami;wrangler whoami. When the whole command is one of these, with nothing around it, the guard changes it toset -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 assudo, 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.
doctorand the session start say so once. To stop such commands there instead, set"privacy": { "output": "stop" }inyokoten.json. Then the guard stops the command and shows the filtered command to run. - git hides you at the source.
installasks before it points git’s globalmailmap.fileat~/.config/yokoten/mailmap, outside every repo. Thengit logandgit showprintownerfor your emails. With no terminal, or on a no,installleaves your git config as it is and prints the command to run. If the file is there,installonly adds the lines it lacks. If you set your ownmailmap.file,installkeeps it and tells you which lines to add. - Marks fill themselves.
installanddoctoradd what this machine knows: your git name (user.name, unless it is an address, a placeholder such asyour name, or a shared name such astest), your git email (unless it is a noreply address), the loginghuses, 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, theprivate-markscheck is ok. Only a file with no marks is missing.doctorthen 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 addadds 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 removetakes one out the same way.yokoten marks off git-name(orgit-email,forge-login) stopsinstalladding 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, runyokoten 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,pullandclone, andwrangler deployorlogin, 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.
envandprintenvare 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). 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, andXDG_CONFIG_HOME,XDG_DATA_HOME,XDG_CACHE_HOME,XDG_STATE_HOMEandXDG_RUNTIME_DIR. On Windows it also keepsSystemRoot,USERPROFILE,APPDATA,PATHEXT,ComSpecand the like. - It keeps the
YOKOTEN_*names that make the guard stricter:YOKOTEN_ROLE,YOKOTEN_FIX_TASK,YOKOTEN_PRIVATE_MARKSand a few settings. The bot’s key and app id,YOKOTEN_RELEASE_APPROVALandYOKOTEN_LEAD_ONLY_OKnever pass from your shell. The lead gives those on purpose. - It drops every other name. A name with
TOKEN,SECRET,KEY,PASSWORD,AUTH,CREDENTIALorPRIVATEin 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_KEYfor 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 CLIGOOGLE_API_KEYandGOOGLE_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.