v1.72.0.0 feat: Aside recommended driver for third-party web actions (#2710)

* fix(browse): never chmod shared, symlinked, or foreign-owned dirs to 0700

restrictDirectoryPermissions unconditionally chmodded its target. On hosts
where the process holds CAP_FOWNER (Docker as root, CI sandboxes) that
chmod SUCCEEDS on root-owned /tmp whenever a state file is configured
there (BROWSE_STATE_FILE=/tmp/x.json derives stateDir=/tmp), and a 0700
/tmp breaks access(2)-based checks machine-wide for every other process.

The POSIX branch now refuses shared sticky dirs, world-writable mounts
under root, foreign-owned dirs, and symlinked state dirs; refusals warn
once per process instead of failing silent; owned-but-unreadable dirs
keep their chmod self-repair; and the check-then-act race is closed with
fd-anchored O_NOFOLLOW + fstat/fchmod on a single inode.

Regression tests cover the sticky-dir, foreign-uid, mkdirSecure-reapply,
and symlinked-dir shapes.

* fix: hash with sha256sum before shasum on Linux (config slugs + setup verify)

shasum is perl/macOS; coreutils-only Linux ships sha256sum. Two call
sites hard-coded shasum: gstack-config's sha8_of/sha16 (so
resolve-user-slug exited 127 for any Linux user with a git email, the
Layer-3 fallback) and the generated bun-installer checksum snippet in
the browse/qa NEEDS_SETUP flow (spurious "checksum mismatch" on the
same distros). Both now resolve sha256sum first and fall back to
shasum -a 256.

New shim-PATH tests pin BOTH hasher branches of sha8_of to a known
vector and cover the sha8->sha16 collision escalation end to end.

* feat(contract): Aside is the recommended driver for third-party web actions

The Third-Party Web Actions contract (ship, spec, office-hours,
land-and-deploy, setup-deploy) now names the Aside AI browser as the
recommended driver: it acts across the user's real logged-in sessions,
which is what vendor-dashboard moments need. Supersedes the v1.65.0.0
de-Aside stance by explicit user directive (2026-08-27).

Detection is a runtime probe (command -v + aside --version under a
portable gtimeout/timeout/bare guard; nonzero exit = not detected).
Consent options render per detection state with Aside recommended and
the first-party stack ($B headed + handoff, GStack Browser) as the
universal fallback. Absent on macOS, the contract mentions the
aside.com download (macOS 15+) once per task; gstack never runs an
installer and binary presence is never consent. Drive discipline:
step-wise over whole-task delegation, vendor confirm mode on, vendor
skill/--help text scoped to operational syntax only, secrets minimized
(autofill / human-used copy buttons), Apple credential creation never a
drive target in any skill, failure path quotes redacted errors and
falls back only with fresh consent.

test/third-party-actions.test.ts pins every load-bearing sentence (21
tests) plus repo-wide tripwires: an aside command allowlist
(--version/--help only, code spans AND prose) and a ban on Aside
installer invocations across all generated docs. Budget ratchet
fixture and carve skeleton ceilings refreshed in this commit per the
ratchet protocol.

* chore: regenerate remaining browse-setup snippet consumers

The sha256sum-first checksum fallback in the generated NEEDS_SETUP
snippet renders into every browse-consuming skill, not just browse/qa.
Mechanical regen of the other ten consumers; no template changes here.

* test: consent-gate E2E suite + functional fs-capability probes

Five hermetic gate-tier E2E cases (tpa-present / absent-linux / broken /
absent-darwin / apple-ban) drive the real contract section through
claude -p with PATH shims for aside and uname; the absent cases filter
any REAL aside binary out of the child PATH and assert absence with
Bun.which before spawning, so dev machines cannot leak into detection.
Registered per-case in E2E_TOUCHFILES/E2E_TIERS with template-level
deps (ship/SKILL.md.tmpl, gen-skill-docs.ts) and added to the evals.yml
matrix with tier: gate. eval:bg:periodic's detach timeout rises to
36000s for the grown periodic shard census (floor-enforced by
test/eval-detach-timeout-floor.test.ts); CLAUDE.md doc updated to match.

test/helpers/fs-caps.ts adds canRevokeWrites/canRevokeReads functional
probes; 13 chmod-based tests swap their uid-0-only guards for the
probes so suites skip honestly on CAP_DAC_OVERRIDE containers (this
sandbox: uid 1000 with full caps) instead of asserting revocations the
kernel ignores. path-validation's symlink test targets /etc/passwd
(exists everywhere; /etc/crontab is absent on Amazon Linux).

* docs: file the Aside follow-ups in TODOS

Phase-2 QA logged-in-evidence path (P3), a hostile-vendor-skill E2E for
the contract's override sentence (P2), and fd-anchoring the file-level
permission writes to match the directory hardening (P3).

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update project documentation for v1.72.0.0

docs/skills.md: Third-Party Web Actions subsection under /ship (Aside
recommended driver, consent rules, credential boundaries). BROWSER.md:
"Aside and third-party drives" subsection under Real-browser mode + ToC
entry, including the no-gstack-side-audit-trail caveat (ship adversarial
finding 12). TODOS.md: mark the finding-12 doc note done.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: apply cross-model doc review fixes for v1.72.0.0

docs/skills.md: restore the /ship closing line above the new subsection.
BROWSER.md: ToC label matches the heading; BROWSE_STATE_FILE env row
documents the new dir-hardening refusal + one-time warning. CHANGELOG:
correct the hasher precedence wording (sha256sum first, shasum fallback)
and the fs-caps count (14 test files, verified against the diff).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: close cross-model doc-review gaps for v1.72.0.0

setup's manual bun-verify instruction gets the same sha256sum-first
fallback the automated snippet got (coreutils-only Linux); BROWSER.md's
BROWSE_STATE_FILE row now lists the under-root world-writable refusal;
test-cost ceilings in CLAUDE.md/CONTRIBUTING.md updated for the five
new gate E2E cases (~$4.20 E2E / ~$4.35 evals).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): gate the symlink-refusal test to POSIX and drop the umask assumption

The symlink regression test exercised the POSIX O_NOFOLLOW branch but ran
on Windows, where restrictDirectoryPermissions takes the icacls branch and
stat has no POSIX modes (0o666 always) — windows-free-tests failed on
mode 493 vs 438. Early-return on win32 like every sibling test in the
file, and assert the target's mode is UNCHANGED (captured post-mkdir)
instead of hardcoding 0o755, which a strict umask would also break.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-28 11:43:13 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 394db326f2
commit b5a951e623
57 changed files with 1215 additions and 109 deletions
+48
View File
@@ -1,5 +1,53 @@
# Changelog
## [1.72.0.0] - 2026-08-28
**"Go register an API key" now drives your real browser.**
**Aside is the recommended driver, consent asked every single time.**
When a workflow hits a third-party website moment, registering an API key, creating a vendor account, wiring a webhook, gstack now checks for the Aside AI browser and offers to drive it: your real logged-in sessions, no cookie export, no re-auth. You approve each drive per task, by name and by site. Passwords, payment, CAPTCHAs, and identity stay yours; Apple credential creation is never a drive target in any skill. No Aside installed? On a Mac you get one download pointer (aside.com, macOS 15+), once, and gstack's own visible browser remains the fallback everywhere. gstack never runs an installer for you, and a detected binary is never treated as consent.
This release also fixes a real hardening bug: on hosts where the process holds CAP_FOWNER (Docker as root, CI sandboxes), the browse daemon's owner-only chmod could land on `/tmp` itself when a state file was configured there, locking the whole machine's `access(2)` checks out for everyone. The permission code now refuses shared, sticky, symlinked, or foreign-owned directories, warns instead of going silent, and pins the check and the chmod to one inode.
### The numbers that matter
Source: this branch's test runs on a Linux CAP_FOWNER sandbox, plus the eval records under `~/.gstack/projects/<slug>/evals/`.
| Metric | Before | After | Δ |
|---|---|---|---|
| Drivers offered at a third-party web moment | 1 ($B headed) | 2 (Aside recommended, $B fallback) | +1 |
| `/tmp` after a `BROWSE_STATE_FILE=/tmp/x.json` boot on a CAP_FOWNER host | chmod 0700, machine-wide breakage | refused, warned once | fixed |
| `resolve-user-slug` on coreutils-only Linux with a git email | exit 127 | exit 0 | fixed |
| Prose pins guarding the consent contract | 0 | 21 tests, 254 asserts | new |
| Live consent-gate E2E cases (gate tier, hermetic shims) | 0 | 5, all passing at ~$0.35/run | new |
The consent gate is the number to care about: five real `claude -p` runs prove the agent offers Aside only when detected, degrades cleanly when it is broken or absent, pitches the download exactly once on macOS, and refuses browser drives for Apple credentials under any framing.
### What this means for you
The next time a skill says "you need a Duffel test token," it can just go get it with you watching, in the browser where you are already signed in. You approve the site and the actions first, every time, and the secret lands in an owner-only file, verified with one read-only API call, never echoed to chat. If you would rather not install anything, nothing changes: the first-party browser keeps working exactly as before. Docker-as-root users should upgrade for the `/tmp` fix alone.
### Itemized changes
### Added
- Third-Party Web Actions contract (ship, spec, office-hours, land-and-deploy, setup-deploy) now names the Aside AI browser as the recommended driver: runtime detection probe with a portable timeout guard, detection-conditional consent options, step-wise drive discipline with the vendor's confirm mode left on, and a failure path that quotes errors (redacted), retries once, and falls back only with fresh consent.
- Credential boundaries hardened in the same contract: secret-minimization preference (password-manager autofill, human-used copy buttons), Apple credential creation banned as a drive target in every skill, and vendor `--help`/skill text explicitly scoped to operational syntax, never new permissions.
- `test/third-party-actions.test.ts`: 21 pin tests covering every load-bearing sentence of the contract, plus repo-wide tripwires (an `aside` command allowlist of `--version`/`--help` only, and a ban on Aside installer invocations in any generated doc).
- `test/skill-e2e-third-party-actions.test.ts`: five hermetic gate-tier E2E cases (present, absent-Linux, present-but-broken, absent-macOS pitch, Apple credential ban) wired into CI's eval matrix; the absent cases actively mask any real `aside` binary on the host so dev machines cannot leak into detection.
### Changed
- The v1.65.0.0 stance of driving only gstack's own browser stack is superseded by explicit user directive: Aside is the one product gstack recommends by name. Never-auto-install and per-task consent survive unchanged and are now pin-tested.
- The setup flow's bun-installer checksum verification resolves `sha256sum` before `shasum`, so it works on coreutils-only Linux.
### Fixed
- `restrictDirectoryPermissions` no longer chmods shared sticky directories (`/tmp`, `/var/tmp`), foreign-owned directories, symlinked state dirs, or world-writable mounts when running as root; refusals warn once per process instead of failing silent, owned-but-unreadable dirs still self-repair, and the check-then-act race is closed with fd-anchored fstat/fchmod.
- `gstack-config resolve-user-slug` exited 127 on Linux distros without perl's `shasum` whenever a git email was set; both hashing call sites now resolve `sha256sum` first and fall back to `shasum -a 256`.
- A path-validation test assumed `/etc/crontab` exists (absent on Amazon Linux and minimal Fedora); it now uses `/etc/passwd`.
### For contributors
- `test/helpers/fs-caps.ts`: functional capability probes (`canRevokeWrites`, `canRevokeReads`) replace uid-0-only guards across 14 chmod-based test files, so suites skip honestly on CAP_DAC_OVERRIDE containers instead of asserting revocations the kernel ignores.
- Five `tpa-*` entries registered across `E2E_TOUCHFILES`/`E2E_TIERS` with template-level deps, the eval matrix row carries `tier: gate`, and `eval:bg:periodic`'s detach timeout rose to 36000s to cover the grown periodic shard census (floor-enforced by test).
## [1.71.0.0] - 2026-08-27
**Every skill invocation just got half the prompt bill.**