From f67c1ff6d7d0f29dc7be1b277d586b40601901b4 Mon Sep 17 00:00:00 2001 From: Nathan Herald Date: Tue, 29 Sep 2026 18:09:50 +0200 Subject: [PATCH] Check CI for private machine, person, and agent names Public repositories hold invented examples only: no machine or host names, no personal home paths, no real person IDs, no internal agent or fleet IDs. scripts/check-private-names.py fails on any of those in a tracked file, and a new workflow runs it on every push and pull request. The tests' home directories (/home/user, /home/bob, /Users/alice, /Users/dev, /Users/Dev99) are now /home/example and /Users/example, keeping the mixed case and digits the encode test needs. --- .github/workflows/private-names.yml | 15 ++++ scripts/check-private-names.py | 124 ++++++++++++++++++++++++++++ test/codex_config_test.ts | 2 +- test/encode_test.ts | 2 +- test/integration_test.ts | 8 +- test/paths_test.ts | 12 +-- 6 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/private-names.yml create mode 100755 scripts/check-private-names.py diff --git a/.github/workflows/private-names.yml b/.github/workflows/private-names.yml new file mode 100644 index 0000000..7a1b5fa --- /dev/null +++ b/.github/workflows/private-names.yml @@ -0,0 +1,15 @@ +name: Private names + +on: + pull_request: + push: + branches: [main] + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - name: No private machine, person, or agent names + run: python3 scripts/check-private-names.py diff --git a/scripts/check-private-names.py b/scripts/check-private-names.py new file mode 100755 index 0000000..9879744 --- /dev/null +++ b/scripts/check-private-names.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Keep private machine, person, and agent names out of this public repository. + +Fails when a tracked file names one of the fleet's machines, a home directory +under /home or /Users, a person/ ID, or an agent/fleet/ ID. Write invented +examples instead: web1.example.com, /home/example, /Users/example, +person/alex, agent/example/worker. + +Machine names are listed as SHA-256 digests of the lowercase name, so this +file does not publish the names it keeps out. To add a machine, append the +output of `printf %s NAME | shasum -a 256`. + +A line that must stay as it is goes in .private-names-allow: one regular +expression per line, searched in `path:text of the line`. Blank lines and +lines starting with # are ignored. + +Run it from anywhere inside the repository: python3 scripts/check-private-names.py +""" + +import hashlib +import re +import subprocess +import sys +from pathlib import Path + +MACHINE_DIGESTS = { + "a994ffcab684f9e2136b0681c20d2a5ef5f23a5cb7b64ab8934d15a888cc4ea3", + "2c619e6895b513481a07dae6c4b5781282ef1eee7a7f16744fafe7dbbd3e5ca2", + "46b6312339466d3b206325f6f402e1fae56cd65f117c779e3b9259833ffbcdf0", + "9fe430e5274621be518924ed90d2355f39eeb1d4d98fb00ea0022f3a26ba5595", + "5881d66b8738b483578c8479c59f09856a5447b47bbd2660cff7dc92e1607a2d", + "294aa8d75483b8331e3ba6a7f24aea15202747f36de65197e7bc6194880b2558", +} + +ALLOW_FILE = ".private-names-allow" + +WORD = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*") +HOME = re.compile(r"/(?:home|Users)/([A-Za-z0-9._-]+)") +PERSON = re.compile(r"(? { }); Deno.test("upsertCodexProject: round-trips a path containing special chars", () => { - const cwd = "/Users/dev/My Project.v2"; + const cwd = "/Users/example/My Project.v2"; const next = upsertCodexProject(SAMPLE, cwd, { trust_level: "trusted" }); const parsed = parseToml(next) as Record; assert(extractCodexProject(parsed, cwd) !== null); diff --git a/test/encode_test.ts b/test/encode_test.ts index 95f9b88..e5132aa 100644 --- a/test/encode_test.ts +++ b/test/encode_test.ts @@ -10,7 +10,7 @@ Deno.test("encodeCwd: dots and slashes both collapse to hyphen", () => { }); Deno.test("encodeCwd: keeps alphanumerics, including digits and mixed case", () => { - assertEquals(encodeCwd("/Users/Dev99/Proj"), "-Users-Dev99-Proj"); + assertEquals(encodeCwd("/Users/Example99/Proj"), "-Users-Example99-Proj"); }); Deno.test("encodeCwd: collapses runs of separators 1:1 (not deduped)", () => { diff --git a/test/integration_test.ts b/test/integration_test.ts index c5148ad..1d235d6 100644 --- a/test/integration_test.ts +++ b/test/integration_test.ts @@ -16,7 +16,7 @@ import { Deno.test("e2e Claude: pack on one machine, import onto another at a NEW path", async () => { await using src = await makeFakeRoot(); - const srcCwd = "/Users/alice/projects/app"; + const srcCwd = "/Users/example/projects/app"; const id = "abcd-1234"; await writeSyntheticSession(src.roots, srcCwd, id, { summary: "ported", @@ -38,7 +38,7 @@ Deno.test("e2e Claude: pack on one machine, import onto another at a NEW path", assertEquals(manifest.sidecarCount, 2); await using dst = await makeFakeRoot(); - const dstCwd = "/home/bob/work/app"; + const dstCwd = "/home/example/work/app"; const result = await importBundle(dst.home, bundle, dstCwd, { fork: false }); assertEquals(result.harness, "claude-code"); @@ -51,7 +51,7 @@ Deno.test("e2e Claude: pack on one machine, import onto another at a NEW path", assertEquals(result.sidecarsWritten, 2); assertEquals( await Deno.readTextFile( - `${dst.roots.claudeDir}/projects/-home-bob-work-app/memory/n.md`, + `${dst.roots.claudeDir}/projects/-home-example-work-app/memory/n.md`, ), "remembered\n", ); @@ -62,7 +62,7 @@ Deno.test("e2e Claude: pack on one machine, import onto another at a NEW path", Deno.test("e2e Codex: pack rollout + trust, import onto another machine at a NEW path", async () => { await using src = await makeFakeRoot(); - const srcCwd = "/Users/alice/code/proj"; + const srcCwd = "/Users/example/code/proj"; const uuid = "0a1b2c3d-4e5f-6789-abcd-ef0123456789"; await writeSyntheticCodexSession(src.home, srcCwd, uuid, { ts: "2026-06-23T23-09-12", diff --git a/test/paths_test.ts b/test/paths_test.ts index d55c7d9..99ab157 100644 --- a/test/paths_test.ts +++ b/test/paths_test.ts @@ -7,21 +7,21 @@ import { transcriptPath, } from "../src/core/paths.ts"; -const roots = rootsFromHome("/home/user"); +const roots = rootsFromHome("/home/example"); Deno.test("rootsFromHome: derives claudeDir and configPath", () => { - assertEquals(roots.claudeDir, "/home/user/.claude"); - assertEquals(roots.configPath, "/home/user/.claude.json"); + assertEquals(roots.claudeDir, "/home/example/.claude"); + assertEquals(roots.configPath, "/home/example/.claude.json"); }); Deno.test("projectsDir: points at ~/.claude/projects", () => { - assertEquals(projectsDir(roots), "/home/user/.claude/projects"); + assertEquals(projectsDir(roots), "/home/example/.claude/projects"); }); Deno.test("projectDir: appends the encoded cwd", () => { assertEquals( projectDir(roots, "/work/app"), - "/home/user/.claude/projects/-work-app", + "/home/example/.claude/projects/-work-app", ); }); @@ -29,7 +29,7 @@ Deno.test("transcriptPath: //.jsonl", () => { const id = "11111111-2222-3333-4444-555555555555"; assertEquals( transcriptPath(roots, "/work/app", id), - `/home/user/.claude/projects/-work-app/${id}.jsonl`, + `/home/example/.claude/projects/-work-app/${id}.jsonl`, ); });