Skip to content

Ghostex hardening pass: security remediation, macOS issue fixes, and five additive hardening features - #62

Closed
pt-act wants to merge 21 commits into
maddada:mainfrom
rial1:main
Closed

Ghostex hardening pass: security remediation, macOS issue fixes, and five additive hardening features#62
pt-act wants to merge 21 commits into
maddada:mainfrom
rial1:main

Conversation

@pt-act

@pt-act pt-act commented Jul 14, 2026

Copy link
Copy Markdown

Summary

This PR bundles a full defensive-security pass over gxserver-rs and the native surface, fixes for reported macOS issues, and five additive hardening features that each retire a root cause of a failure this project actually hit. Everything is additive — no existing happy-path behavior changes — and ships as independent, reviewable slices.

Work is grouped below by theme. All Rust changes are in gxserver-rs; native/frontend changes are in native/sidebar, sidebar, and shared. Documentation for every item lives under contributions/.


1. Security audit remediation (gxserver-rs)

Verified all six findings plus the additional note from the initial audit against the source (every finding reproduced), and fixed the two actionable code findings.

  • [High] Clone subprocess environment (repository_clone.rs): replaced broad ambient-environment inheritance — where env_clear() was effectively a no-op because everything but three color vars was re-added — with an explicit allowlist (PATH, HOME, SSH agent, locale/LC_*, proxy, temp, Windows essentials). Tokens, GIT_*/GIT_SSH_COMMAND overrides, LD_PRELOAD, etc. are dropped. Added tests for the allowlist and ambient-secret non-leakage.
  • [Medium] T3 runtime bind (t3_runtime.rs): T3_RUNTIME_LISTEN_HOST changed from 0.0.0.0 to 127.0.0.1. The runtime is only ever reached over loopback, so this removes off-host/container reachability with no behavior change.
  • Findings 3 (/api/events query token), 4 (shell setup command), 5 & 6 (declarative remote/admin surface) were verified as intentional design or informational parity gaps and documented rather than changed.

Detail: contributions/security-audit-verification-and-remediation.md

2. Reported macOS issue fixes

  • Skills/Desktop Control always show "Not installed": installer writes ~/.agents/ but status checker reads ~/agents/ (no dot) #58 — Skills / Desktop Control stuck on "Not installed" (native/sidebar/native-sidebar.tsx): skills/hooks install under ~/.agents/… but the native status checker and the bundled-skills uninstaller read ~/agents/… (no dot), so badges never updated and Uninstall Skills removed nothing. Pointed the status checks, uninstaller, and shared-hooks catalog at the canonical dotted location. (Requires a native-sidebar rebuild; the shipped native-sidebar.js is an untracked build artifact.)
  • Fix SSH connections to another user account on the same Mac #61 — SSH to another macOS account on the same Mac (RemoteGxserverClient.swift): investigated the native remote-connect flow. Root cause: key-only machines force BatchMode=yes, blocking the interactive password auth a terminal uses for cross-account local SSH. Made failures actionable — sanitizedProcessFailure now maps ssh stderr to specific guidance (host-key change/verification, permission-denied → save the machine's SSH password or add an accepted key, connection-refused → enable Remote Login, DNS, unreachable, timeout). String-only change; the intentional BatchMode fast-fail is left as-is pending a maintainer decision.

Detail: contributions/issue-61-ssh-investigation.md

3. Investigations documented (no code change)

Detail: contributions/issue-52-libxev-kqueue-upstream-report.md, contributions/issue-38-menubar-agent-design-decisions.md

4. Five additive hardening features

Each addresses a concrete failure from the engagement. All independently shippable.

  • Feature 1 — ghostex doctor (doctor.rs, server.rs, sidebar/settings-modal.tsx, native/sidebar/modal-host.tsx): five read-only checks (skills, hooks, toolchain, daemon, T3), an invariant validator, and a CLI with --json that runs in-process even when the daemon is down. POST /api/doctor/run and POST /api/doctor/fix (FullLocal, auth-required); a Support tab with Run Doctor / Copy Diagnostics and a fix-confirmation flow.
  • Feature 2 — Shared path source of truth (paths.rs AgentPaths, bin/generate-agent-paths.rs, scripts/generate-agent-paths.mjs, shared/agent-paths.generated.ts): one canonical Rust definition of agent paths, code-generated into checked-in TS constants consumed by the native sidebar; skills/hooks installers migrated to it. A drift test asserts the generated TS matches the Rust source. Structurally retires the Skills/Desktop Control always show "Not installed": installer writes ~/.agents/ but status checker reads ~/agents/ (no dot) #58 path-mismatch bug class.
  • Feature 3 — Conformance test + capability discovery (shared/gxserver-protocol-conformance.test.ts, protocol.rs, /api/capabilities): CI test asserting set-equality (both directions) between the shared TS contract and the Rust routing table; a RemoteAllowed /api/capabilities endpoint returning a path → implemented|notImplemented map so clients can gate UI instead of rendering dead buttons. Directly addresses audit findings 5/6.
  • Feature 4 — Diagnostic bundle export (handle_export_diagnostics_http): user-triggered "Copy Diagnostics" producing a redacted JSON bundle (version, config summary via export-time allowlist, last 50 error-level log entries, T3 status, skills summary). FullLocal, auth-required.
  • Feature 5 — Unified subprocess policy (subprocess_policy.rs): SubprocessProfile with explicit per-use env allowlists plus a write_secret_file (0600) helper; repository_clone.rs migrated onto it. T3 env restriction deliberately deferred (it relies on the zsh -lic login-shell env).

Detail: contributions/ghostex-hardening-features.md

5. CodeQL alert fixes

Resolved 3 CodeQL alerts in app code (native/sidebar/manage.tsx, native/sidebar/meo/helpers/images.ts, sidebar/session-card-content.tsx): escape backslashes before quotes in markdown quoting; validate URL protocol (http:/https:/data:image/) before assigning img.src; require a data:image/ prefix on favicon data URLs. Skips (vendored/test/dev) documented in codeql.txt.

6. Follow-up hardening-pass audit fixes

A second security audit of the implemented features surfaced two defects, both fixed here:

  • [Medium-High] SSH_AUTH_SOCK was stripped from clone subprocesses: the subprocess-policy sensitive-key filter matched the substring AUTH, dropping SSH_AUTH_SOCK even though it is in the clone allowlist — silently breaking ssh-agent auth for git@ clones. The allowlist now takes precedence over the sensitive filter, with a regression test asserting the socket survives subprocess_environment(Clone).
  • [Low-Medium] Hooks catalog path drift: the native agents-hub catalog still hardcoded ~/.agents/hooks while the canonical installer path is ~/.ghostex/hooks. Migrated those references to the generated HOOKS_ROOT constant and added a guard against hardcoded agent-path literals.

Detail: contributions/Ghostex-Security-Audit-Hardening-Pass.md


Validation

  • Rust: cargo build clean; cargo test -p gxserver green (482 tests; +2 SSH_AUTH_SOCK regression tests from the follow-up fix), including the clone_environment_*, subprocess_policy, doctor, and AgentPaths drift tests.
  • TypeScript: protocol conformance tests pass.
  • Swift: ghostexHost change is a string-only edit to one function; swiftc -parse clean (full Xcode build to be run by a maintainer).
  • One pre-existing test (agent_hooks::…install_writes_notify_hook_without_payload_content) is environment-coupled (fails only when qodercli is installed on the build machine); unrelated to these changes.

Notes for reviewers

Out of scope / future work

Commits

Summary by CodeRabbit

  • New Features
    • Added ghostex doctor health checks with optional automated fixes, plus doctor-related local API endpoints.
    • Added diagnostics export (with redacted config + runtime/log snapshots).
    • Added /api/capabilities to enumerate supported features.
    • Added a doctor diagnostics flow to the sidebar and centralized agent path constants for the UI.
  • Bug Fixes
    • Fixed shared skills/hooks path handling to keep installs and status badges consistent.
    • Hardened subprocess environment handling for Git clones to reduce credential/environment leakage.
    • Restricted T3 runtime listener to loopback; tightened favicon and Markdown quote escaping behavior.
  • Tests / Verification
    • Added protocol conformance checks to keep endpoint catalogs in sync.
  • Documentation
    • Added security audit/hardening, verification, and issue/decision documentation; updated contributions changelog.
  • Chores
    • Added a macOS CI workflow producing an ad-hoc DMG artifact.

Ghostex Security Remediation added 12 commits July 14, 2026 09:50
Address two verified backend security-audit findings in gxserver-rs:

- Repository clone (High): replace broad ambient-environment
  inheritance with an explicit allowlist so env_clear() is meaningful.
  The clone subprocess now receives only Git-essential variables
  (PATH, HOME, SSH agent, locale/LC_*, proxy, temp, Windows essentials)
  and drops tokens, GIT_*/GIT_SSH_COMMAND overrides, LD_PRELOAD, etc.
  Adds tests covering the allowlist and ambient-secret non-leakage.

- T3 runtime (Medium): bind the managed runtime to 127.0.0.1 instead
  of 0.0.0.0. It is only ever reached over loopback, so this removes
  off-host/container reachability with no behavior change.
Skills, hooks, and Desktop Control installed under ~/.agents/... (the path
gxserver-rs agent_skills.rs writes to), but the native status checker and
the bundled-skills uninstaller read ~/agents/... (no dot). Nothing ever
created the no-dot directory, so every skill/Desktop Control badge stayed
'Not installed' after a successful install and Uninstall Skills silently
removed nothing.

Point the status checks, the uninstaller, and the shared-hooks catalog
root (plus its profile attribution) at the canonical dotted ~/.agents
location. The agents-hub catalog keeps its existing legacy no-dot skills
group for backward compatibility.

Fixes maddada#58
Remote connect runs SSH non-interactively from the GUI helper, so failures
were surfaced as a generic 'SSH authentication failed'. Map stderr to
specific, actionable causes: host-key changes, host-key verification,
permission-denied (guides users connecting to another local account to save
that machine's SSH password or add an accepted key), connection refused
(Remote Login disabled), name-resolution, unreachable network, and timeout.

String-only change to sanitizedProcessFailure; no auth-policy or control-flow
change. The key-only BatchMode fast-fail behavior is intentional and left as-is
pending a maintainer decision (see investigation notes).

Refs maddada#61
…addada#38 design)

Add a contributions/ folder documenting our work on this tree:
- CHANGELOG.md (our contributions only; separate from root CHANGELOG.md)
- security-audit-verification-and-remediation.md
- issue-58 covered in changelog; issue-61 SSH investigation
- issue-52 libxev kqueue upstream bug report
- issue-38 menu-bar agent design decisions
Implement system health checker (Doctor) and diagnostic bundle exporter:

Backend (gxserver-rs):
- POST /api/doctor/run — five health checks (skills, hooks, toolchain, daemon, T3)
- POST /api/doctor/fix — validated fix application with confirmation tokens
- POST /api/doctor/exportDiagnostics — structured diagnostics bundle
- DoctorCheck/DoctorFix structs with serde camelCase serialization
- validate_check_invariants enforces Ok checks have no fix
- CLI subcommand with --json support
- 480 Rust tests passing

Frontend (sidebar):
- SupportSettingsTab in Settings modal (Support page, IconStethoscope)
- Run Doctor button with loading state
- Copy Diagnostics button (clipboard-first, native fallback)
- Check results with status icons (ok/warn/fail)
- Two-step fix confirmation flow
- Type guards and state management in modal-host
- RPC handlers in native-sidebar bridge
- 4 TypeScript conformance tests passing

Other:
- Agent paths codegen (generate-agent-paths.mjs + generated TS)
- Subprocess policy module for clone environment hardening
- Spec document in contributions/
…ghostex-hardening

Replaced implementation-detail-heavy doc with a feature-focused summary
that references the canonical spec at spec/ghostex-hardening/ without
exposing internal frameworks or workflow details.
Five features from spec/ghostex-hardening: doctor, shared paths,
CI conformance, diagnostic bundle, subprocess policy.
Resolves 5 Dependabot alerts in tui2/website:
- GHSA-j687-52p2-xcff (XSS in define:vars)
- GHSA-xr5h-phrj-8vxv (server island replay)
- GHSA-8hv8-536x-4wqp (reflected XSS slot name)
- GHSA-jrpj-wcv7-9fh9 (XSS spread props)
- GHSA-2pvr-wf23-7pc7 (host header SSRF)

astro <=7.0.0-beta.6 is vulnerable; 7.0.9 fixes all five.
starlight 0.41.3 requires astro ^7.0.2.
- maddada#33 (High): manage.tsx formatMarkdownQuote now escapes backslashes
  before double quotes, preventing backslash injection
- maddada#37/maddada#35 (High/Med): session-card-content.tsx validates faviconDataUrl
  starts with 'data:image/' before setting img src
- maddada#36/maddada#34 (High/Med): images.ts validates URL protocol (http/https/data:image)
  before assigning to img.src, blocking javascript: URLs

Skipped: vendored code (simdutf, stb, glib), test-only regex,
dev script spawnSync. Documented in codeql.txt.
…es HOOKS_ROOT

- subprocess_policy.rs: allowlisted keys (SSH_AUTH_SOCK, SSH_AGENT_PID)
  now bypass the SENSITIVE_KEY_PATTERNS filter.  Two-pass filter preserved
  so only allowlisted+LC_* keys enter the second gate; allowlisted keys
  then skip the sensitive check.  Fixes SSH clone regression (Finding 1).
- native-sidebar.tsx: replace hardcoded p(".agents","hooks") with
  p(HOOKS_ROOT) to match canonical .ghostex/hooks path (Finding 2).
- Add regression tests: subprocess_environment(Clone|Ssh) must yield
  SSH_AUTH_SOCK when set in parent environment.
- Widen sensitive_vars_absent_from_all_profiles to exclude allowlisted
  keys from the sensitivity check (they are intentionally preserved).
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds ghostex doctor CLI and HTTP health checks with diagnostics export, shared agent-path generation via Rust code-gen into TypeScript, endpoint conformance testing between Rust and TypeScript routing tables, centralized subprocess environment policies with security hardening, T3 runtime loopback binding, comprehensive audit and remediation documentation, native path and content fixes, and a macOS x86_64 CI build workflow.

Changes

Ghostex hardening features

Layer / File(s) Summary
Doctor checks and CLI
gxserver-rs/src/lib.rs, gxserver-rs/src/cli.rs, gxserver-rs/src/doctor.rs
Adds serializable doctor status enums and check/fix data types; implements five health checks (skills, hooks, toolchain, daemon, T3 runtime) with optional repair payloads; validates invariant that Ok checks cannot carry fixes; CLI outputs human-readable or JSON format.
Protocol contracts and capabilities
shared/gxserver-protocol.ts, shared/gxserver-protocol-conformance.test.ts, gxserver-rs/src/protocol.rs
Extends endpoint type union with /api/capabilities, /api/doctor/run, /api/doctor/fix, /api/doctor/exportDiagnostics; adds canonical endpoint lists in Rust (all_ts_endpoint_paths, known_not_implemented_endpoints); adds diagnostic config allowlist; implements bidirectional Rust/TypeScript conformance tests.
Doctor HTTP and sidebar contracts
gxserver-rs/src/server.rs, shared/session-grid-contract-sidebar.ts, sidebar/settings-modal.tsx
Adds HTTP handlers for diagnostics export (config summary, error logs, T3/skills status), doctor check execution, and validated fix application (skills/hooks reinstall); extends sidebar bridge with doctor result and command message types; imports toast notifications.
Shared agent path generation
gxserver-rs/src/paths.rs, gxserver-rs/src/bin/generate-agent-paths.rs, scripts/generate-agent-paths.mjs, gxserver-rs/src/agent_hooks.rs, gxserver-rs/src/agent_skills.rs, native/sidebar/native-sidebar.tsx
Rust AgentPaths struct derives agent-related root directories and emits relative component strings; binary generator outputs JSON; Node script transforms to TypeScript constants; updates hook/skill discovery paths and native sidebar skill detection, uninstall guidance, and routing to use generated constants.
Subprocess environment and runtime binding
gxserver-rs/src/subprocess_policy.rs, gxserver-rs/src/repository_clone.rs, gxserver-rs/src/t3_runtime.rs
Introduces SubprocessProfile enum with per-profile environment allowlists (Clone, Ssh, ProjectSetup, T3Runtime); global sensitive-key filtering; write_secret_file with Unix 0o600 permissions; migrates clone subprocess from local allowlist to centralized policy with comprehensive tests; changes T3 listener host from 0.0.0.0 to 127.0.0.1 with documented loopback-only invariant.

Security audit, issue investigations, and ancillary updates

Layer / File(s) Summary
Documentation and security remediation
contributions/CHANGELOG.md, contributions/ghostex-hardening-features.md, contributions/Ghostex-Security-Audit-Hardening-Pass.md, contributions/security-audit-verification-and-remediation.md, contributions/issue-38-menubar-agent-design-decisions.md, contributions/issue-52-libxev-kqueue-upstream-report.md, contributions/issue-61-ssh-investigation.md, .gitignore, native/sidebar/manage.tsx, native/sidebar/meo/helpers/images.ts, sidebar/session-card-content.tsx, tui2/website/package.json
Adds five hardening feature specifications with validation results; documents security audit scope/findings (clone env SSH_AUTH_SOCK regression, hooks path drift, token theater, dead subprocess profiles, secret-file atomicity, capability advertisement gap, diagnostic username leak); records completed remediations (clone allowlist + tests, T3 loopback binding); lists eight audit findings with remediation status; design decisions for Issue #38 menubar agent; upstream libxev kqueue crash report for Issue #52; SSH failure investigation for Issue #61; ignores local spec/ directory; escapes backslashes in Markdown quote handling; restricts image/favicon rendering to approved URL schemes; updates Astro website dependencies.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SupportSettings
  participant SidebarBridge
  participant gxserver
  participant doctor
  SupportSettings->>SidebarBridge: runDoctor
  SidebarBridge->>gxserver: /api/doctor/run
  gxserver->>doctor: run_all_checks
  doctor-->>gxserver: DoctorCheck list
  gxserver-->>SidebarBridge: checks result
  SidebarBridge-->>SupportSettings: display results
  SupportSettings->>SidebarBridge: exportDiagnostics
  SidebarBridge->>gxserver: /api/doctor/exportDiagnostics
  gxserver-->>SidebarBridge: diagnostics bundle
Loading

Possibly related PRs

  • maddada/Ghostex#47: Related agent skills/hooks paths and gxserver endpoint routing work.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR’s main theme: Ghostex hardening with security fixes, macOS fixes, and added hardening features.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
native/sidebar/meo/helpers/images.ts (1)

251-257: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Support uppercase schemes and render a fallback for rejected URLs.

The current check is case-sensitive, meaning valid image URLs like HTTP://... will be rejected. Additionally, when a URL is rejected, img.src is skipped but the widget never calls fail(), resulting in a blank space instead of the proper text fallback (![alt](url)).

Convert the scheme check to be case-insensitive, and call fail() when the URL is rejected.

🐛 Proposed fix
-      if (src.startsWith("http:") || src.startsWith("https:") || src.startsWith("data:image/")) {
-        img.src = src;
-      }
-      if (img.complete) {
-        hideLoadingPlaceholder();
-      }
+      const lowerSrc = src.toLowerCase();
+      if (lowerSrc.startsWith("http:") || lowerSrc.startsWith("https:") || lowerSrc.startsWith("data:image/")) {
+        img.src = src;
+        if (img.complete) {
+          hideLoadingPlaceholder();
+        }
+      } else {
+        fail();
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/sidebar/meo/helpers/images.ts` around lines 251 - 257, Update the
image source validation in the visible image-loading callback to check HTTP,
HTTPS, and data-image schemes case-insensitively, while preserving accepted URLs
for img.src. When validation rejects src, call the existing fail() handler so
the text fallback renders instead of leaving a blank widget.
gxserver-rs/src/protocol.rs (1)

169-173: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add missing /api/t3Runtime/* paths to GxserverEndpointPath.

The t3Runtime paths (/api/t3Runtime/status, start, stop, panes) were added to the EXPECTED_ENDPOINTS array in shared/gxserver-protocol-conformance.test.ts, which uses satisfies readonly GxserverEndpointPath[]. Because these paths are missing from the GxserverEndpointPath union, this will cause a TypeScript compilation error.

🐛 Proposed fix
   | "/api/installTool"
   | "/api/browseFilesystem"
   | "/api/destructiveAdminAction"
+  | "/api/t3Runtime/status"
+  | "/api/t3Runtime/start"
+  | "/api/t3Runtime/stop"
+  | "/api/t3Runtime/panes"
   | "/api/capabilities"
   | "/api/doctor/exportDiagnostics"
   | "/api/doctor/fix"
   | "/api/doctor/run";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gxserver-rs/src/protocol.rs` around lines 169 - 173, Extend the
GxserverEndpointPath union with the four missing t3Runtime endpoints:
/api/t3Runtime/status, /api/t3Runtime/start, /api/t3Runtime/stop, and
/api/t3Runtime/panes. Preserve all existing endpoint variants and ensure
EXPECTED_ENDPOINTS satisfies the updated type.
🧹 Nitpick comments (4)
scripts/generate-agent-paths.mjs (1)

49-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify snakeToConstant logic.

Splitting the string by _ and uppercasing each part produces the exact same result as calling .toUpperCase() on the entire string, since underscores are unchanged by case transformations.

♻️ Proposed refactor
 function snakeToConstant(value) {
-  return value
-    .split("_")
-    .map((part) => part.toUpperCase())
-    .join("_");
+  return value.toUpperCase();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-agent-paths.mjs` around lines 49 - 54, Streamline the
snakeToConstant function by removing the split/map/join pipeline and returning
the input value transformed with a single toUpperCase() call. Preserve the
existing output for underscore-separated strings.
gxserver-rs/src/paths.rs (1)

170-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant string replacement.

The replace("_ROOT", "_ROOT") operation has no effect and can be safely removed.

♻️ Proposed refactor
-            let ts_constant_name = name.to_ascii_uppercase().replace("_ROOT", "_ROOT");
+            let ts_constant_name = name.to_ascii_uppercase();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gxserver-rs/src/paths.rs` at line 170, In the code producing
ts_constant_name, remove the no-op replace("_ROOT", "_ROOT") call and retain the
uppercase conversion via name.to_ascii_uppercase().
shared/gxserver-protocol-conformance.test.ts (1)

123-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

satisfies does not enforce exhaustiveness.

The inline comment claims that the satisfies operator enforces that the array covers the union type at compile time. However, satisfies readonly GxserverEndpointPath[] only ensures that the array elements are a valid subset of the union (preventing extraneous entries), not that they are exhaustive. The runtime check for > 80 also does not catch specific subtraction drift.

To actually enforce exhaustiveness statically and prevent drift, consider mapping the union to a Record where TypeScript requires every key to be present.

🛠️ Recommended refactor for compile-time exhaustiveness
const EXPECTED_ENDPOINTS_RECORD: Record<GxserverEndpointPath, true> = {
  "/api/health": true,
  "/api/health/server": true,
  // ... map all paths to true
};

const EXPECTED_ENDPOINTS = Object.keys(EXPECTED_ENDPOINTS_RECORD) as GxserverEndpointPath[];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/gxserver-protocol-conformance.test.ts` around lines 123 - 131, Replace
the subset-only EXPECTED_ENDPOINTS satisfies check with an exhaustive
Record<GxserverEndpointPath, true> keyed by every endpoint variant, then derive
EXPECTED_ENDPOINTS from that record. Update the test comment and runtime
assertion to reflect the record-based compile-time coverage, preserving the
existing endpoint list and drift check.
native/sidebar/native-sidebar.tsx (1)

22292-22359: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Doctor/Diagnostics feature is missing the required CDXC documentation comments. As per coding guidelines, **/*.{ts,tsx,js,jsx,zig} changes must "Add CDX_LOG comments describing the date of the change (format: yyyy-MM-dd-hh:mm) and the requirements... Prefix all comments with CDXC:Area-of-product" and "Ensure ALL important user-facing requirements sent by the user are written as comments somewhere in the codebase." None of the new Doctor/Diagnostics code across these three files carries such a comment, unlike every other feature touched in this same PR (e.g. CDXC:AppIconPicker, CDXC:DebuggingSettings).

  • native/sidebar/native-sidebar.tsx#L22292-L22359: add a CDXC:Doctor <date> comment above runDoctorChecks/applyDoctorFix/exportDiagnosticsJson describing the doctor/fix/export requirement.
  • native/sidebar/modal-host.tsx#L698-L2728: add a matching CDXC:Doctor <date> comment near the new doctorChecks/doctorLoading/diagnosticsJson/diagnosticsLoading state and the doctorChecksResult/doctorFixResult/diagnosticsExportResult handling.
  • sidebar/settings-modal.tsx#L11185-L11326: add a CDXC:Doctor <date> comment above SupportSettingsTab describing the Support tab's diagnostics requirement.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/sidebar/native-sidebar.tsx` around lines 22292 - 22359, Add required
CDXC:Doctor documentation comments with the change date in yyyy-MM-dd-hh:mm
format and the relevant user-facing doctor, fix, and diagnostics-export
requirements: above runDoctorChecks/applyDoctorFix/exportDiagnosticsJson in
native/sidebar/native-sidebar.tsx (lines 22292-22359), near the
doctorChecks/doctorLoading/diagnosticsJson/diagnosticsLoading state and result
handling in native/sidebar/modal-host.tsx (lines 698-2728), and above
SupportSettingsTab in sidebar/settings-modal.tsx (lines 11185-11326).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gxserver-rs/src/doctor.rs`:
- Around line 167-195: Update check_daemon to preserve the fetched
ServerHealthResponse.t3_runtime data and expose it to run_doctor_cli. In
run_doctor_cli, derive T3RuntimeStatusPayload from that returned t3_runtime when
available, falling back to the existing non-running placeholder only when daemon
health is unavailable; keep the current fallback behavior for fetch failures or
an absent health response.

In `@gxserver-rs/src/protocol.rs`:
- Around line 663-700: Update
every_endpoint_for_path_is_implemented_or_known_not_implemented to also verify
the reverse subset: each path in known (or separately in
ALL_IMPLEMENTED_ENDPOINTS and known_not_implemented_endpoints()) must exist in
all_ts_endpoint_paths. Preserve the existing forward checks and ensure
extraneous implemented or known-not-implemented paths cause the test to fail.

In `@gxserver-rs/src/server.rs`:
- Around line 1521-1580: Update the fix validation match in
handle_doctor_fix_http to accept the toolchain.install and install-tools
ID/token pair emitted by check_toolchain, and invoke the existing toolchain
installation routine using state.paths, returning applied: true on success.
Preserve the current skills.reinstall and hooks.reinstall behavior; centralize
identifiers only if an existing shared registry is available.

In `@gxserver-rs/src/subprocess_policy.rs`:
- Around line 181-188: Update the Unix file creation block in the subprocess
policy path to use OpenOptions with OpenOptionsExt, setting
create/write/truncate behavior and mode 0o600 atomically. Remove the separate
set_permissions call while preserving the existing write_all, sync_all, and
error propagation flow.

In `@gxserver-rs/src/t3_runtime.rs`:
- Around line 20-25: Update the macOS wrapper’s listenHost configuration in
NativeT3CodePaneReproLog.swift from 0.0.0.0 to 127.0.0.1, matching the
T3_RUNTIME_LISTEN_HOST loopback binding and keeping native-wrapper launches
inaccessible from external interfaces.

In `@native/sidebar/modal-host.tsx`:
- Around line 2566-2575: Update the successful fix handling in the
isDoctorFixResultMessage branch to dispatch the runDoctor message through the
existing vscode.postMessage SidebarToExtensionMessage channel instead of
postAppModalHostMessage. Preserve setting doctorLoading to true and ensure
handleSidebarMessage receives the message so runDoctorChecks() executes and
clears the loading state.

In `@native/sidebar/native-sidebar.tsx`:
- Around line 22598-22611: The generated Node scripts passed to
runNativeNodeScript reference SKILLS_ROOT and HOOKS_ROOT as undeclared bare
identifiers, causing runtime failures. Update the relevant String.raw script
blocks to interpolate these constants as ${SKILLS_ROOT} and ${HOOKS_ROOT},
including the skill status and catalog path checks, while preserving the
existing path construction.
- Around line 39594-39599: Update the generated Agents Hub catalog script’s
profilesFor() logic and hooksRoot reference to interpolate the HOOKS_ROOT value
rather than treating HOOKS_ROOT as a runtime variable. Ensure both generated
references use ${HOOKS_ROOT}, preventing a ReferenceError during catalog
generation while preserving the existing path checks and linkedProfiles
behavior.

In `@shared/gxserver-protocol.ts`:
- Around line 169-173: Extend the GxserverEndpointPath union with the four
t3Runtime endpoints: /api/t3Runtime/status, /api/t3Runtime/start,
/api/t3Runtime/stop, and /api/t3Runtime/panes, so EXPECTED_ENDPOINTS satisfies
the declared type.

---

Outside diff comments:
In `@gxserver-rs/src/protocol.rs`:
- Around line 169-173: Extend the GxserverEndpointPath union with the four
missing t3Runtime endpoints: /api/t3Runtime/status, /api/t3Runtime/start,
/api/t3Runtime/stop, and /api/t3Runtime/panes. Preserve all existing endpoint
variants and ensure EXPECTED_ENDPOINTS satisfies the updated type.

In `@native/sidebar/meo/helpers/images.ts`:
- Around line 251-257: Update the image source validation in the visible
image-loading callback to check HTTP, HTTPS, and data-image schemes
case-insensitively, while preserving accepted URLs for img.src. When validation
rejects src, call the existing fail() handler so the text fallback renders
instead of leaving a blank widget.

---

Nitpick comments:
In `@gxserver-rs/src/paths.rs`:
- Line 170: In the code producing ts_constant_name, remove the no-op
replace("_ROOT", "_ROOT") call and retain the uppercase conversion via
name.to_ascii_uppercase().

In `@native/sidebar/native-sidebar.tsx`:
- Around line 22292-22359: Add required CDXC:Doctor documentation comments with
the change date in yyyy-MM-dd-hh:mm format and the relevant user-facing doctor,
fix, and diagnostics-export requirements: above
runDoctorChecks/applyDoctorFix/exportDiagnosticsJson in
native/sidebar/native-sidebar.tsx (lines 22292-22359), near the
doctorChecks/doctorLoading/diagnosticsJson/diagnosticsLoading state and result
handling in native/sidebar/modal-host.tsx (lines 698-2728), and above
SupportSettingsTab in sidebar/settings-modal.tsx (lines 11185-11326).

In `@scripts/generate-agent-paths.mjs`:
- Around line 49-54: Streamline the snakeToConstant function by removing the
split/map/join pipeline and returning the input value transformed with a single
toUpperCase() call. Preserve the existing output for underscore-separated
strings.

In `@shared/gxserver-protocol-conformance.test.ts`:
- Around line 123-131: Replace the subset-only EXPECTED_ENDPOINTS satisfies
check with an exhaustive Record<GxserverEndpointPath, true> keyed by every
endpoint variant, then derive EXPECTED_ENDPOINTS from that record. Update the
test comment and runtime assertion to reflect the record-based compile-time
coverage, preserving the existing endpoint list and drift check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f6b7522-ffc7-460f-96e0-b177bb67c260

📥 Commits

Reviewing files that changed from the base of the PR and between 5c73715 and a6c28ed.

⛔ Files ignored due to path filters (2)
  • shared/agent-paths.generated.ts is excluded by !**/*.generated.*
  • tui2/website/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (33)
  • .gitignore
  • contributions/CHANGELOG.md
  • contributions/Ghostex-Security-Audit-Hardening-Pass.md
  • contributions/ghostex-hardening-features.md
  • contributions/issue-38-menubar-agent-design-decisions.md
  • contributions/issue-52-libxev-kqueue-upstream-report.md
  • contributions/issue-61-ssh-investigation.md
  • contributions/security-audit-verification-and-remediation.md
  • gxserver-rs/src/agent_hooks.rs
  • gxserver-rs/src/agent_skills.rs
  • gxserver-rs/src/bin/generate-agent-paths.rs
  • gxserver-rs/src/cli.rs
  • gxserver-rs/src/doctor.rs
  • gxserver-rs/src/lib.rs
  • gxserver-rs/src/paths.rs
  • gxserver-rs/src/protocol.rs
  • gxserver-rs/src/repository_clone.rs
  • gxserver-rs/src/server.rs
  • gxserver-rs/src/subprocess_policy.rs
  • gxserver-rs/src/t3_runtime.rs
  • native/macos/ghostexHost/Sources/ghostexHost/RemoteGxserverClient.swift
  • native/sidebar/manage.tsx
  • native/sidebar/meo/helpers/images.ts
  • native/sidebar/modal-host.tsx
  • native/sidebar/native-sidebar.tsx
  • scripts/generate-agent-paths.mjs
  • shared/ghostex-settings.ts
  • shared/gxserver-protocol-conformance.test.ts
  • shared/gxserver-protocol.ts
  • shared/session-grid-contract-sidebar.ts
  • sidebar/session-card-content.tsx
  • sidebar/settings-modal.tsx
  • tui2/website/package.json

Comment thread gxserver-rs/src/doctor.rs
Comment on lines +663 to 700
fn every_endpoint_for_path_is_implemented_or_known_not_implemented() {
// Set-equality direction: Rust routing table ⊆ (implemented ∪ known-not-implemented).
// Every path in endpoint_for() must either have a real handler or
// be in known_not_implemented_endpoints().
let not_impl: HashSet<&str> =
known_not_implemented_endpoints().iter().copied().collect();
let implemented: HashSet<&str> = ALL_IMPLEMENTED_ENDPOINTS
.iter()
.copied()
.collect();
let mut known: HashSet<&str> = HashSet::new();
known.extend(&not_impl);
known.extend(&implemented);

for path in all_ts_endpoint_paths() {
let desc = endpoint_for(path);
assert!(
desc.is_some(),
"Path {} is in TS contract but not in endpoint_for()",
path,
);
assert!(
known.contains(path),
"Path {} is in endpoint_for() but has neither a handler nor is in known_not_implemented_endpoints()",
path,
);
}

// Also verify that known_not_implemented paths really resolve in endpoint_for().
for path in &not_impl {
assert!(
endpoint_for(path).is_some(),
"Known-not-implemented path {} is not in endpoint_for()",
path,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the reverse subset direction for true equality.

The ALL_IMPLEMENTED_ENDPOINTS doc comment claims that this test asserts this set + known_not_implemented == all_ts_endpoint_paths. However, the loop only verifies that all_ts_endpoint_paths is a subset of known. If ALL_IMPLEMENTED_ENDPOINTS contains extraneous paths that are missing from all_ts_endpoint_paths, this test will silently pass.

✅ Proposed fix to assert full equality
         for path in all_ts_endpoint_paths() {
             let desc = endpoint_for(path);
             assert!(
                 desc.is_some(),
                 "Path {} is in TS contract but not in endpoint_for()",
                 path,
             );
             assert!(
                 known.contains(path),
                 "Path {} is in endpoint_for() but has neither a handler nor is in known_not_implemented_endpoints()",
                 path,
             );
         }
+
+        for path in known.iter() {
+            assert!(
+                all_ts_endpoint_paths().contains(path),
+                "Path {} is in known/implemented endpoints but missing from all_ts_endpoint_paths()",
+                path,
+            );
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn every_endpoint_for_path_is_implemented_or_known_not_implemented() {
// Set-equality direction: Rust routing table ⊆ (implemented ∪ known-not-implemented).
// Every path in endpoint_for() must either have a real handler or
// be in known_not_implemented_endpoints().
let not_impl: HashSet<&str> =
known_not_implemented_endpoints().iter().copied().collect();
let implemented: HashSet<&str> = ALL_IMPLEMENTED_ENDPOINTS
.iter()
.copied()
.collect();
let mut known: HashSet<&str> = HashSet::new();
known.extend(&not_impl);
known.extend(&implemented);
for path in all_ts_endpoint_paths() {
let desc = endpoint_for(path);
assert!(
desc.is_some(),
"Path {} is in TS contract but not in endpoint_for()",
path,
);
assert!(
known.contains(path),
"Path {} is in endpoint_for() but has neither a handler nor is in known_not_implemented_endpoints()",
path,
);
}
// Also verify that known_not_implemented paths really resolve in endpoint_for().
for path in &not_impl {
assert!(
endpoint_for(path).is_some(),
"Known-not-implemented path {} is not in endpoint_for()",
path,
);
}
}
}
fn every_endpoint_for_path_is_implemented_or_known_not_implemented() {
// Set-equality direction: Rust routing table ⊆ (implemented ∪ known-not-implemented).
// Every path in endpoint_for() must either have a real handler or
// be in known_not_implemented_endpoints().
let not_impl: HashSet<&str> =
known_not_implemented_endpoints().iter().copied().collect();
let implemented: HashSet<&str> = ALL_IMPLEMENTED_ENDPOINTS
.iter()
.copied()
.collect();
let mut known: HashSet<&str> = HashSet::new();
known.extend(&not_impl);
known.extend(&implemented);
for path in all_ts_endpoint_paths() {
let desc = endpoint_for(path);
assert!(
desc.is_some(),
"Path {} is in TS contract but not in endpoint_for()",
path,
);
assert!(
known.contains(path),
"Path {} is in endpoint_for() but has neither a handler nor is in known_not_implemented_endpoints()",
path,
);
}
for path in known.iter() {
assert!(
all_ts_endpoint_paths().contains(path),
"Path {} is in known/implemented endpoints but missing from all_ts_endpoint_paths()",
path,
);
}
// Also verify that known_not_implemented paths really resolve in endpoint_for().
for path in &not_impl {
assert!(
endpoint_for(path).is_some(),
"Known-not-implemented path {} is not in endpoint_for()",
path,
);
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gxserver-rs/src/protocol.rs` around lines 663 - 700, Update
every_endpoint_for_path_is_implemented_or_known_not_implemented to also verify
the reverse subset: each path in known (or separately in
ALL_IMPLEMENTED_ENDPOINTS and known_not_implemented_endpoints()) must exist in
all_ts_endpoint_paths. Preserve the existing forward checks and ensure
extraneous implemented or known-not-implemented paths cause the test to fail.

Comment thread gxserver-rs/src/server.rs
Comment on lines +1521 to +1580
async fn handle_doctor_fix_http(
state: &AppState,
endpoint_path: String,
request_id: String,
body: &Value,
) -> RoutedResponse {
let params = match body.get("params").and_then(|v| v.as_object()) {
Some(params) => params,
None => {
return routed_json(
Some(endpoint_path),
StatusCode::BAD_REQUEST,
rpc_error("badRequest", "Missing params object.", Some(request_id)),
);
}
};
let fix_id = params.get("fixId").and_then(Value::as_str).unwrap_or("");
let confirmation_token = params
.get("confirmationToken")
.and_then(Value::as_str)
.unwrap_or("");

// Validate fix ID and confirmation token.
let applied = match (fix_id, confirmation_token) {
("skills.reinstall", "reinstall-skills") => {
crate::agent_skills::install_agent_skills(
&state.paths,
&serde_json::Map::new(),
)
.await
.is_ok()
}
("hooks.reinstall", "reinstall-hooks") => {
crate::agent_hooks::install_agent_hooks(
&state.paths,
&serde_json::Map::new(),
)
.is_ok()
}
_ => false,
};

if applied {
routed_json(
Some(endpoint_path),
StatusCode::OK,
rpc_success(request_id, json!({ "applied": true, "fixId": fix_id })),
)
} else {
routed_json(
Some(endpoint_path),
StatusCode::BAD_REQUEST,
rpc_error(
"badRequest",
format!("Unknown fix or invalid confirmation token: {fix_id}"),
Some(request_id),
),
)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n== doctor.rs matches ==\n'
rg -n 'DoctorFix|toolchain\.install|reinstall-skills|reinstall-hooks|install-tools' gxserver-rs/src/doctor.rs gxserver-rs/src/server.rs gxserver-rs/src/protocol.rs

printf '\n== handle_doctor_fix_http ==\n'
sed -n '1521,1580p' gxserver-rs/src/server.rs

printf '\n== check_toolchain / related doctor fix constructors ==\n'
sed -n '1,260p' gxserver-rs/src/doctor.rs

printf '\n== protocol auth routing snippets ==\n'
sed -n '1,260p' gxserver-rs/src/protocol.rs

Repository: maddada/Ghostex

Length of output: 19807


Add the missing toolchain.install handler in handle_doctor_fix_http
check_toolchain() already emits DoctorFix { id: "toolchain.install", confirmation_token: "install-tools" }, but this match only accepts skills.reinstall and hooks.reinstall. That makes the “Install missing tools through gxserver” fix fail with BAD_REQUEST every time. Consider centralizing the fix IDs/tokens in a shared registry to avoid this drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gxserver-rs/src/server.rs` around lines 1521 - 1580, Update the fix
validation match in handle_doctor_fix_http to accept the toolchain.install and
install-tools ID/token pair emitted by check_toolchain, and invoke the existing
toolchain installation routine using state.paths, returning applied: true on
success. Preserve the current skills.reinstall and hooks.reinstall behavior;
centralize identifiers only if an existing shared registry is available.

Comment on lines +181 to +188
#[cfg(unix)]
{
let mut file = fs::File::create(path)?;
file.set_permissions(fs::Permissions::from_mode(0o600))?;
file.write_all(contents.as_bytes())?;
file.sync_all()?;
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Prevent file permission race condition (TOCTOU).

Creating a file with default permissions and subsequently restricting them via set_permissions introduces a Time-of-Check to Time-of-Use (TOCTOU) vulnerability. An attacker with access to the parent directory could open the file between creation and the chmod operation, retaining a read-capable file descriptor to read the secrets written afterward.

Use OpenOptionsExt to atomically create the file with the 0o600 mode.

🔒️ Proposed fix
     #[cfg(unix)]
     {
-        let mut file = fs::File::create(path)?;
-        file.set_permissions(fs::Permissions::from_mode(0o600))?;
+        use std::os::unix::fs::OpenOptionsExt;
+        let mut file = fs::OpenOptions::new()
+            .write(true)
+            .create(true)
+            .truncate(true)
+            .mode(0o600)
+            .open(path)?;
         file.write_all(contents.as_bytes())?;
         file.sync_all()?;
         Ok(())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[cfg(unix)]
{
let mut file = fs::File::create(path)?;
file.set_permissions(fs::Permissions::from_mode(0o600))?;
file.write_all(contents.as_bytes())?;
file.sync_all()?;
Ok(())
}
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(path)?;
file.write_all(contents.as_bytes())?;
file.sync_all()?;
Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gxserver-rs/src/subprocess_policy.rs` around lines 181 - 188, Update the Unix
file creation block in the subprocess policy path to use OpenOptions with
OpenOptionsExt, setting create/write/truncate behavior and mode 0o600
atomically. Remove the separate set_permissions call while preserving the
existing write_all, sync_all, and error propagation flow.

Comment on lines +20 to +25
// The managed T3 runtime is only ever reached over loopback (see
// `t3_http_request`, which connects to `T3_RUNTIME_HOST`). Bind the listener to
// loopback as well so the runtime is not reachable from other hosts, containers,
// or bridged interfaces. Do not widen this to `0.0.0.0` without an explicit,
// documented remote opt-in and verified auth on every T3 endpoint.
const T3_RUNTIME_LISTEN_HOST: &str = "127.0.0.1";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the native macOS wrapper still binds the T3 runtime to 0.0.0.0

rg 'listenHost\s*=\s*"0\.0\.0\.0"' native/macos/

Repository: maddada/Ghostex

Length of output: 273


Bind the macOS wrapper to 127.0.0.1
native/macos/ghostexHost/Sources/ghostexHost/NativeT3CodePaneReproLog.swift still hardcodes listenHost = "0.0.0.0", so launches through the native wrapper can expose the T3 runtime on external interfaces. Change it to 127.0.0.1 too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gxserver-rs/src/t3_runtime.rs` around lines 20 - 25, Update the macOS
wrapper’s listenHost configuration in NativeT3CodePaneReproLog.swift from
0.0.0.0 to 127.0.0.1, matching the T3_RUNTIME_LISTEN_HOST loopback binding and
keeping native-wrapper launches inaccessible from external interfaces.

Comment thread native/sidebar/modal-host.tsx Outdated
Comment on lines +2566 to +2575
if (isDoctorFixResultMessage(message.message)) {
if (message.message.ok) {
toast.success("Fix applied successfully");
setDoctorLoading(true);
postAppModalHostMessage({ type: "runDoctor" }, "AppModals:runDoctor");
} else {
toast.error(message.message.error ?? "Fix failed");
}
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Auto re-run after a successful fix is sent on the wrong message channel — stuck "Running..." state.

On a successful doctorFixResult, this calls postAppModalHostMessage({ type: "runDoctor" }, ...), which posts to the native AppModalHost lifecycle bridge (window.webkit.messageHandlers.ghostexAppModalHost), used elsewhere in this file only for ready/presented/contentHeightMeasured/toastDismissed/debugLog. "runDoctor" is a SidebarToExtensionMessage that must go through vscode.postMessage to reach handleSidebarMessage's case "runDoctor": void runDoctorChecks(); in native-sidebar.tsx.

Because native never receives this retrigger, runDoctorChecks() is never called, no doctorChecksResult ever arrives, and doctorLoading (set to true right before this call) is never cleared — the Support tab's "Run Doctor" button becomes permanently disabled after any successful fix, until the whole app-modal host remounts.

🔧 Proposed fix
   if (isDoctorFixResultMessage(message.message)) {
     if (message.message.ok) {
       toast.success("Fix applied successfully");
       setDoctorLoading(true);
-      postAppModalHostMessage({ type: "runDoctor" }, "AppModals:runDoctor");
+      vscode.postMessage({ type: "runDoctor" });
     } else {
       toast.error(message.message.error ?? "Fix failed");
     }
     return;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isDoctorFixResultMessage(message.message)) {
if (message.message.ok) {
toast.success("Fix applied successfully");
setDoctorLoading(true);
postAppModalHostMessage({ type: "runDoctor" }, "AppModals:runDoctor");
} else {
toast.error(message.message.error ?? "Fix failed");
}
return;
}
if (isDoctorFixResultMessage(message.message)) {
if (message.message.ok) {
toast.success("Fix applied successfully");
setDoctorLoading(true);
vscode.postMessage({ type: "runDoctor" });
} else {
toast.error(message.message.error ?? "Fix failed");
}
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/sidebar/modal-host.tsx` around lines 2566 - 2575, Update the
successful fix handling in the isDoctorFixResultMessage branch to dispatch the
runDoctor message through the existing vscode.postMessage
SidebarToExtensionMessage channel instead of postAppModalHostMessage. Preserve
setting doctorLoading to true and ensure handleSidebarMessage receives the
message so runDoctorChecks() executes and clears the loading state.

Comment on lines +22598 to +22611
// Skills install under ~/.agents/skills (see gxserver-rs agent_skills.rs), so
// the status checks must read the dotted directory. Reading ~/agents (no dot)
// left every badge stuck on "Not installed" even after a successful install.
const browserSkillPath = path.join(home, SKILLS_ROOT, "ghostex-browser-use", "SKILL.md");
const browserSkillInstalled = isFile(browserSkillPath);
const computerUseSkillPath = path.join(home, "agents", "skills", "ghostex-computer-use", "SKILL.md");
const computerUseSkillPath = path.join(home, SKILLS_ROOT, "ghostex-computer-use", "SKILL.md");
const computerUseSkillInstalled = isFile(computerUseSkillPath);
const agentOrchestrationSkillPath = path.join(home, "agents", "skills", "ghostex-agent-orchestration", "SKILL.md");
const agentOrchestrationSkillPath = path.join(home, SKILLS_ROOT, "ghostex-agent-orchestration", "SKILL.md");
const agentOrchestrationSkillInstalled = isFile(agentOrchestrationSkillPath);
const fable55OrchestrationSkillPath = path.join(home, "agents", "skills", "ghostex-fable-5.5-orchestration", "SKILL.md");
const fable55OrchestrationSkillPath = path.join(home, SKILLS_ROOT, "ghostex-fable-5.5-orchestration", "SKILL.md");
const fable55OrchestrationSkillInstalled = isFile(fable55OrchestrationSkillPath);
const generateTitleSkillPath = path.join(home, "agents", "skills", "ghostex-generate-title", "SKILL.md");
const generateTitleSkillPath = path.join(home, SKILLS_ROOT, "ghostex-generate-title", "SKILL.md");
const generateTitleSkillInstalled = isFile(generateTitleSkillPath);
const moveCodexSessionSkillPath = path.join(home, "agents", "skills", "ghostex-move-codex-session", "SKILL.md");
const moveCodexSessionSkillPath = path.join(home, SKILLS_ROOT, "ghostex-move-codex-session", "SKILL.md");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the bare (non-interpolated) usages vs. the correctly-interpolated ones in the same file.
rg -n '\bSKILLS_ROOT\b|\bHOOKS_ROOT\b|\bAGENTS_ROOT\b' native/sidebar/native-sidebar.tsx
rg -n 'function runNativeNodeScript' -A 40 native/sidebar/native-sidebar.tsx

Repository: maddada/Ghostex

Length of output: 2879


Bare SKILLS_ROOT / HOOKS_ROOT inside the generated Node scripts will crash at runtime.

These String.raw blocks are passed to runNativeNodeScript, so the bare names are emitted literally into the subprocess source. SKILLS_ROOT and HOOKS_ROOT are not declared there, so the first path check throws ReferenceError and aborts the whole status/catalog script. Use ${SKILLS_ROOT} / ${HOOKS_ROOT} here, like the other interpolated constants in this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/sidebar/native-sidebar.tsx` around lines 22598 - 22611, The generated
Node scripts passed to runNativeNodeScript reference SKILLS_ROOT and HOOKS_ROOT
as undeclared bare identifiers, causing runtime failures. Update the relevant
String.raw script blocks to interpolate these constants as ${SKILLS_ROOT} and
${HOOKS_ROOT}, including the skill status and catalog path checks, while
preserving the existing path construction.

Comment on lines 39594 to 39599
function profilesFor(candidatePath) {
if (isRelativeTo(candidatePath, p(".agents")) || isRelativeTo(candidatePath, path.join(home, "agents"))) {
if (isRelativeTo(candidatePath, path.join(home, "agents", "hooks"))) {
if (isRelativeTo(candidatePath, p(HOOKS_ROOT))) {
return linkedProfiles.concat([piAgent]);
}
return linkedProfiles;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="native/sidebar/native-sidebar.tsx"

# Show the relevant ranges around the cited lines.
sed -n '39550,39620p;39880,39920p' "$file" | cat -n

# Find the surrounding String.raw template and all HOOKS_ROOT occurrences in the file.
rg -n --context 3 'HOOKS_ROOT|getAgentsHubCatalogNodeScript|String\.raw|profilesFor|hooksRoot' "$file"

Repository: maddada/Ghostex

Length of output: 12191


HOOKS_ROOT is still bare inside the generated Agents Hub catalog script. profilesFor() and hooksRoot need ${HOOKS_ROOT} here; otherwise catalog generation will throw ReferenceError: HOOKS_ROOT is not defined when it reaches those paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/sidebar/native-sidebar.tsx` around lines 39594 - 39599, Update the
generated Agents Hub catalog script’s profilesFor() logic and hooksRoot
reference to interpolate the HOOKS_ROOT value rather than treating HOOKS_ROOT as
a runtime variable. Ensure both generated references use ${HOOKS_ROOT},
preventing a ReferenceError during catalog generation while preserving the
existing path checks and linkedProfiles behavior.

Comment on lines +169 to +173
| "/api/destructiveAdminAction"
| "/api/capabilities"
| "/api/doctor/exportDiagnostics"
| "/api/doctor/fix"
| "/api/doctor/run";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add missing /api/t3Runtime/* paths to GxserverEndpointPath.

The t3Runtime paths (/api/t3Runtime/status, start, stop, panes) were added to the EXPECTED_ENDPOINTS array in shared/gxserver-protocol-conformance.test.ts, which uses satisfies readonly GxserverEndpointPath[]. Because these paths are missing from the GxserverEndpointPath union, this will cause a TypeScript compilation error.

🐛 Proposed fix
   | "/api/installTool"
   | "/api/browseFilesystem"
   | "/api/destructiveAdminAction"
+  | "/api/t3Runtime/status"
+  | "/api/t3Runtime/start"
+  | "/api/t3Runtime/stop"
+  | "/api/t3Runtime/panes"
   | "/api/capabilities"
   | "/api/doctor/exportDiagnostics"
   | "/api/doctor/fix"
   | "/api/doctor/run";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| "/api/destructiveAdminAction"
| "/api/capabilities"
| "/api/doctor/exportDiagnostics"
| "/api/doctor/fix"
| "/api/doctor/run";
| "/api/destructiveAdminAction"
| "/api/t3Runtime/status"
| "/api/t3Runtime/start"
| "/api/t3Runtime/stop"
| "/api/t3Runtime/panes"
| "/api/capabilities"
| "/api/doctor/exportDiagnostics"
| "/api/doctor/fix"
| "/api/doctor/run";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/gxserver-protocol.ts` around lines 169 - 173, Extend the
GxserverEndpointPath union with the four t3Runtime endpoints:
/api/t3Runtime/status, /api/t3Runtime/start, /api/t3Runtime/stop, and
/api/t3Runtime/panes, so EXPECTED_ENDPOINTS satisfies the declared type.

@maddada

maddada commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Hey @pt-act,

I appreciate the PR but it's A LOT in 1 go.

It's even touched dependencies and a lot of risky parts that would take a lot from me to review.

Can I please understand what you're trying to solve here? I see 6 issues being fixed in 1 PR which is also not a nice thing for me as the reviewer :(

If you want join the discord and we can discuss better and then merge them in 1 by 1 if I agree with you that this added code is needed.

Keep in mind that code is not free.

I'll have to maintain and keep the stuff you're adding now for a very long into the future so hope you understand why I'm hesitant to merge this huge load of code in 1 go.

For next time if you want to contribute to any project the best way is to do smaller PRs and 1 or 2 at a time. And even better would be to join discord or create an issue asking if something is needed first.

All the best 🙏🏻

rial1 added 2 commits July 16, 2026 10:10
Upstream's 17-commit GPUI pivot retires the native macOS ghostexHost
shell (commit 62e63b5) and makes GPUI the default local entrypoint
(bb32814), winding down native macOS surfaces in favor of GPUI.

Resolved conflicts:
- native/macos/ghostexHost/Sources/ghostexHost/RemoteGxserverClient.swift:
  take upstream deletion. maddada#61 SSH fix is obsolete — the native shell is
  retired in favor of GPUI (the BatchMode fast-fail / SSH auth guidance
  can inform GPUI's remote path separately).
- shared/ghostex-settings.ts: keep both 'support' and 'about' nav tabs
  (ours added support, upstream added about — additive).
- sidebar/settings-modal.tsx: keep both Support and About TabsContent
  blocks, each wrapped in its own isFirstLaunchSetup conditional.
- native/sidebar/native-sidebar.tsx: take upstream's fable-5.6 rename
  with our SKILLS_ROOT (maddada#58 dotted-dir ~/.agents/skills) path; drop the
  fable-5.5 lines. The rest of the file already references fable56.

gxserver-rs (security hardening, doctor backend + /api/doctor/*,
subprocess_policy, paths, capability discovery, agent_skills) auto-
merged cleanly — frontend-agnostic, the durable core of the PR.

Verified:
- cargo test -p gxserver --lib: 483 passed, 0 failed, incl.
  paths::tests::generated_ts_matches_rust_agent_paths (AgentPaths
  drift) and all 12 agent_skills tests.
- rg confirms the maddada#58 fix is preserved: path.join(home,"agents","skills")
  appears only at the intentional legacy catalog group (~line 39767);
  no fable-5.5/fable55 remains; all skill-status checkers use SKILLS_ROOT.
- Typecheck (tsc --noEmit): 0 errors in the merged files. 7 pre-existing
  PR errors remain (6 in retired native doctor UI modal-host.tsx scope
  mismatch + 1 gxserver-client.ts switch return) — byte-identical to
  origin/main, NOT introduced by this merge.
The native doctor UI added in 4e3c600 is compile-broken and
non-functional: its doctor useState lives in AppModalHost() while the
message handlers that call the setters sit in the separate top-level
useModalStateFromNative() hook — 6 TS scope errors (setDoctorChecks /
setDoctorLoading / setDiagnosticsJson / setDiagnosticsLoading out of
scope). The PR's frontend gate (Rust tests + TS conformance tests) never
ran tsc --noEmit on the React sidebar, so the breakage passed validation.

Given the GPUI pivot is retiring the native macOS surfaces, fixing the
scoping is throwaway work. Remove the native doctor UI wiring and defer
the surface to GPUI; the durable backend is retained untouched.

Removed (frontend wiring only):
- shared/ghostex-settings.ts: drop the "support" nav tab (keep "about").
- sidebar/settings-modal.tsx: drop the SupportSettingsTab component, its
  TabsContent block, the nav entry, the SettingsModalProps doctor fields,
  and now-orphaned imports (SidebarDoctorCheck, IconStethoscope).
- native/sidebar/modal-host.tsx: drop the doctor useState, the <SettingsModal>
  doctor props, the orphaned message handlers, the three type guards, and
  the doctor type imports.
- native/sidebar/native-sidebar.tsx: drop the runDoctorChecks /
  applyDoctorFix / exportDiagnosticsJson RPC handlers and the
  runDoctor / applyDoctorFix / exportDiagnostics switch cases.

Retained (frontend-agnostic, fully tested):
- gxserver-rs doctor backend: doctor.rs, /api/doctor/run|fix|exportDiagnostics
  routes, CLI. cargo test -p gxserver --lib: 483 passed, 0 failed.
- shared message/type contract (SidebarDoctorCheck,
  SidebarDoctorChecksResultMessage, SidebarDoctorFixResultMessage,
  SidebarDiagnosticsExportResultMessage) for GPUI to reuse.

Verified: tsc --noEmit drops from 7 errors to 1 (the 6 doctor scope
errors gone; the remaining gxserver-client.ts:1033 TS2366 is pre-existing
and unrelated). vitest: 942 passed; the 2 failures
(gpui-agents-hub-scanner-parity agents/hooks gap, native-agent-prompt-text
bun:test runner mismatch) are both pre-existing on the PR tip and
unrelated to this change.

Scope note: keep-vs-fix-vs-rebuild of the doctor UI is the maintainer's
call; this surfaces the decision on the PR rather than carrying known-broken
native code forward through the pivot. Trivially reversible.
@pt-act

pt-act commented Jul 16, 2026

Copy link
Copy Markdown
Author

Merged upstream maddada/main (17 commits, the GPUI pivot) into this PR. The durable gxserver-rs core — security hardening, doctor backend (doctor.rs + /api/doctor/run|fix|exportDiagnostics + CLI), subprocess policy, paths, capability discovery, agent_skills — auto-merged cleanly and is frontend-agnostic. cargo test -p gxserver --lib is green (483 passed, incl. the AgentPaths drift test).

4 conflicts resolved:

One decision for you (scope call): the native doctor UI.
The doctor backend is ready and tested. But the native doctor UI I added had a pre-existing scoping bug: the doctor useState is declared in AppModalHost() while the message handlers that update it sit in the separate useModalStateFromNative() hook — 6 tsc scope errors; the native UI never compiled or ran. (Our frontend gate ran the TS conformance tests but not tsc --noEmit, so the breakage passed validation.)

Given the GPUI pivot is retiring the native macOS surfaces, fixing the scoping is throwaway work. So in a follow-up commit I pulled the native doctor UI wiring (the Support tab, the mis-scoped handlers/state, and the /api/doctor RPC handlers) and kept the backend; the shared message/type contract is preserved for GPUI to reuse. Tell us if you'd rather I fix it in place instead — the removal is trivially reversible.

@pt-act

pt-act commented Jul 16, 2026

Copy link
Copy Markdown
Author

Consolidated heads-up — three pre-existing issues I found while verifying the merge. All are unrelated to this PR's changes and all are yours to triage; flagging so they're not silent. None block this PR.

  • shared/gpui-agents-hub-scanner-parity.test.ts fails on the PR tip (confirmed on a6c28eda before the merge): the GPUI Rust scanner (gpui/src/main.rs) correctly references the agents/hooks node and the parity test requires it, but the macOS Node catalog scanner (getAgentsHubCatalogNodeScript in native/sidebar/native-sidebar.tsx) never did. As the macOS scanner sunsets with the GPUI pivot, this test likely wants relaxing/retiring rather than the GPUI side being "fixed." Low urgency.
  • native/sidebar/gxserver-client.ts:1033 TS2366 (describeGxserverOperation switch lacks a fallback return) — pre-existing, byte-identical to a6c28eda. A one-line fix if you want it; I left it out of this PR to avoid scope creep.
  • native/sidebar/native-agent-prompt-text.test.ts imports bun:test but the project test script runs vitest, so the test can't load. Pre-existing config mismatch — switch the import to vitest or route it to a bun test runner.

@pt-act

pt-act commented Jul 16, 2026

Copy link
Copy Markdown
Author

Hi maddada — fair on all counts, and thanks for the directness. Let me answer your main question first, honestly.

Context: it began as a security audit of gxserver-rs, which surfaced a couple of concrete fixes and from there we got ahead of ourselves and folded in several improvements we thought were useful without first checking whether you want them. That's the wrong way to contribute to a project you'll maintain long-term.

Two of these solve concrete problems; the rest are improvements we proposed on our own initiative and bundling those in without asking you first was the mistake.

What actually solves a reported/real problem:

#58 (skills always "Not installed") a real bug: the installer writes to ~/.agents/… but the status checker read ~/agents/… (no dot), so skills/Desktop Control never showed as installed and "Uninstall" silently did nothing. Small, self-contained fix.

A security audit of gxserver-rs (which we ran on our own initiative, not something you asked for) surfaced two things worth fixing: the repo-clone subprocess inherited the entire ambient environment (the env_clear() was effectively a no-op, so tokens/git config could leak into git clone), and the T3 runtime bound to 0.0.0.0 when it's only ever reached over loopback.
Both are small, defensive, tested. Plus 3 CodeQL DOM-injection sinks (javascript: URLs / unescaped quotes).

What we were trying to solve:

The unifying goal: each of the five takes a specific failure the audit or your open issues exposed and addresses the class behind it — so that class becomes self-diagnosing or can't recur instead of just patching the one instance.

ghostex doctor: Achieves one command that reports skills / daemon / SSH / permissions / T3 state and offers safe fixes.
Why: #58 and #61 both had to be diagnosed by hand; doctor turns that manual expert debugging into self-service and puts the same state in bug reports.
Shared path source of truth: Achieves one canonical path definition both the Rust installer and the TS checker import, with a drift test.
Why: #58 existed only because the two sides hardcoded the path and disagreed by a single dot; this makes that whole "installed but not detected" class impossible rather than fixing the one instance.

Capability discovery + conformance test: Achieves a runtime endpoint listing which APIs are actually implemented, plus CI that fails if the shared TS contract and the Rust server drift. Why: the audit found endpoints declared in the contract but not implemented server-side, so clients can't tell what's live. (Honest: this is preventive, not an active user-facing bug.)

Diagnostic bundle export: Achieves a "Copy Diagnostics" that emits a redacted bundle (versions, config summary, recent errors, T3/skills status).
Why: crashes like #52 were hard to act on with no captured context; this gets you that context without a ten-question back-and-forth.

Unified subprocess policy: Achieves one auditable module defining what environment each spawned process inherits, plus a 0600 secret-file helper.
Why: the audit found three separate subprocess trust-boundary issues (clone env, shell setup, T3 bind); centralizing them means the next spawn site can't silently reintroduce the leak. (Honest: partly a preventive refactor).

That's the actual goal behind each. But I'll be equally straight: this is our read of what would help, extrapolated from a handful of instances, a single #58 bug may not, in your judgment, justify a whole shared-path system, and your existing debugging may not need a doctor. That's exactly why these belong as questions to you, not as merged code.

I'll close this PR. If you're up for it and join Discord to align, then send only what you want as small single-purpose PRs.
I'll also drop the dependency bump (you flagged it, rightly) and the #61 native-SSH change, the latter's obsolete now the macOS shell is retired.

Apologies for the assumptions and unsolicited initiatives, thanks for creating and maintaining this project.

Kind regards

pt-act (Discord: .rialm)

rial1 added 2 commits July 18, 2026 07:15
Brings in: ghostex-web workspace app, release CI workflows, GPUI CEF focus
hardening, gxserver-rs polish (rustfmt, color env, web UI bootstrap), mobile
remote reconnect stabilization, sidebar project collections, android/iOS
submodule bumps.

Preserves pt-act hardening: security allowlist, subprocess policy, doctor
diagnostics, SSH actionable errors, astro/starlight bumps.
workflow_dispatch-only on macos-15-intel. Builds the GPUI macOS app for x86_64 and uploads an ad-hoc signed, unnotarized DMG as a CI artifact. Scope input (minimal default / full) to de-risk compile snags before the full build. Calls prepare-macos-runtime.sh and build-macos-app.sh directly with GHOSTEX_MACOS_ARCH=x86_64, no Sparkle/notarize/remote-packages/on-demand. No existing files modified; zero risk to the arm64 release flow. Cache budget: stable things only (skips gpui/target and gxserver-rs/target to avoid blowing the 10GB repo cache budget and evicting the ~500MB cef-cache). Shelf life: GitHub is phasing Intel macOS out (macos-13 retired, macos-14 deprecated); macos-15-intel works today but the durable fallback is cross-compiling from arm64.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-gpui-macos-x86_64.yml:
- Around line 119-127: Prevent shell injection in the workflow by passing inputs
and step outputs through environment variables instead of interpolating them
inside bash. In .github/workflows/build-gpui-macos-x86_64.yml lines 119-127,
expose inputs.version as INPUT_VERSION and use it in the Resolve version step;
lines 188-198, expose steps.version.outputs.version as VERSION and use it; lines
207-246, expose inputs.scope and the version output as INPUT_SCOPE and VERSION
and reference those variables throughout, including the generated DMG command.
Also correct “macos-13 runner” to “macos-15 runner” in that final section.
- Around line 57-60: Update the actions/checkout@v6 step in the macOS x86_64
workflow to set persist-credentials to false, while preserving the existing
fetch-depth and submodules settings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 949d9362-f7c9-4668-bd9a-ce8cb37956fe

📥 Commits

Reviewing files that changed from the base of the PR and between f9fc72f and 61f1dac.

📒 Files selected for processing (1)
  • .github/workflows/build-gpui-macos-x86_64.yml

Comment on lines +57 to +60
- uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the latest version of actions/checkout
gh api repos/actions/checkout/releases/latest --jq '.tag_name'

Repository: maddada/Ghostex

Length of output: 160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow snippet =="
nl -ba .github/workflows/build-gpui-macos-x86_64.yml | sed -n '45,80p'

echo
echo "== upstream checkout tags =="
gh api repos/actions/checkout/git/refs/tags/v6 --jq '.ref + " " + .object.type + " " + .object.sha' || true
gh api repos/actions/checkout/git/refs/tags/v5 --jq '.ref + " " + .object.type + " " + .object.sha' || true
gh api repos/actions/checkout/git/refs/tags/v4 --jq '.ref + " " + .object.type + " " + .object.sha' || true

echo
echo "== latest releases =="
gh api repos/actions/checkout/releases/latest --jq '.tag_name'
gh api repos/actions/checkout/releases?per_page=5 --jq '.[].tag_name'

Repository: maddada/Ghostex

Length of output: 216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow snippet =="
nl -ba .github/workflows/build-gpui-macos-x86_64.yml | sed -n '45,80p'

echo
echo "== upstream checkout tags =="
for v in v6 v5 v4; do
  echo "-- $v --"
  gh api "repos/actions/checkout/git/refs/tags/$v" --jq '.ref + " " + .object.type + " " + .object.sha' || true
done

echo
echo "== latest releases =="
gh api repos/actions/checkout/releases/latest --jq '.tag_name'
gh api repos/actions/checkout/releases?per_page=5 --jq '.[].tag_name'

Repository: maddada/Ghostex

Length of output: 216


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for endpoint in \
  repos/actions/checkout/git/refs/tags/v6 \
  repos/actions/checkout/git/refs/tags/v5 \
  repos/actions/checkout/git/refs/tags/v4
do
  echo "== $endpoint =="
  gh api "$endpoint" --jq '.ref + " " + .object.type + " " + .object.sha' || echo "missing"
done
echo "== latest release =="
gh api repos/actions/checkout/releases/latest --jq '.tag_name'

Repository: maddada/Ghostex

Length of output: 502


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== git-related commands in workflow =="
rg -n '\bgit\b|push:|checkout@|persist-credentials|fetch-depth|submodules' .github/workflows/build-gpui-macos-x86_64.yml

echo
echo "== full workflow size =="
wc -l .github/workflows/build-gpui-macos-x86_64.yml

Repository: maddada/Ghostex

Length of output: 751


🌐 Web query:

actions/checkout v6 release tag GitHub

💡 Result:

The actions/checkout GitHub action has multiple release tags within the v6 series. The v6 major version was released on November 20, 2025 [1]. Subsequent patch releases for this major version included v6.0.1, v6.0.2, and v6.0.3 [2]. As of July 18, 2026, the latest major version of the actions/checkout action is v7.0.0, which was released on June 18, 2026 [3][4]. Users are generally encouraged to use the latest major version for current features and security updates [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow section =="
sed -n '57,80p' .github/workflows/build-gpui-macos-x86_64.yml

echo
echo "== .gitmodules =="
if [ -f .gitmodules ]; then
  sed -n '1,200p' .gitmodules
else
  echo "no .gitmodules at repo root"
fi

echo
echo "== submodule URLs mentioned in workflow =="
rg -n 'git submodule update|submodules:|checkout@|persist-credentials|url =' .github/workflows/build-gpui-macos-x86_64.yml .gitmodules || true

Repository: maddada/Ghostex

Length of output: 4559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for repo in \
  maddada/Ghostex \
  maddada/ghostex-android \
  maddada/zmx \
  maddada/ghostex-ios \
  maddada/zehn \
  maddada/code-server \
  maddada/m7code
do
  echo "== $repo =="
  gh api "repos/$repo" --jq '.full_name + " private=" + (.private|tostring) + " archived=" + (.archived|tostring) + " disabled=" + (.disabled|tostring) + " visibility=" + (.visibility // "n/a")'
done

Repository: maddada/Ghostex

Length of output: 869


Set persist-credentials: false on checkout.

This workflow only updates public submodules later, so keeping the GITHUB_TOKEN in .git/config is unnecessary and leaves it available to downstream steps.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 57-60: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-gpui-macos-x86_64.yml around lines 57 - 60, Update
the actions/checkout@v6 step in the macOS x86_64 workflow to set
persist-credentials to false, while preserving the existing fetch-depth and
submodules settings.

Source: Linters/SAST tools

Comment on lines +119 to +127
- name: Resolve version
id: version
run: |
if [[ -n "${{ inputs.version }}" ]]; then
echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
else
echo "version=$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Prevent command injection by using environment variables instead of template interpolation.

Directly interpolating GitHub Actions context variables (such as the free-text inputs.version) into inline bash scripts exposes the runner to arbitrary command execution. Any single quotes or structural characters in the input can escape the intended shell strings.

  • .github/workflows/build-gpui-macos-x86_64.yml#L119-L127: Bind ${{ inputs.version }} to an environment variable in the env: block and reference it as $INPUT_VERSION in the script.
  • .github/workflows/build-gpui-macos-x86_64.yml#L188-L198: Bind ${{ steps.version.outputs.version }} to a VERSION environment variable and use $VERSION in the script.
  • .github/workflows/build-gpui-macos-x86_64.yml#L207-L246: Bind both ${{ inputs.scope }} and ${{ steps.version.outputs.version }} to environment variables and use them in the script. Additionally, correct the documentation typo from macos-13 runner to macos-15 runner.
🔒️ Proposed fixes

For L119-L127:

       - name: Resolve version
         id: version
+        env:
+          INPUT_VERSION: ${{ inputs.version }}
         run: |
-          if [[ -n "${{ inputs.version }}" ]]; then
-            echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
+          if [[ -n "$INPUT_VERSION" ]]; then
+            echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT"
           else

For L188-L198:

       - name: Build DMG
+        env:
+          VERSION: ${{ steps.version.outputs.version }}
         run: |
-          VERSION="${{ steps.version.outputs.version }}"
           APP_PATH="gpui/build/macos/Ghostex.app"

For L207-L246:

       - name: Run summary
         if: always()
+        env:
+          INPUT_SCOPE: ${{ inputs.scope }}
+          VERSION: ${{ steps.version.outputs.version }}
         run: |
           {
             echo "## GPUI macOS x86_64 build"
             echo ""
             echo "| | |"
             echo "|---|---|"
-            echo "| Scope | \`${{ inputs.scope }}\` |"
-            echo "| Version | \`${{ steps.version.outputs.version }}\` |"
-            echo "| Arch | x86_64 (Intel, macos-13 runner) |"
+            echo "| Scope | \`$INPUT_SCOPE\` |"
+            echo "| Version | \`$VERSION\` |"
+            echo "| Arch | x86_64 (Intel, macos-15 runner) |"
             echo "| Signing | ad-hoc (unnotarized) |"
             echo ""
             echo "### Download"
             echo ""
-            echo "Grab the \`ghostex-macos-x86_64\` artifact from this run — it contains \`ghostex-${{ steps.version.outputs.version }}-x86_64.dmg\`."
+            echo "Grab the \`ghostex-macos-x86_64\` artifact from this run — it contains \`ghostex-$VERSION-x86_64.dmg\`."
             echo ""
             echo "### Install on Intel Mac (ad-hoc signed, unnotarized)"
             echo ""
             echo "Gatekeeper will block this DMG because it is ad-hoc signed and not notarized. To open:"
             echo ""
             echo '```bash'
-            echo "open ghostex-${{ steps.version.outputs.version }}-x86_64.dmg   # drag Ghostex.app to Applications"
+            echo "open ghostex-$VERSION-x86_64.dmg   # drag Ghostex.app to Applications"
             echo "xattr -dr com.apple.quarantine /Applications/Ghostex.app"
             echo '```'
             echo ""
             echo "Or right-click the app → **Open** → **Open anyway**."
             echo ""
             echo "### Known gaps in this dev DMG"
             echo ""
             echo "- **Project board (bd) is not bundled** — Beads is a separate checkout, not a submodule. Use the signed release build for Project board."
-            if [[ "${{ inputs.scope }}" == "minimal" ]]; then
+            if [[ "$INPUT_SCOPE" == "minimal" ]]; then
               echo "- **Zehn search and TUI are skipped** (minimal scope). Re-run with scope=full to include them."
             fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Resolve version
id: version
run: |
if [[ -n "${{ inputs.version }}" ]]; then
echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
else
echo "version=$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
fi
- name: Resolve version
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
if [[ -n "$INPUT_VERSION" ]]; then
echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT"
else
echo "version=$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
fi
🧰 Tools
🪛 zizmor (1.26.1)

[error] 122-122: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 123-123: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

📍 Affects 1 file
  • .github/workflows/build-gpui-macos-x86_64.yml#L119-L127 (this comment)
  • .github/workflows/build-gpui-macos-x86_64.yml#L188-L198
  • .github/workflows/build-gpui-macos-x86_64.yml#L207-L246
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-gpui-macos-x86_64.yml around lines 119 - 127,
Prevent shell injection in the workflow by passing inputs and step outputs
through environment variables instead of interpolating them inside bash. In
.github/workflows/build-gpui-macos-x86_64.yml lines 119-127, expose
inputs.version as INPUT_VERSION and use it in the Resolve version step; lines
188-198, expose steps.version.outputs.version as VERSION and use it; lines
207-246, expose inputs.scope and the version output as INPUT_SCOPE and VERSION
and reference those variables throughout, including the generated DMG command.
Also correct “macos-13 runner” to “macos-15 runner” in that final section.

Source: Linters/SAST tools

rial1 added 5 commits July 18, 2026 08:44
Review fixes before first dispatch:
- tui2 is a regular directory (not a submodule); git submodule update --init tui2 would fail. Drop it from the full-scope init; tui2 is already in the checkout from actions/checkout.
- Clarify GHOSTEX_GPUI_BUNDLE_ID=com.madda.ghostex.host is deliberate (matches scripts/release-gpui/macos.sh:67, not a mistake); CEF/lid-sleep helper ids derive from it consistently.
- Run summary: macos-13 -> macos-15-intel (stale string from the runner-label fix).

Verified non-issues:
- actions/checkout@v6 is used in all 8 other release-gpui-*.yml workflows (real released tag).
- All submodule URLs are public HTTPS to maddada/* (same as existing release-gpui-macos.yml which inits code-server/t3code with no auth rewriting).
First dispatch failed: gpui crate path-deps at /Users/runner/work/_references/zed (and cef-rs, gpui-component) were missing because the workflow called prepare-macos-runtime.sh + build-macos-app.sh directly, skipping scripts/release-gpui/prepare-references.sh which clones those pinned references. Add the step right after submodule init. Sets GHOSTEX_RELEASE_SKIP_SUBMODULES=1 so prepare-references.sh does ONLY the _references/* checkouts (zed@65e1c5af, cef-rs@0ddbc2ac, gpui-component@0775df39, beads@672d9420) and skips submodule init (our workflow already inits per scope). Does NOT set GHOSTEX_RELEASE_SKIP_GPUI_REFERENCES (we need zed + cef-rs + gpui-component). beads is cloned but not consumed (no BEADS_ROOT set), so bd stays absent from the dev DMG by design.
Second dispatch failed at the final link of ghostex-gpui: clang: error: no such file or directory: '.../gpui/../ghostty/macos/GhosttyKit.xcframework/macos-arm64_x86_64/ghostty-internal.a'. The whole cargo tree compiled; it died at link because gpui/build.rs hard-links the GhosttyKit universal slice at a constant path, and the xcframework was never built — scripts/release-gpui/macos.sh (which we skip because it's arm64/signing-coupled) builds it via zig build -Demit-xcframework -Dxcframework-target=universal. Add that exact invocation as a step after the references step and before Prepare macOS runtime. build.rs wants the universal (macos-arm64_x86_64) slice, so we build universal even for an x86_64 app — the linker selects the x86_64 members from the fat archive. Zig cross-compiles the arm64 slice on the Intel runner. Everything it needs is already present (ZIG=0.15.2 from the install step, xcode-select/xcrun + Metal toolchain from the runner Xcode). Also add ghostty/.zig-cache and ghostty/macos/GhosttyKit.xcframework to the cache paths (and ghostty/build.zig.zon to the key) to speed retries — the GhosttyKit build is heavy (Metal shaders for both arches).
…G env)

Third dispatch failed instantly: 'GHOSTTY_ZIG is missing:' because the GhosttyKit step was at position 3 (after references) but the Zig install step that sets ZIG was at position 7. ${{ env.ZIG }} resolved to empty. Moved GhosttyKit to position 12 — after Zig install (6) and after cache (11, which restores ghostty/.zig-cache for fast retries). Now the step has ZIG set and the zig-cache restored before it runs.
The 'Next step' section was stale after full-scope went green — it still said 'Once this is green on minimal, re-run with scope=full'. Make it scope-aware: minimal runs point at re-running with scope=full; full runs point at the durable deliverable (parameterizing scripts/release-gpui/macos.sh for the signed/notarized release flow with appcast-x86_64.xml and on-demand bd) and surface the shelf-life pressure (GitHub phasing Intel macOS out). No re-run needed — this only affects the run-summary text.
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.

3 participants