Skip to content

fix: security wave 1 — 14 fixes for audit #783 (v0.15.7.0)#810

Merged
garrytan merged 16 commits intomainfrom
garrytan/security-wave-1
Apr 5, 2026
Merged

fix: security wave 1 — 14 fixes for audit #783 (v0.15.7.0)#810
garrytan merged 16 commits intomainfrom
garrytan/security-wave-1

Conversation

@garrytan
Copy link
Copy Markdown
Owner

@garrytan garrytan commented Apr 5, 2026

Summary

Addresses 6 critical + 4 high findings from security audit #783. Fourteen bisected commits, each independently revertable.

Network exposure:

  • Design server bound 0.0.0.0 (anyone on your WiFi could read your files). Now localhost-only + path traversal blocked on /api/reload.
  • CORS wildcard removed from browse server. Chrome extension uses manifest host_permissions, unaffected.
  • /health endpoint no longer leaks auth token to non-extension callers.

Auth gaps:

  • /inspector/events SSE endpoint now requires auth (matching /activity/stream).
  • Cookie picker auth made mandatory (was conditional on authToken being defined).

Filesystem:

  • All ~/.gstack/ dirs created 0o700, files 0o600. Setup script sets umask 077.
  • TOCTOU race in setup symlink creation fixed.
  • Symlink bypass in validateOutputPath closed.

LLM trust boundaries:

  • Design feedback wrapped in XML trust markers with tag escaping. Accumulated feedback capped to 5 iterations.

Community PRs included: #744 (DNS rebinding IPv6), #745 (symlink bypass), #751 (URL validation), #750 (telemetry key), #743 (killAgent), #803 (design server localhost).

Deferred to wave 2: H3 (auth token in URLs), H6 (sidebar trust markers), H7 (git clone GPG), H8 (frozen-lockfile), H4 (codex trust boundaries in template).

Test Coverage

All existing tests pass. server-auth test updated for new /health token gating.

Pre-Landing Review

CEO review (HOLD_SCOPE, clean) + Eng review (FULL_REVIEW, clean) + Codex adversarial review completed.

Test plan

  • All bun tests pass (skill validation, gen-skill-docs, browse integration, server-auth)
  • CHANGELOG version sequence verified (0.15.7.0 > 0.15.6.2, contiguous)

Closes #783 (wave 1)

🤖 Generated with Claude Code

garrytan and others added 16 commits April 4, 2026 21:18
Cherry-pick PR #744 by @Gonzih. Closes the IPv6-only DNS rebinding gap
by checking both A and AAAA records independently.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e-dir check

Cherry-pick PR #745 by @Gonzih. Adds a second pass using fs.realpathSync()
to resolve symlinks after lexical path validation.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-pick PR #751 by @Gonzih. Prevents navigation to cloud metadata
endpoints or file:// URIs embedded in user-writable state files.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-pick PR #750 by @Gonzih. The service role key bypasses RLS and
grants unrestricted database access — anon key + RLS is the right model
for a public telemetry endpoint.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-pick PR #743 by @Gonzih. Implements cross-process kill signaling
via kill-file + polling pattern, tracks active processes per-tab.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-pick PR #803 by @garagon. Adds hostname: '127.0.0.1' to Bun.serve()
and validates /api/reload paths are within cwd() or tmpdir(). Closes C1+C2
from security audit #783.

Co-Authored-By: garagon <garagon@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /inspector/events endpoint had no authentication, unlike /activity/stream
which validates tokens. Now requires the same Bearer header or ?token= query
param check. Closes C3 from security audit #783.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap user feedback in <user-feedback> XML markers with tag escaping to
prevent prompt injection via malicious feedback text. Cap accumulated
feedback to last 5 iterations to limit incremental poisoning.
Closes C4 and H5 from security audit #783.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add mode 0o700 to all mkdirSync calls for state/session directories.
Add mode 0o600 to all writeFileSync calls for session.json, chat.jsonl,
and log files. Add umask 077 to setup script. Prevents auth tokens, chat
history, and browser logs from being world-readable on multi-user systems.
Closes C5, H9, M9, M10 from security audit #783.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the existence check before mkdir -p (it's idempotent) and validate
the target isn't already a symlink before creating the link. Prevents a
local attacker from racing between the check and mkdir to redirect
SKILL.md writes. Closes C6 from security audit #783.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Access-Control-Allow-Origin: * with http://127.0.0.1 on sidebar
tab/chat endpoints. The Chrome extension uses manifest host_permissions
to bypass CORS entirely, so this only blocks malicious websites from
making cross-origin requests. Closes H1 from security audit #783.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the conditional if(authToken) guard that skipped auth when
authToken was undefined. Now all cookie picker data/action routes
reject unauthenticated requests. Closes H2 from security audit #783.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only return the auth token in /health response when the request Origin
starts with chrome-extension://. The Chrome extension always sends this
origin via manifest host_permissions. Regular HTTP requests (including
tunneled ones from ngrok/SSH) won't get the token. The extension also
has a fallback path through background.js that reads the token from the
state file directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test previously checked for 'localhost-only' comment. Now checks for
'chrome-extension://' since the token is gated on Origin header.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

E2E Evals: ✅ PASS

8/8 tests passed | $1.08 total cost | 12 parallel runners

Suite Result Status Cost
e2e-browse 2/2 $0.13
e2e-deploy 2/2 $0.33
e2e-qa-workflow 1/1 $0.47
llm-judge 1/1 $0.02
e2e-browse 2/2 $0.13

12x ubicloud-standard-2 (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite

@garrytan garrytan merged commit 115d81d into main Apr 5, 2026
18 checks passed
joethorngren added a commit to joethorngren/jstack that referenced this pull request Apr 6, 2026
Upstream features merged:
- GStack Browser with anti-bot stealth (garrytan#695)
- Adaptive gating + cross-review dedup for review army (garrytan#760)
- Voice-friendly skill triggers for AquaVoice (garrytan#732)
- Native OpenClaw skills + ClaHub publishing (garrytan#832)
- Declarative multi-host platform (OpenCode, Slate, Cursor, OpenClaw) (garrytan#793)
- Interactive /plan-devex-review + DX review skills (garrytan#784, garrytan#796)
- Ship re-run verification checks (garrytan#833)
- Community security wave — 8 PRs, 4 contributors (garrytan#847)
- Security wave 1 — 14 fixes for audit (garrytan#810)
- Team-friendly gstack install mode (garrytan#809)
- Anti-skip rule for all review skills (garrytan#804)
- Various bug fixes and doc updates

Conflict resolution strategy:
- Branding files (README, CLAUDE.md, CHANGELOG, VERSION, CONTRIBUTING, TODOS): kept jstack
- SKILL.md / SKILL.md.tmpl files: took upstream (skill improvements)
- Code files (browse/, scripts/, tests, setup, package.json): took upstream
- Telemetry files (bin/gstack-telemetry-sync, supabase telemetry-ingest): kept deleted (jstack privacy policy)
- New upstream files (hosts/, openclaw/, devex-review/, etc.): accepted as-is

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jeffdhooton pushed a commit to jeffdhooton/gstack that referenced this pull request Apr 7, 2026
…arrytan#810)

* fix: DNS rebinding protection checks AAAA (IPv6) records too

Cherry-pick PR garrytan#744 by @Gonzih. Closes the IPv6-only DNS rebinding gap
by checking both A and AAAA records independently.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: validateOutputPath symlink bypass — resolve real path before safe-dir check

Cherry-pick PR garrytan#745 by @Gonzih. Adds a second pass using fs.realpathSync()
to resolve symlinks after lexical path validation.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: validate saved URLs before navigation in restoreState

Cherry-pick PR garrytan#751 by @Gonzih. Prevents navigation to cloud metadata
endpoints or file:// URIs embedded in user-writable state files.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: telemetry-ingest uses anon key instead of service role key

Cherry-pick PR garrytan#750 by @Gonzih. The service role key bypasses RLS and
grants unrestricted database access — anon key + RLS is the right model
for a public telemetry endpoint.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: killAgent() actually kills the sidebar claude subprocess

Cherry-pick PR garrytan#743 by @Gonzih. Implements cross-process kill signaling
via kill-file + polling pattern, tracks active processes per-tab.

Co-Authored-By: Gonzih <gonzih@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(design): bind server to localhost and validate reload paths

Cherry-pick PR garrytan#803 by @garagon. Adds hostname: '127.0.0.1' to Bun.serve()
and validates /api/reload paths are within cwd() or tmpdir(). Closes C1+C2
from security audit garrytan#783.

Co-Authored-By: garagon <garagon@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add auth gate to /inspector/events SSE endpoint (C3)

The /inspector/events endpoint had no authentication, unlike /activity/stream
which validates tokens. Now requires the same Bearer header or ?token= query
param check. Closes C3 from security audit garrytan#783.

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

* fix: sanitize design feedback with trust boundary markers (C4+H5)

Wrap user feedback in <user-feedback> XML markers with tag escaping to
prevent prompt injection via malicious feedback text. Cap accumulated
feedback to last 5 iterations to limit incremental poisoning.
Closes C4 and H5 from security audit garrytan#783.

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

* fix: harden file/directory permissions to owner-only (C5+H9+M9+M10)

Add mode 0o700 to all mkdirSync calls for state/session directories.
Add mode 0o600 to all writeFileSync calls for session.json, chat.jsonl,
and log files. Add umask 077 to setup script. Prevents auth tokens, chat
history, and browser logs from being world-readable on multi-user systems.
Closes C5, H9, M9, M10 from security audit garrytan#783.

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

* fix: TOCTOU race in setup symlink creation (C6)

Remove the existence check before mkdir -p (it's idempotent) and validate
the target isn't already a symlink before creating the link. Prevents a
local attacker from racing between the check and mkdir to redirect
SKILL.md writes. Closes C6 from security audit garrytan#783.

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

* fix: remove CORS wildcard, restrict to localhost (H1)

Replace Access-Control-Allow-Origin: * with http://127.0.0.1 on sidebar
tab/chat endpoints. The Chrome extension uses manifest host_permissions
to bypass CORS entirely, so this only blocks malicious websites from
making cross-origin requests. Closes H1 from security audit garrytan#783.

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

* fix: make cookie picker auth mandatory (H2)

Remove the conditional if(authToken) guard that skipped auth when
authToken was undefined. Now all cookie picker data/action routes
reject unauthenticated requests. Closes H2 from security audit garrytan#783.

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

* fix: gate /health token on chrome-extension Origin header

Only return the auth token in /health response when the request Origin
starts with chrome-extension://. The Chrome extension always sends this
origin via manifest host_permissions. Regular HTTP requests (including
tunneled ones from ngrok/SSH) won't get the token. The extension also
has a fallback path through background.js that reads the token from the
state file directly.

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

* test: update server-auth test for chrome-extension Origin gating

The test previously checked for 'localhost-only' comment. Now checks for
'chrome-extension://' since the token is gated on Origin header.

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

* chore: bump version and changelog (v0.15.7.0)

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

---------

Co-authored-by: Gonzih <gonzih@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: garagon <garagon@users.noreply.github.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.

Security audit: 6 critical, 10 high findings across network, filesystem, LLM trust boundaries

1 participant