Skip to content

[security] - Classify the Keychain CLI and uv in the telemetry egress inventory - #1637

Merged
cgfixit merged 15 commits into
mainfrom
claude/otel-hardening-sweep-2026-10-13
Oct 13, 2026
Merged

cgfixit merged 15 commits into
mainfrom
claude/otel-hardening-sweep-2026-10-13

Conversation

@cgfixit

@cgfixit cgfixit commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Proposed changes

Output of a full /otel-hardening run on 2026-10-13 against main @ 6519135. The static checker and its mutation self-test were green before any edit; the live vendor sweep re-verified every category-1 control at the pinned versions; the only real findings were two components the classification inventory (T13) had never heard of, plus stale evidence text. The first commit closes those. The later commits (1f7aa63, d1ae1a7, 2e0ccb0, e0229f9, 5081ec5, d791d62, 3d1debd, 2f64b05, 4e1c8ad, 37aa395, 9e01b0f from ten Codex review rounds, and 23c7c36 from the owner's boundary decision) close the mechanism that let them slip: every fixed binary now has verified spawn sites pinned to their invocations across a complete per-binary sweep of Python, shell and PowerShell, the sweep is consistent in both directions including alias targets, evidence paths must exist inside the repository, every install-time network operation is classified as the functional egress it is, the one shell surface that ran a telemetry-capable binary without the canonical block now exports it and fails closed when it cannot, both operator installers export the block before pip runs, every Ollama call the setup script makes is pinned to loopback, the secret-store exec wrappers are modelled as the dynamic launchers they are, site paths cannot escape the repository, and the inventory's boundary with agent tooling is stated and enforced over fixed and dynamic rows. 430a7fc merges main (docs-only commits) so the push hook's fresh-main ancestry rule is satisfied without rewriting history; 9e01b0f sits on top of it.

What T13 missed and why. T13 discovers Python dependencies from the four manifests on its own, but an external binary is only swept if it is named in KNOWN_EXTERNAL_COMPONENTS. Two binaries entered the repo after the 2026-09-28 sweep without being named there, so the checker reported "all declared components classified" while being blind to both:

  1. /usr/bin/security (macOS Keychain CLI). sync/scheduled.py started spawning it in [security] - Retrieve scheduled rclone credentials at execution time #1632 (2026-10-12) to read the scheduled rclone credential at execution time. Local Keychain read, fixed absolute path, runs under child_environment("filesystem"), no network sink. Classified category 4 under the existing "local OS process tooling" row; added to KNOWN_EXTERNAL_COMPONENTS and INVENTORY_ALIASES so a future removal is caught too. The macos/cyclaw-keychain-*.sh scripts already used the same binary; the row now names them.
  2. uv. Retired from the checker 2026-09-11 when the Dockerfile's uv stage was dropped (the comment literally said "the repo spawns uv nowhere; re-add the row if uv ever returns"). It returned in [infra] - Build the cloud test venv in the background at session start, with uv #1572 (2026-10-07) via scripts/refresh-runtime-lock.sh, scripts/refresh-ci-locks.sh, and .claude/skills/cyclaw-gotchas/driver.sh. New row: crates/uv-static/src/env_vars.rs at current main defines no telemetry, analytics, tracking, or update-check variable; self-update is explicit-only (uv self update); the post-uv sync OSV malware check is opt-in (UV_MALWARE_CHECK=1) and never reached by uv pip. No control was invented for it, per the skill's rule for categories 3–5. (Filed as category 5 at first; moved to category 3 in the eleventh commit because its only invocations are index access, with SECURITY.md updated to match.)

Second commit: spawn-site verification for fixed binaries (Codex round 1, 1f7aa63). Until now a name in KNOWN_EXTERNAL_COMPONENTS was unioned into the sweep unconditionally; only DYNAMIC_LAUNCHER_SITES had its source markers checked. So a retired binary stayed "classified" forever, which is exactly how the uv row went stale in September. Now:

  • FIXED_LAUNCHER_SITES holds one (component, file, markers) row per spawn site for every fixed binary (shell and PowerShell files are first-class sites, since a marker is a text match). T13 warns when a marker disappears, as for dynamic sites.
  • A known binary with no FIXED or DYNAMIC row and no FRONTED_SERVICES entry is a hard FAIL.
  • verify.sh gains mutations for both rules, and _mktree now copies the site files straight from the checker's table via ast.literal_eval, so the scratch tree and the rule cannot drift.
  • SKILL.md step 5 says the AST walk is Python-only and names the shell, PowerShell, Dockerfile/compose and driver surfaces to sweep as well.

Third commit: Codex round 2 (d1ae1a7), all three findings confirmed.

  • The uv site rows matched only the uv_bin= assignment; they now also pin the "$uv_bin" pip compile invocation, with a mutation that deletes just the call.
  • Naming the venv driver as a swept surface obliges the sweep to classify every binary it runs, and curl was missing. New category-3 row with its sites (three operator setup/launch scripts, the sandbox driver, the three CyClaw-Sandbox smoke/verify scripts, and the launchd/Task Scheduler health jobs telegram/cli.py generates): every call is a loopback readiness/smoke probe or the sandbox's agent-proxy status endpoint, except the ollama.com/install.sh pipe in setup-from-clone.sh, which sits behind an interactive confirm that defaults to no.
  • The reverse-consistency check skipped DYNAMIC_LAUNCHER_SITES; dynamic names are now included, with a mutation that adds an unlisted dynamic row and expects the FAIL.

Fourth commit: Codex round 3 (2e0ccb0), all three findings confirmed. This one touches a file outside the skill directory.

  • A default parameter is not an invocation: rclone_bin: str = "rclone" survives the removal of every rclone spawn. Every row whose marker was only a name declaration now also pins an argv literal or shell command line; the table comment and SKILL.md state the rule.
  • macos/setup-from-clone.sh runs gh auth status / gh auth login directly, with whatever GH_TELEMETRY the operator's shell carries, while the gh row claimed every spawn site forced the canonical env. The script now evals the same utils.telemetry_kill --export shell block invoke-cyclaw.sh does, after the key/.env loading and before its gh and ollama steps, same -S -E isolation. Values are rendered by the canonical module, never hand-copied. The eval line is a site marker and verify.sh scenario 34 removes it and expects the WARN. agentic/writer.py's gh argv is sited too.
  • ollama was in FRONTED_SERVICES as never spawned; the same setup script starts ollama serve. Moved to FIXED_LAUNCHER_SITES. FRONTED_SERVICES is empty for now and kept for the next real case.

Fifth commit: Codex round 4 (e0229f9), all three findings confirmed.

  • ollama pull contacts the model registry and had landed on the category-4 (local-only) daemon row. It is now its own component ollama-pull with a category-3 row: interactive setup step 7 only, behind the operator's confirm, tag validated before argv. The daemon row is back to ollama serve alone.
  • The Keychain helper's security site pinned only the resolver; it now pins the find-generic-password call, and the other Keychain scripts are sited on their calls too, with a mutation that removes the helper's call alone.
  • The curl row cited docs/setup-guide.md, which does not exist (the guide is setup-guide.md at the root). Fixed, and T13 now fails any row whose repo-relative url is absent; the scratch tree copies those evidence paths from the inventory so the rule runs in the self-test too.

Sixth and seventh commits: Codex round 5 (5081ec5, d791d62), both findings confirmed.

  • The setup script's export block continued into the gh step on failure, so an ambient GH_TELEMETRY could still reach gh, which cannot self-apply the Python kill. It now fails closed: the script records whether the export succeeded and skips step 6 when it did not; the rest of setup continues (Python entry points re-apply at import, ollama has no vendor switch to lose). The skip guard is a site marker beside the eval line. A hand-copied GH_TELEMETRY=false fallback was rejected on purpose: a second copy of a kill value outside the oracle is the drift this checker exists to catch. d791d62 makes the skip message name the real reason instead of falling through to "gh: not on PATH".
  • CyClaw-Sandbox/smoke.sh and macos-smoke.sh run curl and were not sited although their sibling verify.sh was. Both are rows now.

Eighth commit: the agent-tooling boundary (owner decision, 23c7c36). A full sweep of .claude/**/*.sh had shown gh run by the babysit-github-pr helper scripts and git by the session hooks and skill bootstraps, none exporting the kill block. The owner's decision: agent tooling under .claude/ is out of the inventory; those scripts run on the agent's own machine or sandbox, outside the gateway's process tree, and their gh/git calls are not findings. The two exceptions stay because they exercise the gateway itself: cyclaw-gotchas/driver.sh and the CyClaw-Sandbox/ suite, now named in SWEPT_AGENT_SURFACES. T13 fails any site row under .claude/ outside those, so the boundary is only ever widened by editing that tuple on purpose; verify.sh scenario 37 adds a row for a session hook and expects the FAIL. SKILL.md step 5 states the decision.

Ninth commit: Codex round 6 (3d1debd), all five findings confirmed.

  • A logic bug in the sweep: an alias whose target named no inventory row still counted as "classified", because the condition short-circuited on the alias key existing. The sweep now resolves the target, and a dangling target is a FAIL (verify.sh scenario 38).
  • launchctl pinned only the which() lookup; sync/scheduler.py's bootout and print argv are sited.
  • telegram/cli.py generates launchd / Task Scheduler health jobs that run curl and curl.exe; both literals are curl sites.
  • open <url> in the macOS operator scripts is the browser, not the file-manager reveal: new component open-url, category 3 (one remote page, the signed Ollama download, behind a setup prompt; the rest open the loopback console).
  • The ollama daemon row claimed the setup-started daemon binds loopback unconditionally, but the script preserves the operator's environment and never set OLLAMA_HOST. Pinned to loopback (widened to the whole step in the tenth commit).

Tenth commit: Codex round 7 (2f64b05), all four findings confirmed.

  • The site table was incomplete and the "all sites carry their markers" line was therefore a false completeness claim. It had been built reactively, one binary per review round. Replaced with a sweep of every in-boundary Python and shell file per known binary, which added fifteen rows across ten binaries. verify.sh scenario 40 removes one of the new invocations and expects the WARN.
  • windows-smoke.ps1, inside the swept sandbox surface, makes Invoke-RestMethod / Invoke-WebRequest calls; the curl row had said PowerShell makes none. New component pwsh-http with a category-3 row, sited; the curl row is corrected.
  • The boundary check covered fixed rows only and matched the single-file exception by prefix. It now covers dynamic rows too and matches a non-directory entry exactly; verify.sh scenario 39 adds both offending shapes and requires both named in the FAIL.
  • OLLAMA_HOST had been pinned on the serve line alone; ollama list and the confirmed pull still inherited the ambient value. Step 7 now exports the loopback endpoint once for every Ollama call it makes; the markers pin the export and each client call.

Eleventh commit: Codex round 8 (4e1c8ad), all four findings confirmed. This one also touches SECURITY.md.

  • uv is category 3, not 5. Its only invocations (uv pip compile, uv pip install) resolve and download from an index on a cold cache. The row now describes what the invocations do on the network and keeps the telemetry negative finding in its evidence. SECURITY.md's egress classification is the other copy of this list: uv moves out of its category-5 sentence and the install-time category-3 surface is named there so the checker and the document agree.
  • The installers' git clone / git pull contradicted the git row's "local-only subcommands" claim. New component git-remote with a category-3 row and three installer sites (two macOS, one PowerShell); the local git row now says where network git lives.
  • The round-7 sweep's patterns were shell- and Python-shaped and missed PowerShell forms. A PowerShell-aware pass sited Uninstall-CyClaw.ps1 (cmd.exe and schtasks.exe), Install-CyClaw.ps1 (PowerShell children) and Invoke-CyClaw.ps1 (Start-Process on the console URL, joining open-url).
  • export HOMEBREW_NO_ANALYTICS=1 is the pre-execution protection the Homebrew row claims and was not a marker; it is now, with verify.sh scenario 41 removing it and expecting the WARN.

Twelfth commit: Codex round 9 (37aa395), all three findings confirmed.

  • The evidence-path rule joined root / url, which an absolute url discards and a .. component escapes. The schema now refuses absolute paths and .. components outright and requires the resolved target (symlinks followed) to stay under the root; verify.sh scenarios 42 and 43 cover both shapes.
  • pip's hash-pinned installs in macos/install-cyclaw.sh, powershell/Install-CyClaw.ps1, the Dockerfile and the venv driver's fallback, and the confirmed brew install python@3.12, are package-index downloads: new category-3 components pip-install and brew-install with their sites, consistent with the uv reclassification. The Homebrew analytics row keeps only its export and analytics off markers. SECURITY.md's paragraph names both.
  • RepoWorkspaceTools.push_branch runs git push --set-upstream origin when allow_git_write_tools and agentic.enabled are both switched on (both ship false). The git-remote row is broadened to "git remote operations (installers + gated push)" with that argv as a marker, and the local git row says so.

Thirteenth commit: Codex round 10 (9e01b0f), all four findings confirmed. This one touches both operator installers.

  • macos/cyclaw-keychain-env.sh (exec "$@") and powershell/CyClaw-CredMan-Env.ps1 (& $exe @exeArgs) run whatever command follows their --, so they are dynamic launchers like the pre-action hook and the executor checks. New component secret-env-wrapper on the caller-configured row, sited on the secret export and the exec line of each; the row records who supplies the argv (the launchd and Task Scheduler generators, or the operator). verify.sh scenario 44 removes the macOS exec and expects the WARN.
  • The swept sandbox surfaces ran pip (CyClaw-Sandbox/verify.sh), git clone (run_full_verification.py) and local git (driver.sh's inventory) without site rows. All three are sited and the pip, git and git-remote rows name them; scenarios 46 and 47 remove the pip install and the clone.
  • Site row paths and repo-relative evidence urls now share one rule: no absolute path, no ./.. component, no redundant separator, resolved target inside the root. A bad site path is a FAIL before the boundary prefix test or any marker read, so CyClaw-Sandbox/../../hooks/x cannot smuggle a hook in; scenario 45 adds that row and an absolute one.
  • Both operator installers now eval the canonical telemetry block immediately before their pip calls (install-cyclaw.sh in shell form, Install-CyClaw.ps1 with the same two rigid line shapes Invoke-CyClaw.ps1 parses), so setup-from-clone's step 4 no longer runs pip under the ambient PIP_DISABLE_PIP_VERSION_CHECK. Warn-and-continue on failure: pip carries no telemetry and its version check is a category-2 update check, not worth aborting an install. The pip row says per site where the block reaches pip and where it does not (the Dockerfile build stage, the sandbox verify.sh, the venv driver), and the installers' export lines are site markers.

Declined, owner-confirmed (round 1): treat an operator's UV_BIN path override as a dynamic launcher. It is the same operator-resolved-binary shape as every shutil.which() site, none of which are modelled that way; the uv row's text says so and the site rows pin both scripts' uv_bin= lines. The owner agreed and the thread is resolved.

Vendor sweep results (all unchanged, dates and evidence refreshed on the rows):

Vendor (pin) Verified against Result
onnxruntime 1.30.0 docs/Privacy.md at main + installed wheel ORT_DISABLE_TELEMETRY=1 pre-init still the documented process-lifetime control for all 1DS builds; disable_telemetry_events still callable
nemoguardrails 0.24.0 telemetry.py at v0.24.0 tag + installed both names honored at lines 372–374; sink unchanged
huggingface_hub 1.32.0 constants.py at v1.32.0 tag three-name OR unchanged
chromadb 1.5.9 telemetry/opentelemetry/__init__.py + config.py at 1.5.9 tag otel_init early-returns on granularity none; Settings is pydantic-settings with no env_prefix and env_file=".env", and os.environ outranks the dotenv, so the pre-import overlay still wins
langsmith 0.13.0 installed client.py, run_helpers.py, run_trees.py, langchain_core/tracers/context.py precedence unchanged. New surface: LANGSMITH_TRACING_MODE / legacy OTEL_ENABLED / OTEL_ONLY. Traced it: these only pick a destination for runs that tracing_is_enabled() already admits, so no new kill pair is owed and none was added (speculative variables are the advertise-false-protection anti-pattern). LANGSMITH_RUNS_ENDPOINTS is now parsed as write replicas; still a fan-out destination, still scrubbed
gh pkg/cmd/root/help_topic.go at trunk GH_TELEMETRY=false still disables and takes precedence over DO_NOT_TRACK, which the map also sets
pwsh PowerShell-Docs 7.5 about_Telemetry still read before the process starts. New: 7.5.7+ on Windows also honors the OS Diagnostics & feedback setting, an additional opt-out, not a replacement

Also corrected the sentence-transformers row's versions text: transformers is still unbounded on the wheel surface, but the new hashed locks in locks/ pin it at 5.18.0 on the documented install path, which is why T13's standing INFO finding stays accurate per-surface.

Invariant / Governance Impact: none of the six invariants or I6 are touched. The diff is the checker's inventory data, its self-test, the skill text, one paragraph of SECURITY.md's egress classification (kept in step with the checker's categories), in macos/setup-from-clone.sh one additive export block, a fail-closed skip guard, and a step-wide loopback export for the Ollama step, and in macos/install-cyclaw.sh and powershell/Install-CyClaw.ps1 one additive export block each before their pip calls (operator setup scripts; not core-six files, not launchers the server depends on). No production map (utils/telemetry_kill.py), no kill value, no scrub name changed; CONTRACT_DIGEST is unaffected. check_invariants.py was not needed because no core file moved.

Agent-instruction surface (the pre-commit security gate flagged this, correctly): the skill files steer later agent sessions. Every evidence string is my own paraphrase of what I read at the cited source; the two short phrases I had initially quoted verbatim from vendor docs were rewritten before push so no web text is copied into an instruction file. The gate's secure-by-design question about a "new config input" at the uv row does not apply: the row has empty controls, so nothing new is read at runtime. On the second commit the gate asked about a new code-execution or deserialization path: the only new evaluation is ast.literal_eval over the checker's own tables inside verify.sh's scratch-tree builder, reading a file from this repo, never model output, retrieved text or a request field; it runs only in the self-test. The setup script's eval "$_kill_env" evaluates only the output of the repo's own stdlib module, run with -S -E, exactly as invoke-cyclaw.sh already does.


Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Security (auth, privacy, injection, egress, redaction, or secrets)
  • Invariant / Governance refinement (use this for changes that strengthen or evolve the 6 invariants, I6 isolation, or harness phases)
  • Dependencies (added, removed, or bumped)
  • CI / infrastructure (workflows, hooks, templates, or checkers)
  • Refactor or tests (behavior-preserving, no new capability)
  • Documentation update
  • Other (none of the boxes above fit)

Scope note: Infrastructure / CI (the verify-skills checker's egress inventory and its self-test) plus an additive export block, a fail-closed guard and a step-wide loopback export in the macOS setup script, an additive export block before pip in each operator installer, and one paragraph of SECURITY.md; no server runtime path.


Benefits / why

  • The egress classification is only worth anything if it is complete and honest about kind. Two spawned binaries sat outside it for 6 and 1 days respectively; both turned out benign, but the checker could not have told you that, and the next one might not be. Every install-time network operation (git clone/pull, pip and uv index access, the Homebrew install, the Ollama pull, the browser) is now filed as the functional egress it is rather than under "no mechanism" or "local-only".
  • After the later commits a retired, renamed or no-longer-invoked binary can no longer stay "classified": its invocation marker vanishes and T13 says so, a name with no site at all fails outright, a site row with no inventory entry fails too, a row citing a file that does not exist or lies outside the repository fails, and an alias pointing at a row that does not exist fails. The site table is now the product of a complete per-binary sweep over Python, shell and PowerShell rather than a per-finding accretion.
  • The setup script's gh calls were the one place a telemetry-capable binary ran under the operator's ambient environment. They now get the canonical block the same way the launcher does, are skipped entirely when it cannot be produced, and the checker pins both facts, as it now pins the Homebrew export. Every Ollama call the same script makes is addressed to loopback, and both operator installers export the block before pip runs so its PyPI version check is off there too.
  • The inventory's scope is now written down and enforced over every site row: what counts as CyClaw's egress surface and what is agent tooling. The next reviewer cannot reopen that by adding a row of either kind.
  • Each category-1 row now carries a dated re-read at the exact pinned version, so the 120-day staleness window (T4) restarts from evidence rather than from a date bump.
  • The langsmith finding is recorded where the next reviewer will look: a new env surface that looks like it needs a kill pair and provably does not. That saves someone from "completing" the map with dead variables.
  • The skill text now says how to sweep spawn sites so the security miss does not recur: AST-walk Python, then grep the shell, PowerShell, container and driver surfaces the AST cannot see with patterns shaped for each language, pin invocations rather than declarations, sweep per binary rather than per finding, and split a binary into two rows when its calls differ in kind.

Risks to monitor

  • No server runtime risk: nothing under utils/, gate*.py, graph.py, or the launchers the server depends on changed. A regression in the checker would be a reporting regression, not a telemetry leak.
  • Installer behaviour change. Both operator installers now print one more step line (or a warning) before the pip installs. If the kill module cannot be rendered, pip runs ambient and the installer says so; nothing else changes. Not executed here beyond the fake-Python installer contract tests, which exercise the warn-and-continue path.
  • Setup script behaviour changes. (1) If the venv Python or the kill module is unavailable at step 6, gh auth is now skipped with a warning naming the reason, where before it ran ambient; setup still completes and the operator runs gh auth login by hand later. (2) Step 7 exports OLLAMA_HOST=127.0.0.1:11434 for every Ollama call it makes and for the convenience daemon it may start; an operator who wants a LAN-bound or remote daemon runs it themselves, as before, and the export persists for the rest of the script run (the servers read config.yaml's base_url, not this variable). Both are macOS-only and not executed here (Linux sandbox); CI's shellcheck (macos scripts) leg passed on the head that introduced the block.
  • Marker brittleness. A FIXED_LAUNCHER_SITES marker is a literal substring; an innocent refactor (renaming rclone_bin, reflowing an argv list, a curl line, a pip install line or a PowerShell launch line) trips a T13 WARN that must be re-pointed. With 70 fixed sites and 4 dynamic ones that surface is wider than before. That is the intended cost: the WARN names the row and the fix. Signal: a verify-skills (otel-hardening) leg going yellow on an unrelated PR. The evidence-path rule has the same shape: moving a cited doc fails the schema until the row is re-pointed.
  • Classification wrong on uv. If a future uv release adds telemetry, the row's telemetry negative finding goes stale silently, since uv is an unpinned host binary. Signal: the row's reviewed date crossing T4's 120-day window, and the /otel-hardening step-3 vendor table, which should gain a uv line at the next run.
  • The install-time category-3 rows all sit behind an operator action or prompt (curl's installer pipe, the Ollama pull, the installer git clone/pull, the Homebrew install, the remote URL open) or behind two shipped-false switches (the workspace push). If any gate is ever loosened, the rows' evidence is wrong. The site markers pin the exact lines, so a rewrite trips a WARN, but a change to the surrounding _confirm or config gate would not.
  • security row scope creep. The row documents reads, the setup-time writes and the uninstall-time delete the Keychain scripts make; the site markers pin each call, so a new kind of call needs a new marker.
  • The agent-tooling boundary is a decision, not a measurement. The babysit-github-pr scripts do run gh without the kill block, on the agent's machine. If that ever matters (an operator running them on the production host), the fix is to widen SWEPT_AGENT_SURFACES and add the export eval to those scripts, deliberately.
  • verify-skills is continue-on-error in ci.yml, so a red check_otel.py leg would not block a merge. That is pre-existing and documented in the skill's gotchas; I am noting it because it means the green I report below is the only gate this PR actually has.

Checklist

  • I have read docs/THREAT_MODEL.md, INVARIANTS.md and SECURITY.md (and any relevant Phase docs)
  • This change preserves all 6 security invariants and I6 module isolation (explicit evidence or invariant matrix included for core changes)
  • Full sandbox validation has been run (GROK_API_KEY=dummy pytest tests/ -q --tb=short, and bash .claude/skills/CyClaw-Sandbox/verify.sh for core RAG/agentic paths) and passes with no regressions — deliberately not run, per CLAUDE.md §5's verification policy; CI runs the suites. The checks that do exercise this diff were run directly and are listed under Further comments.
  • No new external network dependencies or mandatory online LLM assumptions were introduced without explicit justification + offline fallback path
  • For any agentic/fsconnect change: two-phase audit, quota enforcement, governed delete/trash, and write guards have been verified — n/a
  • Relevant architecture docs or threat model notes have been updated if core behavior or topology changed — n/a for core; the skill's own SKILL.md and SECURITY.md's egress classification updated
  • Commit messages follow the title prefix convention above
  • For large or complex changes: before/after invariant matrix + sandbox evidence is included in "Further comments" or linked — not large

Further comments

Checks run directly on the pushed head 9e01b0f (all green):

  • python3 .claude/skills/otel-hardening/check_otel.py --strict --as-of 2026-10-13 → 0 failures, 0 warnings; 42 inventory rows, 84 declared components resolve, 70 fixed launcher sites (28 binaries) and 4 dynamic sites carry their invocation markers, every site path and repo-relative evidence path is a clean path inside the repository, every alias target names a row, every .claude/ site of either kind is inside the boundary.
  • bash .claude/skills/otel-hardening/verify.sh → ALL PASS (51 mutation scenarios, eighteen new across the review rounds and the boundary decision).
  • python3 .claude/skills/doc-sync/doc_sync.py → 0 drift items, re-run on the merged tree.
  • bash -n on verify.sh, macos/setup-from-clone.sh and macos/install-cyclaw.sh; ruff check on the checker clean; bash scripts/check-pr-template.sh on this body → OK.
  • tests/test_installer_python_contract.py, tests/test_telemetry_env_delivery.py and tests/test_powershell_windows_parity.py (the files that pin the installer scripts' text and run install-cyclaw.sh under a fake Python) → green; the fake Python rejects -S -E -m utils.telemetry_kill, so they exercise the new warn-and-continue path.
  • Spawn-site sweep: AST walk over every non-test subprocess.run/Popen/check_*/call and create_subprocess_exec site, each non-literal first argv element resolved by reading the call site; then a per-binary grep of every in-boundary Python and shell file for invocations; then a PowerShell-aware pass over powershell/ and windows/ (& cmd, cmd.exe /c, Start-Process, .exe forms); and a grep of every launch surface for curl/wget/Invoke-WebRequest/Invoke-RestMethod, for git's network subcommands and for pip install.

Only CI has run: the pytest suites and the 80% coverage gate (this diff adds no Python under coverage sources), shellcheck on the macOS scripts (not installed in the sandbox), and any PowerShell parse of Install-CyClaw.ps1 (no pwsh here; the new block is the launcher's proven block with the identical regex lines, and the brace count balances).

Not run, and why: the four telemetry pytest files the skill's step 4 names. They pin utils/telemetry_kill.py's maps, the Dockerfile/compose delivery, and the ONNX seam, none of which this PR touches; running them would have exercised nothing in the diff. The setup script itself was not executed: it is macOS-only and interactive.

Network note for the sweep itself: the sandbox proxy denies docs.trychroma.com, cli.github.com, learn.microsoft.com, and docs.astral.sh, so the vendor re-reads above were done against the pinned source tags on raw.githubusercontent.com and the installed packages in the cloud venv (which carry exactly the pinned versions). That is a stronger oracle than the marketing docs anyway, but it means the row URLs that point at doc sites were not themselves fetched this round.

Review threads: all thirty-four Codex threads are resolved: thirty-three fixed, one (UV_BIN) declined with reasoning and confirmed by the owner.

Body layout note: pr-template-check.yml requires the Last updated stamp to be the body's last non-blank line, so the Claude Code attribution block sits just above it rather than after it.


Suggested merge order of open PRs

Trial merge verified (git merge --no-commit --no-ff + abort) against main @ e61481d on 2026-10-13, then main merged into the branch (430a7fc) to satisfy the push hook's fresh-main rule; 9e01b0f sits on that merge and main has not moved since.

#1637 (this PR) · Safe ✅
Impact: Checker inventory, its self-test, skill text, one SECURITY.md paragraph, an additive block, a fail-closed guard and a loopback export in the macOS setup script, and an additive export block in each operator installer; no server runtime path. Independent, only open PR.
Action: Merge when CI on 9e01b0f is green; auto-merge (rebase) is already enabled by the owner.

ELI5

Think of the telemetry checker as a bouncer with a guest list. Every program CyClaw can start has to be on the list with a note saying whether it phones home. Two programs got in without being on the list: security, the Mac keychain tool that sync/scheduled.py started using last week to fetch the rclone password, and uv, the fast pip replacement that came back in the lock-refresh scripts after the checker had been told it was gone. Neither one phones home, but the bouncer couldn't tell you that, because he'd never looked at them.

The first commit adds both to the list with the evidence (where they are started, what they do, why they don't need a telemetry switch), and re-dates seven existing entries after re-reading each vendor's actual source at the version we pin. One vendor, langsmith, grew a new set of knobs since the last read; I traced them and they only matter if tracing is already on, which ours never is, so I deliberately did not add fake "protection" variables for them.

The second commit fixes the deeper problem the Codex review pointed at: the guest list was never compared with who actually shows up. A program could leave the codebase and stay on the list forever, which is exactly what happened with uv in September. Now every listed program has to name the file that launches it and a line of text that only exists while that file still does; if the line disappears, the checker complains, and if a listed program has no file at all, the checker fails.

The later commits answer Codex's next nine looks, which kept finding the same shape of gap one layer down. Two of them fixed the pattern rather than another instance: every known program was swept across every file in scope at once, first the Python and shell files and then, when that still missed things, the PowerShell files with patterns written for PowerShell. Along the way the lines pinned were often the line that names a program, not the line that runs it, so now every entry pins the actual command. The sweeps turned up curl and the PowerShell web calls (local "is the server up" probes in the setup scripts, the sandbox smoke suites on three platforms, and the scheduled health jobs the Telegram channel generates, plus one Ollama installer fetch behind a prompt that defaults to no), the Mac setup script running gh with whatever telemetry setting your shell had (it now loads the same canonical block the launcher does, and if it cannot, it skips gh rather than run it unprotected), the same script starting Ollama and pulling a model, the installers cloning the repository from GitHub and downloading packages with pip and Homebrew, the scripts opening web pages in your browser, and the one place the running code can push to GitHub, which needs two switches turned on that ship off. The last look found the two little wrapper scripts that fetch a password from the Mac Keychain or Windows Credential Manager and then run whatever command you hand them, which makes them launchers whose program nobody can name in advance, so they are now listed as exactly that; it also found that the two installer scripts ran pip before anyone had told pip not to check PyPI for a newer version of itself, so both now load the telemetry block first. Anything that actually reaches the internet on purpose now carries its own entry marked as intentional internet traffic instead of hiding under a "local" or "no mechanism" one, and the security summary document says the same thing in the same words. Every Ollama command the setup script runs is now told to talk to localhost only. The checker also now complains if a launch site is written down without the program being on the guest list, if an entry or a launch site points at a path that does not exist, lives outside the repository or sneaks out of the sandbox folder with a .., and if a program's list entry points at a note that does not exist, which was a real bug in the checker's own logic. The self-test proves every one of these complaints can fire.

One line was drawn by the owner: the helper scripts the AI agents use (session hooks, skill self-tests, the PR-babysitting wrappers) are not CyClaw and are not on the guest list, even though some of them run gh and git. The two sandbox scripts that poke the actual server stay on it. The checker now refuses any new entry for an agent helper, of either kind, with the single allowed file matched exactly, so that line only moves if someone edits it on purpose.

What could still go wrong: the markers are plain text, so an innocent rename in one of those files will make the checker grumble until someone re-points the row; with 74 of them that will happen more often than before, and that is the price of it noticing at all. The Mac setup script now skips gh login when it cannot load the telemetry block and points every Ollama command at localhost; both are behaviour changes you will see as a warning or a fixed address, not a failure. They were not run here (Linux sandbox), but the shell lint passed in CI on the first head that carried the block. uv is not version-pinned, so if Astral ever adds telemetry, the "no telemetry" note goes stale until the 120-day review alarm fires. Nothing in the actual running server changed, so there is no new way for data to leave the box because of this diff. I did not run the full test suite; CI does that. I did run the checker, its self-test, and the doc-drift checker directly on the pushed head, all green.

🤖 Generated with Claude Code

https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

Last updated: 2026-10-13 04:50 ET

… inventory

otel-hardening sweep of 2026-10-13 (static checker + mutation self-test
green before and after; vendor controls re-verified against the pinned
source tags and the installed packages, all unchanged).

Two components had slipped past T13's classification sweep, which only
knows the binaries named in KNOWN_EXTERNAL_COMPONENTS:

- /usr/bin/security (macOS Keychain CLI): sync/scheduled.py started
  spawning it in PR #1632, after the 2026-09-28 sweep. Category 4 under
  the local OS process tooling row; aliased and listed so a future
  removal is caught too.
- uv: retired 2026-09-11 with the Dockerfile stage, back since PR #1572
  via scripts/refresh-*-lock.sh and the cloud venv driver. New
  category-5 row: crates/uv-static/src/env_vars.rs defines no telemetry,
  analytics or update-check variable; self-update is explicit-only and
  the OSV malware check is opt-in and never reached by `uv pip`.

Also refreshes the reviewed dates and evidence on the seven category-1
rows re-read today (chromadb 1.5.9, langsmith 0.13.0 incl. its
tracing-mode surface sitting behind tracing_is_enabled(), nemoguardrails
0.24.0, onnxruntime 1.30.0, gh, pwsh) and notes that locks/ hash-pin
transformers on the documented install path. SKILL.md records the
precedent: sweep spawn sites with an AST walk, not a literal-argv grep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

PR is a complete fill of .github/PULL_REQUEST_TEMPLATE.md (every section, ticked boxes, merge order, ELI5 last, last-updated stamp, title, branch).

@cgfixit
cgfixit marked this pull request as ready for review October 13, 2026 05:37
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T08:14:03.166066Z 9e01b0f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cgfixit
cgfixit enabled auto-merge (rebase) October 13, 2026 05:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bd3153cb5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/SKILL.md Outdated
…tory

Codex review of PR #1637 (P2): a binary named only in
KNOWN_EXTERNAL_COMPONENTS stayed "classified" forever, because T13
unioned the list unconditionally and verified source markers only for
DYNAMIC_LAUNCHER_SITES. That is the exact mechanism behind the stale uv
row of 2026-09-11 and the day-late security(1) classification.

- FIXED_LAUNCHER_SITES: one (component, file, markers) row per spawn
  site for all 20 fixed binaries, shell and PowerShell files included
  (brew, two of the uv sites, the Keychain scripts). T13 warns when a
  marker disappears, like a dynamic site, and FAILS when a known binary
  has no FIXED/DYNAMIC row and is not a documented FRONTED_SERVICES entry
  (ollama: a loopback daemon CyClaw probes but never spawns).
- verify.sh: two mutations (a site losing its marker -> WARN; a binary
  losing every site row -> FAIL); the scratch tree now copies every
  site file straight from the checker's table so they cannot drift.
- SKILL.md step 5: the sweep covers the non-Python launch surfaces the
  AST walk cannot see (macos/*.sh, scripts/*.sh, powershell/*.ps1, the
  Dockerfile/compose command lines, the skill drivers) and documents the
  site-row requirement.
- uv row: UV_BIN (like PYTHON_BIN) is the operator's path to their own
  uv install, the same operator-resolved-binary shape as shutil.which();
  not modelled as a second launcher.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f7aa63719

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
…e names

Second Codex round on PR #1637 (three P2s, all confirmed):

- The uv site rows matched only the `uv_bin="${UV_BIN:-uv}"` assignment,
  so deleting the `"$uv_bin" pip compile` call while keeping the override
  plumbing left T13 green. Both rows now also pin the invocation, and a
  new verify.sh mutation deletes just the call.
- Naming the cloud venv driver as a swept shell surface obliges the sweep
  to classify every binary it runs: curl was absent. New category-3 row
  with its five sites (three operator setup/launch scripts, two sandbox
  drivers): every call is a loopback readiness/smoke probe or the
  sandbox's agent-proxy status endpoint, except the ollama.com installer
  pipe in setup-from-clone.sh, which sits behind an interactive confirm
  that defaults to no. curl itself has no telemetry; no runtime path, the
  Dockerfile or the PowerShell scripts spawn it.
- The reverse-consistency check skipped DYNAMIC_LAUNCHER_SITES, so a
  dynamic row without a KNOWN_EXTERNAL_COMPONENTS entry verified its
  marker and never entered the sweep. Dynamic names are now included, and
  a verify.sh mutation adds an unlisted dynamic row and expects the FAIL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1ae1a7356

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
…export the kill block there

Third Codex round on PR #1637 (three P2s, all confirmed):

- A default parameter is not an invocation: `rclone_bin: str = "rclone"`
  survives the removal of every rclone spawn. Every fixed row now pins
  the argv literal or shell command line as well (rclone, gh, git,
  openssl, numbat), and the table's comment states the rule.
- macos/setup-from-clone.sh runs `gh auth status` / `gh auth login`
  directly, with whatever GH_TELEMETRY the operator's shell carries, and
  the gh row claimed every spawn site forced the canonical env. The
  script now evals the same `utils.telemetry_kill --export shell` block
  invoke-cyclaw.sh does, before its gh and ollama steps (non-fatal, same
  -S -E isolation); the eval line is a site marker so removing it is a
  WARN (verify.sh scenario 34). agentic/writer.py's gh argv is sited too.
- ollama was listed as never spawned; the same setup script starts
  `ollama serve` and runs `ollama pull`. Moved from FRONTED_SERVICES to
  FIXED_LAUNCHER_SITES with both lines pinned; the row text says so.
  FRONTED_SERVICES is empty for now and kept for the next real case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e0ccb0fe3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
…cations, verify evidence paths

Fourth Codex round on PR #1637 (three P2s, all confirmed):

- `ollama pull` contacts the model registry; it sat on the category-4
  daemon row, which means local-only. The pull is now its own component
  (`ollama-pull`) and category-3 row: interactive setup only, behind the
  operator's confirm, tag validated before it reaches argv. The daemon
  row is back to `ollama serve` alone.
- The Keychain helper's security(1) site pinned only the SECURITY_BIN
  resolver. It now pins the find-generic-password call, and the two
  other Keychain scripts (set + setup-cyclaw-keys) are sited on their
  add-/find-generic-password calls too. verify.sh scenario 36 removes the
  helper's call alone and expects the WARN.
- The curl row cited docs/setup-guide.md, which does not exist; the guide
  is setup-guide.md at the root. T13 now fails any row whose
  repo-relative url is absent (scenario 35), and the self-test's scratch
  tree copies those evidence paths from the inventory so the rule can
  run there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0229f935a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread macos/setup-from-clone.sh Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py
…uites' curl

Fifth Codex round on PR #1637 (two P2s, both confirmed):

- macos/setup-from-clone.sh continued into its gh auth step when the
  canonical export could not be produced, so an ambient GH_TELEMETRY
  could still reach gh(1), which cannot self-apply the Python kill block.
  The script now records whether the export succeeded and skips step 6
  when it did not: fail closed for the one telemetry-capable binary it
  runs, non-fatal for the rest of setup (the servers re-apply at import).
  The skip guard is a FIXED_LAUNCHER_SITES marker beside the eval line.
- CyClaw-Sandbox/smoke.sh and macos-smoke.sh run curl and were not
  sited, although their sibling verify.sh was. Both are rows now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
The skip guard added in 5081ec5 fell through to the "gh: not on PATH"
message when the real reason was a failed telemetry export. Report the
actual reason so the operator is not sent to install a gh they have.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
Owner decision on PR #1637: scripts under .claude/ are agent tooling and
out of the egress inventory, except the two sandbox surfaces that
exercise the gateway itself (cyclaw-gotchas/driver.sh and the
CyClaw-Sandbox suite), whose rows stay.

SWEPT_AGENT_SURFACES names those two; T13 fails any FIXED_LAUNCHER_SITES
row under .claude/ outside them, so the boundary is only ever widened by
editing that tuple on purpose. verify.sh scenario 37 adds a row for a
session hook and expects the FAIL. SKILL.md step 5 states the decision
and why the hooks', skill helpers' and babysit wrappers' gh/git calls
are not findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23c7c36e51

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
… URL opens, pin the setup Ollama bind

Seventh Codex round on PR #1637 (five P2s, all confirmed):

- An alias whose target named no inventory row still counted as
  "classified": the sweep short-circuited on the alias key. The sweep now
  resolves the target, and a dangling target is a FAIL (verify.sh 38).
- launchctl pinned only the which() lookup; sync/scheduler.py's bootout
  and print argv are sited.
- telegram/cli.py generates launchd / Task Scheduler health jobs that run
  curl and curl.exe; both literals are curl sites and the row says so.
- `open <url>` in the four macOS operator scripts is the browser, not
  the file-manager reveal: new component open-url, category 3 (one remote
  page, the signed Ollama download, behind a setup prompt; the rest open
  the loopback console).
- The ollama daemon row claimed the setup-started daemon binds loopback
  unconditionally, but the script preserves the operator's environment
  and never set OLLAMA_HOST. The spawn now pins OLLAMA_HOST=127.0.0.1:11434
  on its own command line (the threat model's Ollama endpoint), the
  marker pins that, and the row states the daemon-policy caveat for a
  daemon the operator runs themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d1debd055

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread macos/setup-from-clone.sh Outdated
…s, bound every site row, pin all Ollama client calls

Eighth Codex round on PR #1637 (four P2s, all confirmed):

- The site table was built reactively, one binary at a time, so the
  "all fixed launcher sites carry their markers" line was a completeness
  claim over an incomplete table. A sweep of every in-boundary Python,
  shell and PowerShell file per known binary adds the missing rows:
  rclone in sync/scheduled.py; git in the executor manifest, the eval
  dogfood script and both git-hook installers; security in
  cyclaw-keychain-load.sh and uninstall-cyclaw.sh; openssl in
  setup-cyclaw-keys.sh and cyclaw-linux-key.sh; launchctl in
  setup-cyclaw-keys.sh, uninstall-cyclaw.sh and launchd_plist.py's
  bootout; crontab, schtasks, bwrap and sandbox-exec argv; brew's
  python install; ollama's list and --version. verify.sh 40 removes one
  of the new invocations and expects the WARN.
- windows-smoke.ps1, inside the swept sandbox surface, makes
  Invoke-RestMethod / Invoke-WebRequest calls; the curl row had said
  PowerShell makes none (true of powershell/ and windows/ only). New
  pwsh-http component and category-3 row, sited; the curl row is
  corrected.
- The agent-tooling boundary checked fixed rows only and matched the
  single-file exception by prefix. It now covers dynamic rows too and
  matches a non-directory entry exactly (verify.sh 39 adds both an
  excluded dynamic row and "driver.sh.backup" and expects both named).
- OLLAMA_HOST was pinned on the serve line alone; `ollama list` and the
  confirmed pull still inherited the ambient value, so a remote
  OLLAMA_HOST would list and pull against another daemon. Step 7 now
  exports the loopback endpoint once for every Ollama call it makes; the
  markers pin the export and each client call.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f64b059a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
…erShell launchers, pin the Homebrew export

Ninth Codex round on PR #1637 (four P2s, all confirmed):

- uv's only invocations (`uv pip compile`, `uv pip install`) resolve and
  download from a package index on a cold cache; the row said as much
  and still called it category 5. It is category 3 now: the row
  describes what the invocations do on the network, and the telemetry
  negative finding stays in the evidence. SECURITY.md's egress
  classification moves uv accordingly and names the rest of the
  install-time category-3 surface so the two copies agree.
- The installers' `git clone` / `git pull` contradicted the git row's
  "local-only subcommands" claim. New git-remote component and
  category-3 row with the three installer sites (two macOS, one
  PowerShell); the local row now says where network git lives.
- powershell/Uninstall-CyClaw.ps1 runs cmd.exe and schtasks.exe,
  Install-CyClaw.ps1 launches PowerShell children and Invoke-CyClaw.ps1
  opens the console with Start-Process: all sited. The earlier sweep's
  patterns were shell- and Python-shaped and missed PowerShell forms.
- `export HOMEBREW_NO_ANALYTICS=1` is the pre-execution protection the
  Homebrew row claims and was not a marker; it is now, with verify.sh 41
  removing it and expecting the WARN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e1c8ad819

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
Comment thread SECURITY.md Outdated
Comment thread .claude/skills/otel-hardening/check_otel.py Outdated
…lls, site the gated git push

Tenth Codex round on PR #1637 (three P2s, all confirmed):

- The repo-relative url rule joined `root / url`, which an absolute url
  discards and a `..` component escapes. The schema now refuses absolute
  paths and `..` components outright and requires the resolved target to
  stay under the root (verify.sh 42 and 43).
- pip's hash-pinned installs in macos/install-cyclaw.sh,
  powershell/Install-CyClaw.ps1, the Dockerfile and the venv driver's
  fallback, and the confirmed `brew install python@3.12`, are
  package-index downloads: category-3 pip-install and brew-install
  components with their sites, consistent with the uv reclassification.
  The Homebrew analytics row keeps its export and analytics-off markers;
  the install moves to the new row. SECURITY.md's egress paragraph names
  them.
- RepoWorkspaceTools.push_branch runs `git push --set-upstream origin`
  when allow_git_write_tools and agentic.enabled are both switched on;
  the git-remote row now covers installers' clone/pull and that gated
  runtime push, with the argv literal as its marker, and the local git
  row says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 430a7fc424

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread macos/setup-from-clone.sh
Comment thread .claude/skills/otel-hardening/check_otel.py
…t the kill block before pip

Codex round ten on #1637, all four findings confirmed:

- macos/cyclaw-keychain-env.sh and powershell/CyClaw-CredMan-Env.ps1 exec
  whatever command follows their `--`, so they are dynamic launchers like
  the pre-action hook and the executor checks. New component
  `secret-env-wrapper` on the caller-configured row, sited on the export
  and exec lines of both wrappers; verify.sh scenario 44 removes the
  macOS exec and expects the dynamic-site WARN.
- The swept sandbox surfaces ran pip (CyClaw-Sandbox/verify.sh), git clone
  (run_full_verification.py) and local git (driver.sh inventory) without
  site rows. All three are sited, the driver's torch retry is pinned beside
  its pip fallback, and the pip, git and git-remote rows name them;
  scenarios 46 and 47 remove the pip install and the clone.
- Site row paths and repo-relative evidence urls share one rule
  (_repo_relative_problem): no absolute path, no '.'/'..' component, no
  redundant separator, resolved target inside the root. A bad site path is
  a FAIL before the boundary prefix test or any marker read; scenario 45
  adds a `CyClaw-Sandbox/../../hooks/...` row and an absolute one.
- Both operator installers eval the canonical telemetry block immediately
  before their pip calls (shell and PowerShell forms, modelled on the
  launchers), so setup-from-clone's step 4 no longer runs pip under the
  ambient PIP_DISABLE_PIP_VERSION_CHECK. Warn-and-continue on failure: a
  category-2 version check is not worth aborting an install. The pip row
  says per site where the block reaches pip and where it does not.

Checks: strict checker 0/0 (42 rows, 84 components, 70 sites / 28 binaries,
4 dynamic sites), verify.sh ALL PASS (51), doc-sync 0 drift, ruff clean,
bash -n, and the three test files that pin the installer scripts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e01b0f1b7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py
Comment thread .claude/skills/otel-hardening/check_otel.py
@cgfixit
cgfixit merged commit ee7b6a3 into main Oct 13, 2026
88 of 90 checks passed
@cgfixit
cgfixit deleted the claude/otel-hardening-sweep-2026-10-13 branch October 13, 2026 08:21
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…tory

Codex review of PR #1637 (P2): a binary named only in
KNOWN_EXTERNAL_COMPONENTS stayed "classified" forever, because T13
unioned the list unconditionally and verified source markers only for
DYNAMIC_LAUNCHER_SITES. That is the exact mechanism behind the stale uv
row of 2026-09-11 and the day-late security(1) classification.

- FIXED_LAUNCHER_SITES: one (component, file, markers) row per spawn
  site for all 20 fixed binaries, shell and PowerShell files included
  (brew, two of the uv sites, the Keychain scripts). T13 warns when a
  marker disappears, like a dynamic site, and FAILS when a known binary
  has no FIXED/DYNAMIC row and is not a documented FRONTED_SERVICES entry
  (ollama: a loopback daemon CyClaw probes but never spawns).
- verify.sh: two mutations (a site losing its marker -> WARN; a binary
  losing every site row -> FAIL); the scratch tree now copies every
  site file straight from the checker's table so they cannot drift.
- SKILL.md step 5: the sweep covers the non-Python launch surfaces the
  AST walk cannot see (macos/*.sh, scripts/*.sh, powershell/*.ps1, the
  Dockerfile/compose command lines, the skill drivers) and documents the
  site-row requirement.
- uv row: UV_BIN (like PYTHON_BIN) is the operator's path to their own
  uv install, the same operator-resolved-binary shape as shutil.which();
  not modelled as a second launcher.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…e names

Second Codex round on PR #1637 (three P2s, all confirmed):

- The uv site rows matched only the `uv_bin="${UV_BIN:-uv}"` assignment,
  so deleting the `"$uv_bin" pip compile` call while keeping the override
  plumbing left T13 green. Both rows now also pin the invocation, and a
  new verify.sh mutation deletes just the call.
- Naming the cloud venv driver as a swept shell surface obliges the sweep
  to classify every binary it runs: curl was absent. New category-3 row
  with its five sites (three operator setup/launch scripts, two sandbox
  drivers): every call is a loopback readiness/smoke probe or the
  sandbox's agent-proxy status endpoint, except the ollama.com installer
  pipe in setup-from-clone.sh, which sits behind an interactive confirm
  that defaults to no. curl itself has no telemetry; no runtime path, the
  Dockerfile or the PowerShell scripts spawn it.
- The reverse-consistency check skipped DYNAMIC_LAUNCHER_SITES, so a
  dynamic row without a KNOWN_EXTERNAL_COMPONENTS entry verified its
  marker and never entered the sweep. Dynamic names are now included, and
  a verify.sh mutation adds an unlisted dynamic row and expects the FAIL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…export the kill block there

Third Codex round on PR #1637 (three P2s, all confirmed):

- A default parameter is not an invocation: `rclone_bin: str = "rclone"`
  survives the removal of every rclone spawn. Every fixed row now pins
  the argv literal or shell command line as well (rclone, gh, git,
  openssl, numbat), and the table's comment states the rule.
- macos/setup-from-clone.sh runs `gh auth status` / `gh auth login`
  directly, with whatever GH_TELEMETRY the operator's shell carries, and
  the gh row claimed every spawn site forced the canonical env. The
  script now evals the same `utils.telemetry_kill --export shell` block
  invoke-cyclaw.sh does, before its gh and ollama steps (non-fatal, same
  -S -E isolation); the eval line is a site marker so removing it is a
  WARN (verify.sh scenario 34). agentic/writer.py's gh argv is sited too.
- ollama was listed as never spawned; the same setup script starts
  `ollama serve` and runs `ollama pull`. Moved from FRONTED_SERVICES to
  FIXED_LAUNCHER_SITES with both lines pinned; the row text says so.
  FRONTED_SERVICES is empty for now and kept for the next real case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…cations, verify evidence paths

Fourth Codex round on PR #1637 (three P2s, all confirmed):

- `ollama pull` contacts the model registry; it sat on the category-4
  daemon row, which means local-only. The pull is now its own component
  (`ollama-pull`) and category-3 row: interactive setup only, behind the
  operator's confirm, tag validated before it reaches argv. The daemon
  row is back to `ollama serve` alone.
- The Keychain helper's security(1) site pinned only the SECURITY_BIN
  resolver. It now pins the find-generic-password call, and the two
  other Keychain scripts (set + setup-cyclaw-keys) are sited on their
  add-/find-generic-password calls too. verify.sh scenario 36 removes the
  helper's call alone and expects the WARN.
- The curl row cited docs/setup-guide.md, which does not exist; the guide
  is setup-guide.md at the root. T13 now fails any row whose
  repo-relative url is absent (scenario 35), and the self-test's scratch
  tree copies those evidence paths from the inventory so the rule can
  run there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…uites' curl

Fifth Codex round on PR #1637 (two P2s, both confirmed):

- macos/setup-from-clone.sh continued into its gh auth step when the
  canonical export could not be produced, so an ambient GH_TELEMETRY
  could still reach gh(1), which cannot self-apply the Python kill block.
  The script now records whether the export succeeded and skips step 6
  when it did not: fail closed for the one telemetry-capable binary it
  runs, non-fatal for the rest of setup (the servers re-apply at import).
  The skip guard is a FIXED_LAUNCHER_SITES marker beside the eval line.
- CyClaw-Sandbox/smoke.sh and macos-smoke.sh run curl and were not
  sited, although their sibling verify.sh was. Both are rows now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
Owner decision on PR #1637: scripts under .claude/ are agent tooling and
out of the egress inventory, except the two sandbox surfaces that
exercise the gateway itself (cyclaw-gotchas/driver.sh and the
CyClaw-Sandbox suite), whose rows stay.

SWEPT_AGENT_SURFACES names those two; T13 fails any FIXED_LAUNCHER_SITES
row under .claude/ outside them, so the boundary is only ever widened by
editing that tuple on purpose. verify.sh scenario 37 adds a row for a
session hook and expects the FAIL. SKILL.md step 5 states the decision
and why the hooks', skill helpers' and babysit wrappers' gh/git calls
are not findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
… URL opens, pin the setup Ollama bind

Seventh Codex round on PR #1637 (five P2s, all confirmed):

- An alias whose target named no inventory row still counted as
  "classified": the sweep short-circuited on the alias key. The sweep now
  resolves the target, and a dangling target is a FAIL (verify.sh 38).
- launchctl pinned only the which() lookup; sync/scheduler.py's bootout
  and print argv are sited.
- telegram/cli.py generates launchd / Task Scheduler health jobs that run
  curl and curl.exe; both literals are curl sites and the row says so.
- `open <url>` in the four macOS operator scripts is the browser, not
  the file-manager reveal: new component open-url, category 3 (one remote
  page, the signed Ollama download, behind a setup prompt; the rest open
  the loopback console).
- The ollama daemon row claimed the setup-started daemon binds loopback
  unconditionally, but the script preserves the operator's environment
  and never set OLLAMA_HOST. The spawn now pins OLLAMA_HOST=127.0.0.1:11434
  on its own command line (the threat model's Ollama endpoint), the
  marker pins that, and the row states the daemon-policy caveat for a
  daemon the operator runs themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…s, bound every site row, pin all Ollama client calls

Eighth Codex round on PR #1637 (four P2s, all confirmed):

- The site table was built reactively, one binary at a time, so the
  "all fixed launcher sites carry their markers" line was a completeness
  claim over an incomplete table. A sweep of every in-boundary Python,
  shell and PowerShell file per known binary adds the missing rows:
  rclone in sync/scheduled.py; git in the executor manifest, the eval
  dogfood script and both git-hook installers; security in
  cyclaw-keychain-load.sh and uninstall-cyclaw.sh; openssl in
  setup-cyclaw-keys.sh and cyclaw-linux-key.sh; launchctl in
  setup-cyclaw-keys.sh, uninstall-cyclaw.sh and launchd_plist.py's
  bootout; crontab, schtasks, bwrap and sandbox-exec argv; brew's
  python install; ollama's list and --version. verify.sh 40 removes one
  of the new invocations and expects the WARN.
- windows-smoke.ps1, inside the swept sandbox surface, makes
  Invoke-RestMethod / Invoke-WebRequest calls; the curl row had said
  PowerShell makes none (true of powershell/ and windows/ only). New
  pwsh-http component and category-3 row, sited; the curl row is
  corrected.
- The agent-tooling boundary checked fixed rows only and matched the
  single-file exception by prefix. It now covers dynamic rows too and
  matches a non-directory entry exactly (verify.sh 39 adds both an
  excluded dynamic row and "driver.sh.backup" and expects both named).
- OLLAMA_HOST was pinned on the serve line alone; `ollama list` and the
  confirmed pull still inherited the ambient value, so a remote
  OLLAMA_HOST would list and pull against another daemon. Step 7 now
  exports the loopback endpoint once for every Ollama call it makes; the
  markers pin the export and each client call.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…erShell launchers, pin the Homebrew export

Ninth Codex round on PR #1637 (four P2s, all confirmed):

- uv's only invocations (`uv pip compile`, `uv pip install`) resolve and
  download from a package index on a cold cache; the row said as much
  and still called it category 5. It is category 3 now: the row
  describes what the invocations do on the network, and the telemetry
  negative finding stays in the evidence. SECURITY.md's egress
  classification moves uv accordingly and names the rest of the
  install-time category-3 surface so the two copies agree.
- The installers' `git clone` / `git pull` contradicted the git row's
  "local-only subcommands" claim. New git-remote component and
  category-3 row with the three installer sites (two macOS, one
  PowerShell); the local row now says where network git lives.
- powershell/Uninstall-CyClaw.ps1 runs cmd.exe and schtasks.exe,
  Install-CyClaw.ps1 launches PowerShell children and Invoke-CyClaw.ps1
  opens the console with Start-Process: all sited. The earlier sweep's
  patterns were shell- and Python-shaped and missed PowerShell forms.
- `export HOMEBREW_NO_ANALYTICS=1` is the pre-execution protection the
  Homebrew row claims and was not a marker; it is now, with verify.sh 41
  removing it and expecting the WARN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit pushed a commit that referenced this pull request Oct 13, 2026
…lls, site the gated git push

Tenth Codex round on PR #1637 (three P2s, all confirmed):

- The repo-relative url rule joined `root / url`, which an absolute url
  discards and a `..` component escapes. The schema now refuses absolute
  paths and `..` components outright and requires the resolved target to
  stay under the root (verify.sh 42 and 43).
- pip's hash-pinned installs in macos/install-cyclaw.sh,
  powershell/Install-CyClaw.ps1, the Dockerfile and the venv driver's
  fallback, and the confirmed `brew install python@3.12`, are
  package-index downloads: category-3 pip-install and brew-install
  components with their sites, consistent with the uv reclassification.
  The Homebrew analytics row keeps its export and analytics-off markers;
  the install moves to the new row. SECURITY.md's egress paragraph names
  them.
- RepoWorkspaceTools.push_branch runs `git push --set-upstream origin`
  when allow_git_write_tools and agentic.enabled are both switched on;
  the git-remote row now covers installers' clone/pull and that gated
  runtime push, with the argv literal as its marker, and the local git
  row says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3
cgfixit added a commit that referenced this pull request Oct 13, 2026
…-13)

High-signal since 3fe8217 (#1619): #1637 telemetry/egress inventory completion (fail-closed kill block, full spawn-site table for PowerShell/pip/brew/git-remote cat-3, agent-tooling boundary, OLLAMA_HOST pin, secret-env wrappers); #1643/#1645 docs resyncs correcting I6/harness and checker skill drifts. Graph remains 12 nodes.
cgfixit added a commit that referenced this pull request Oct 13, 2026
…d keyring helpers, drop the sandbox punkt fetch (#1646)

Codex round eleven on #1637, all four findings confirmed, plus what a
token-level sweep of every in-boundary shell surface turned up beside them:

- gh's GitHub traffic is functional egress, not only suppressed telemetry:
  new category-3 component `gh-api` sited on the setup script's
  `gh auth status` / `gh auth login --with-token` and the agentic
  `gh pr view` / `gh pr create` argv, beside the category-1 telemetry row
  (the git / git-remote precedent). SECURITY.md's category-3 sentence names
  it. Scenario 48 removes both auth-login branches and expects the WARN.
- The generated Task Scheduler rows pinned a filename and a which():
  cmd.exe now pins the COMSPEC command construction and the `/c <launcher>`
  argument, powershell the executable resolver and the `-File wrapper`
  argv. COMSPEC is a path override of the same interpreter (the UV_BIN
  shape), so it stays a fixed component. Scenario 49 swaps the COMSPEC
  construction and expects the WARN.
- macOS setup/uninstall helpers and the Linux keyring CLI had no rows:
  expect, osascript, pbcopy, pbpaste, lsof, sw_vers and secret-tool join the
  local OS tooling row with invocation sites; CYCLAW_SECRET_TOOL is a path
  override, not a caller-chosen command. The same sweep found the
  Dockerfile's apt-get (new category-3 row `apt-install`) and its
  groupadd/useradd (local tooling). Scenarios 50 and 51 remove the expect
  invocation and the secret-tool lookup.
- The sandbox audit's `nltk.download('punkt')` step was obsolete (the
  stemmer uses PorterStemmer only, no punkt, per SECURITY.md) and would
  have reached the NLTK data host on a cold cache: removed from
  .claude/skills/CyClaw-Sandbox/verify.sh; the core-libs row says so.
- SKILL.md step 5 records the token-level sweep method and the
  path-override rule so the next round starts from a complete list.

Checks: strict checker 0/0 (44 rows, 95 components, 89 sites / 39
binaries, 4 dynamic sites), verify.sh ALL PASS (55), doc-sync 0 drift,
ruff clean, bash -n on both verify scripts.


Claude-Session: https://claude.ai/code/session_012semJHbJjTtdTy3zrhUnR3

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants