fix(security): 20 additional bugs found via sqry semantic code graph (round 2)#806
Closed
mr-k-man wants to merge 16 commits intogarrytan:mainfrom
Closed
fix(security): 20 additional bugs found via sqry semantic code graph (round 2)#806mr-k-man wants to merge 16 commits intogarrytan:mainfrom
mr-k-man wants to merge 16 commits intogarrytan:mainfrom
Conversation
…ymlink traversal Both meta-commands.ts and write-commands.ts validateOutputPath previously used path.resolve which resolves paths logically without following symlinks. A symlink at /tmp/safe pointing to /etc would pass the safe-directory check. Switched to fs.realpathSync (matching the existing validateReadPath pattern in read-commands.ts): - Resolve the full real path for existing files - Resolve the parent directory for new files (ENOENT case) - Also resolve SAFE_DIRECTORIES themselves through realpathSync (handles macOS /tmp → /private/tmp) Added browse/test/security-audit-r2.test.ts with source-level checks and behavioral tests including symlink-to-/etc attack verification.
Block data exfiltration via url(), expression(), @import, javascript:, and data: patterns in write-commands style handler, cdp-inspector modifyStyle, extension injectCSS (also validates id format), and extension toggleClass (validates className format). Adds source-level regression tests for all four injection points.
Replace direct bash variable interpolation into JS string literals with environment variable passing. Values are now passed via GSTACK_FILTER_* env vars and read with process.env inside the bun -e block, eliminating the shell injection vector where single quotes in --query/--type/--slug could break out of JS string literals and execute arbitrary code. Adds browse/test/learnings-injection.test.ts with source-level and behavioral tests to prevent regression.
…ions Add isValidQueueEntry() validator to sidebar-agent.ts that checks all 8 QueueEntry fields (prompt required, args array, cwd path traversal, typed optional fields). Invalid entries are skipped with console.warn. Queue directory and file permissions hardened to 0o700/0o600 in all three writers: server.ts, sidebar-agent.ts, and cli.ts. Tests added to security-audit-r2.test.ts.
- applyStyle in extension/inspector.js now validates CSS values with the same DANGEROUS_CSS pattern as injectCSS (blocks url(), expression(), @import, javascript:, data: exfiltration vectors) - snapshot.ts annotated screenshot path validation now uses realpathSync to resolve symlinks before checking against safe directories - cookie-import in write-commands.ts replaces inline path.resolve+isPathWithin with realpathSync-based validation consistent with validateOutputPath - isValidQueueEntry in sidebar-agent.ts now checks stateFile for '..' path traversal sequences in addition to type validation - Adds source-level regression tests for all four fixes in security-audit-r2.test.ts
Owner
|
Big thanks for this contribution, @mr-k-man! Your fix has been landed on the garrytan/security-wave-5 branch as part of our community security wave (commit dfe946fe). We credited you as co-author. Ships in v0.15.12.0. Really appreciate you taking the time to find and fix this. |
garrytan
added a commit
that referenced
this pull request
Apr 6, 2026
…ion, tests (#806) Community PR #806 by @mr-k-man (security audit round 2, new parts only). - CSS value validation (DANGEROUS_CSS) in cdp-inspector, write-commands, extension inspector - Queue file permissions (0o700/0o600) in cli, server, sidebar-agent - escapeRegExp for frame --url ReDoS fix - Responsive screenshot path validation with validateOutputPath - State load cookie filtering (reject localhost/.internal/metadata cookies) - Session ID format validation in loadSession - /health endpoint: remove currentUrl and currentMessage fields - QueueEntry interface + isValidQueueEntry validator for sidebar-agent - SIGTERM->SIGKILL escalation in timeout handler - Viewport dimension clamping (1-16384), wait timeout clamping (1s-300s) - Cookie domain validation in cookie-import and cookie-import-browser - DocumentFragment-based tab switching (XSS fix in sidepanel) - pollInProgress reentrancy guard for pollChat - toggleClass/injectCSS input validation in extension inspector - Snapshot annotated path validation with realpathSync - 714-line security-audit-r2.test.ts + 33-line learnings-injection.test.ts Co-Authored-By: mr-k-man <mr-k-man@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
garrytan
added a commit
that referenced
this pull request
Apr 6, 2026
* fix(bin): pass search params via env vars (RCE fix) (#819) Replace shell string interpolation with process.env in gstack-learnings-search to prevent arbitrary code execution via crafted learnings entries. Also fixes the CROSS_PROJECT interpolation that the original PR missed. Adds 3 regression tests verifying no shell interpolation remains in the bun -e block. Co-authored-by: garagon <garagon@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(browse): add path validation to upload command (#821) Add isPathWithin() and path traversal checks to the upload command, blocking file exfiltration via crafted upload paths. Uses existing SAFE_DIRECTORIES constant instead of a local copy. Adds 3 regression tests. Co-authored-by: garagon <garagon@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(browse): symlink resolution in meta-commands validateOutputPath (#820) Add realpathSync to validateOutputPath in meta-commands.ts to catch symlink-based directory escapes in screenshot, pdf, and responsive commands. Resolves SAFE_DIRECTORIES through realpathSync to handle macOS /tmp -> /private/tmp symlinks. Existing path validation tests pass with the hardened implementation. Co-authored-by: garagon <garagon@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add uninstall instructions to README (#812) Community PR #812 by @0531Kim. Adds two uninstall paths: the gstack-uninstall script (handles everything) and manual removal steps for when the repo isn't cloned. Includes CLAUDE.md cleanup note and Playwright cache guidance. Co-Authored-By: 0531Kim <0531Kim@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(browse): Windows launcher extraEnv + headed-mode token (#822) Community PR #822 by @pieterklue. Three fixes: 1. Windows launcher now merges extraEnv into spawned server env (was only passing BROWSE_STATE_FILE, dropping all other env vars) 2. Welcome page fallback serves inline HTML instead of about:blank redirect (avoids ERR_UNSAFE_REDIRECT on Windows) 3. /health returns auth token in headed mode even without Origin header (fixes Playwright Chromium extensions that don't send it) Also adds HOME/USERPROFILE fallback for cross-platform compatibility. Co-Authored-By: pieterklue <pieterklue@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(browse): terminate orphan server when parent process exits (#808) Community PR #808 by @mmporong. Passes BROWSE_PARENT_PID to the spawned server process. The server polls every 15s with signal 0 and calls shutdown() if the parent is gone. Prevents orphaned chrome-headless-shell processes when Claude Code sessions exit abnormally. Co-Authored-By: mmporong <mmporong@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): IPv6 ULA blocking, cookie redaction, per-tab cancel, targeted token (#664) Community PR #664 by @mr-k-man (security audit round 1, new parts only). - IPv6 ULA prefix blocking (fc00::/7) in url-validation.ts with false-positive guard for hostnames like fd.example.com - Cookie value redaction for tokens, API keys, JWTs in browse cookies command - Per-tab cancel files in killAgent() replacing broken global kill-signal - design/serve.ts: realpathSync upgrade prevents symlink bypass in /api/reload - extension: targeted getToken handler replaces token-in-health-broadcast - Supabase migration 003: column-level GRANT restricts anon UPDATE scope - Telemetry sync: upsert error logging - 10 new tests for IPv6, cookie redaction, DNS rebinding, path traversal Co-Authored-By: mr-k-man <mr-k-man@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): CSS injection guard, timeout clamping, session validation, tests (#806) Community PR #806 by @mr-k-man (security audit round 2, new parts only). - CSS value validation (DANGEROUS_CSS) in cdp-inspector, write-commands, extension inspector - Queue file permissions (0o700/0o600) in cli, server, sidebar-agent - escapeRegExp for frame --url ReDoS fix - Responsive screenshot path validation with validateOutputPath - State load cookie filtering (reject localhost/.internal/metadata cookies) - Session ID format validation in loadSession - /health endpoint: remove currentUrl and currentMessage fields - QueueEntry interface + isValidQueueEntry validator for sidebar-agent - SIGTERM->SIGKILL escalation in timeout handler - Viewport dimension clamping (1-16384), wait timeout clamping (1s-300s) - Cookie domain validation in cookie-import and cookie-import-browser - DocumentFragment-based tab switching (XSS fix in sidepanel) - pollInProgress reentrancy guard for pollChat - toggleClass/injectCSS input validation in extension inspector - Snapshot annotated path validation with realpathSync - 714-line security-audit-r2.test.ts + 33-line learnings-injection.test.ts Co-Authored-By: mr-k-man <mr-k-man@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.15.13.0) Community security wave: 8 PRs from 4 contributors (@garagon, @mr-k-man, @mmporong, @0531Kim, @pieterklue). IPv6 ULA blocking, cookie redaction, per-tab cancel signaling, CSS injection guards, timeout clamping, session validation, DocumentFragment XSS fix, parent process watchdog, uninstall docs, Windows fixes, and 750+ lines of security regression tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: garagon <garagon@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: 0531Kim <0531Kim@users.noreply.github.com> Co-authored-by: pieterklue <pieterklue@users.noreply.github.com> Co-authored-by: mmporong <mmporong@users.noreply.github.com> Co-authored-by: mr-k-man <mr-k-man@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second security audit of gstack's browse subsystem, Chrome extension, and CLI scripts — all findings beyond the 10 already reported in PR #664 and issues #665-#675.
Findings by severity
How we found them
Cross-validation with other community reports
7 of our original 10 findings from PR #664 were independently rediscovered by stedfn (#712-#717) and Gonzih (#741-#758). Several of these round 2 findings overlap with seanomich's audit (#783) but target different specific vectors.
Test results
Security regression tests (119/119 pass)
```
119 pass, 0 fail, 182 expect() calls
Ran 119 tests across 5 files [47ms]
```
E2E evals (33 pass, 0 regressions)
Ran in `gstack-ci` Docker image (Ubuntu 24.04 + Chromium 145/Playwright 1.58.2 + `--cap-add SYS_ADMIN`):
All previously-failing browse tests now pass:
1 unrelated failure (`qa-bootstrap` — test infrastructure, not our code).
Test plan