v1.55.1.0 fix: telemetry consent accuracy + gstack-slug cache sanitization (#1848)

* fix(gstack-slug): sanitize cached slug before eval

The compute and fallback paths filter slug output to [a-zA-Z0-9._-], but a
value read straight from ~/.gstack/slug-cache was echoed into eval output
unsanitized. A locally-planted cache file could inject shell into
eval "$(gstack-slug)". Re-sanitize on every path so the invariant the file
header promises actually holds, and heal a poisoned cache on the next write.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(telemetry): accurate consent copy + JSON-safe repo basename

The telemetry consent prompt promised "no repo names" while the preamble
epilogue records the repo basename in the local skill-usage.jsonl. It is
already stripped before any remote upload, so it never left the machine, but
the copy was unqualified. Reword it to state repo name is local-only and
stripped before upload.

Also sanitize the basename to [a-zA-Z0-9._-] before it goes into the
hand-built JSON, so a repo directory name containing quotes or newlines can
neither break the JSON nor leak a fragment past the regex stripper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(docs): regenerate SKILL.md + ship goldens for telemetry change

Generated output of the preceding resolver change: the corrected consent copy
and sanitized repo basename now appear in every skill preamble. Golden ship
fixtures refreshed to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(telemetry): enforce no-repo-identity-egress invariant

Pins the contract that repo/branch identity in the synced skill-usage.jsonl is
stripped before the remote POST. Three checks: a floor (the three known fields),
coverage (every repo/branch field a producer writes into skill-usage.jsonl is
stripped, so a future producer rename can't silently leak), and behavior (runs
the actual sed strip expressions over a sample event). Scoped to the synced
file, so the local-only timeline branch field is correctly excluded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(gstack-slug): regression test for cached-slug eval injection

Proves a poisoned ~/.gstack/slug-cache file cannot inject shell metacharacters
into gstack-slug output (the value consumed by eval). Verified red when the
cache-read sanitization is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: bump version and changelog (v1.55.1.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-02 22:36:34 -07:00
committed by GitHub
parent 3bef43bc5a
commit c43c850cae
59 changed files with 356 additions and 108 deletions
+38
View File
@@ -1,5 +1,43 @@
# Changelog
## [1.55.1.0] - 2026-06-02
## **Telemetry now tells you exactly what it records and where it stays. The project-slug helper hands the shell a safe identifier on every path.**
The telemetry opt-in screen now states the truth without asterisks: it shares skill name, duration, crashes, and a stable device ID, with no code and no file paths, and your repo name is recorded locally only and stripped before any upload. Under the hood, the helper that every skill uses to find your project (`gstack-slug`) now filters its output to `[a-zA-Z0-9._-]` on every path, including the cached one, so the value that gets handed to the shell is always a plain identifier. Two regression tests lock both behaviors so they can't quietly drift back.
### The guarantees that matter
These are enforced by tests in this release, not promises (`bun test test/telemetry-repo-strip.test.ts test/gstack-slug-sanitize.test.ts`):
| Guarantee | Pinned by |
|-----------|-----------|
| Your repo name never leaves the machine (stripped before upload) | `telemetry-repo-strip.test.ts` — floor + producer-coverage + runs the real strip over a sample event |
| A tampered slug cache can't put shell characters into the helper's output | `gstack-slug-sanitize.test.ts` — fails if the sanitization is removed |
| The consent copy matches what the code actually does | `generate-telemetry-prompt.ts` (regenerated into every skill) |
The repo-identity test covers all three producer fields (`repo`, `_repo_slug`, `_branch`), so adding a new field that forgets to get stripped fails CI rather than shipping silently.
### What this means for you
Your telemetry choice screen now describes what actually happens, so you can opt in (or not) on accurate information. If you share a machine or have ever worried about a tampered `~/.gstack` cache, the slug helper now refuses to pass anything but a safe identifier to the shell. Nothing to do — both land automatically on upgrade.
### Itemized changes
#### Changed
- Telemetry consent copy is now accurate: "No code or file paths. Your repo name is recorded locally only and stripped before any upload" (was "No code, file paths, or repo names").
#### Fixed
- `gstack-slug` sanitizes its output to `[a-zA-Z0-9._-]` on every path, including values read from its on-disk cache, so `eval "$(gstack-slug)"` always receives a plain identifier. A tampered cache file is also healed on the next write.
- The telemetry preamble sanitizes the repo basename before building its JSON line, so an unusual repo directory name can't malform the local analytics record.
#### Added
- `test/telemetry-repo-strip.test.ts` — enforces that no repo/branch identity field reaches the upload batch (floor + producer-coverage + real-strip behavior).
- `test/gstack-slug-sanitize.test.ts` — regression test proving a poisoned slug cache cannot inject shell metacharacters.
#### For contributors
- The consent copy and repo-basename handling live in `scripts/resolvers/preamble/`; all `SKILL.md` files and the ship goldens were regenerated from those resolvers.
## [1.55.0.0] - 2026-05-30
## **`/sync-gbrain` can no longer be the trigger that lets gbrain delete your repo. The headed browser stops crash-looping, and gbrain installs the current release instead of a pin 23 versions stale.**