Node.js security releases (October 2026): update Node to 22.23.4, 24.22.1 or 26.12.1 before you use yokoten
yokoten
Docs Privacy

The guide

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 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). 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.