Can you perform a deep security audit?
thinking of downloading this. do a deep security audit.
#Security audit summary
Verdict: I found no obvious malware, backdoor, credential-stealing code, or critical renderer-to-OS escape in the reviewed source. The Electron boundary is substantially better hardened than most small desktop projects.
However, GooeyPi is not a low-risk desktop application. It launches coding agents, shells, plugins, MCP servers, and browser automation with your user account’s permissions. I found two concrete medium-severity weaknesses, plus several important design and supply-chain risks.
Recommendation: Reasonable to install for development use if you:
- use an official, signed release;
- verify its checksum/provenance;
- disable browser control unless needed;
- do not use the in-app browser for sensitive authenticated accounts;
- avoid third-party plugins/MCP servers initially;
- preferably run it under a separate OS account or VM for untrusted repositories.
Audit target: repository state corresponding to package.json version 0.1.8. Git was unavailable in the audit environment, so I could not record the exact commit SHA.
##Findings
###1. Capability tokens remain usable after their runtime exits
Severity: Medium
Each agent runtime receives bearer tokens for browser, scheduling, and collaboration brokers. Tokens:
- remain valid for 24 hours;
- are stored in each bridge’s claims map;
- are removed only upon expiration pruning or complete bridge shutdown;
- have no runtime-exit revocation mechanism.
Relevant code:
- Token TTL and claim structure:
electron/main/lib/capability-bridge.ts:8-18 - Token creation:
electron/main/lib/capability-bridge.ts:75-79 - Claims are cleared only when the whole bridge stops:
electron/main/lib/capability-bridge.ts:86-91 - Expired claims are pruned only when new environments are created:
electron/main/lib/capability-bridge.ts:136-139 - Browser claim is later bound to a session, but never revoked:
electron/main/browser/agent-bridge.ts:35-39
A malicious or compromised agent process could copy its token into a detached helper before termination. That helper could continue to:
- operate browser tabs;
- run JavaScript in pages;
- read the active terminal context;
- manipulate scoped schedules;
- communicate with other sessions;
until the token expires or GooeyPi exits.
Process-tree termination reduces accidental leftovers, but a deliberately daemonized/reparented process can be difficult to contain reliably.
Recommended fix:
- Add
revokeToken(token)toCapabilityBridge. - Associate every issued token with a runtime ID.
- Revoke all tokens immediately on runtime close, failed startup, environment refresh, session archive, and project revocation.
- Reduce the TTL to a short fallback period, such as 5–15 minutes, with renewal only while the owning runtime remains live.
- Make broker dispatch verify runtime liveness in addition to token validity.
###2. Agent runtimes have no numeric concurrency limit
Severity: Medium
The security documentation says runtimes are capped, but the manager intentionally allows unlimited busy or starting runtimes:
“Busy, queued, compacting, and still-starting runtimes remain alive, with no numeric concurrency cap.”
Source: electron/main/agent-rpc/manager.ts:324-330
The manager replaces an idle runtime, but an attacker that keeps each runtime starting or busy can create an unbounded number of subprocesses. There are three independent managers—Prime, OMP, and Pi—so this can multiply across harnesses.
The main renderer is strongly protected, so ordinary remote pages cannot call this IPC directly. Nevertheless, renderer compromise, a logic bug, aggressive automation, or repeated voice/session operations could cause:
- process and file-descriptor exhaustion;
- memory exhaustion;
- large numbers of provider requests;
- system instability or unexpected API costs.
Recommended fix:
- Set hard limits per harness and globally, for example:
- 4 starting runtimes;
- 8–16 live runtimes per harness;
- 24 total.
- Bound the pending runtime-start admission queue.
- Reject excess starts with a stable user-facing error.
- Add tests proving starts cannot exceed the limit.
- Correct
docs/security.md, which currently overstates this protection.
###3. Browser automation is enabled by default and can access authenticated/internal pages
Severity: High operational risk; design issue rather than a conventional vulnerability
The default configuration has:
browserEnabled: true
Source: electron/main/store.ts:50-53
This conflicts with the README’s implication that users enable browser control when wanted. Agent browser tools can:
- navigate to arbitrary HTTP(S) URLs, including localhost and private-network hosts;
- read page text and interactive elements;
- click and type;
- take screenshots;
- execute arbitrary JavaScript in the page.
The JavaScript execution primitive is explicit in:
electron/main/browser/agent-service.ts:558-561electron/main/browser/page-scripts.ts:166-182
The browser profile is isolated from the main renderer, which is good. But it is persistent, and there is no per-origin or per-action approval. If you log into email, GitHub, cloud consoles, internal dashboards, or financial services in that profile, an agent with browser access can act with that session.
The extension wraps page output in “untrusted content” markers, but prompt-injection warnings are advisory controls, not dependable security boundaries.
GooeyPi’s agents already have broad local execution capabilities, so browser restrictions cannot turn the overall system into a sandbox. Still, persistent authenticated browser sessions add a distinct and easily overlooked capability.
Recommended fix:
- Default
browserEnabledtofalse. - Clearly show an always-visible indicator while an agent has browser access.
- Add per-origin grants, especially for:
- loopback;
- RFC1918/private networks;
- link-local and cloud metadata addresses.
- Require confirmation before the first agent action on an authenticated tab.
- Consider disabling
browser_evaluateby default or requiring a separate advanced toggle. - Provide an ephemeral browser-profile mode and automatic cookie clearing.
- Explain that enabling browser control grants access to existing cookies and logged-in pages.
User mitigation: Keep browser control off and do not sign into sensitive services in GooeyPi’s browser.
###4. MCP OAuth discovery can perform GET requests to arbitrary network locations
Severity: Low–Medium
MCP OAuth discovery fetches a user-supplied HTTP(S) server URL and then potentially fetches a metadata URL provided by that server:
electron/main/providers.ts:47-68
The implementation correctly rejects redirects, credentials in URLs, and oversized metadata. However, it does not reject:
- localhost;
- private network ranges;
- link-local addresses;
- cloud metadata endpoints;
- DNS rebinding.
Local MCP servers are a valid use case, so completely blocking private addresses may not be appropriate. But the current behavior forms an SSRF-like network primitive from the privileged main process.
Recommended fix:
- Separate “local MCP” from remote MCP configuration.
- Require explicit confirmation for loopback/private/link-local destinations.
- Resolve and re-check addresses before connecting.
- Block cloud metadata ranges by default.
- Ensure the metadata URL is same-origin unless the user approves the authorization-server origin.
###5. Development mode deliberately trusts a loopback web server
Severity: Low; packaged releases unaffected
In development mode, ELECTRON_RENDERER_URL may point to any uncredentialed loopback HTTP(S) origin:
electron/main/index.ts:112-123
Once that page loads successfully, it receives the privileged preload API through the normal authorization path. A hijacked development server or malicious environment variable could therefore gain terminal, Git, agent, plugin, and settings privileges.
Packaged builds ignore this environment variable and use the custom prime-work:// protocol, so official releases are not affected.
Mitigation:
- Do not run
npm run devin an untrusted environment. - Avoid exporting
ELECTRON_RENDERER_URLglobally. - Bind the dev server safely and ensure the chosen port is not shared.
- Optionally require a random development token in the URL.
###6. The diagnostics setting appears to be non-functional
Severity: Informational/privacy correctness
The UI says:
“Allow anonymous crash and reliability diagnostics.”
Source: src/pages/settings/PrivacySettings.tsx:9-12
But the telemetry field is only persisted and displayed; I found no telemetry sender or crash-upload implementation. Local crash logging exists, but no corresponding diagnostics upload code was identified.
This is safer than undisclosed telemetry, but the setting is misleading.
Recommended fix: Either implement and document the exact diagnostics destination and payload, or relabel it as unavailable/remove the toggle.
##Positive security properties
The desktop boundary is unusually thorough.
###Renderer isolation
The main window uses:
nodeIntegration: falsecontextIsolation: truesandbox: truewebSecurity: trueallowRunningInsecureContent: false
Source: electron/main/index.ts:230-240
The preload exposes a fixed, frozen API rather than Electron or Node primitives: electron/preload/index.ts:1-143.
IPC requires:
- an explicitly authorized
WebContents; - the top-level main frame;
- an exact trusted renderer URL;
- current, non-destroyed sender state.
Source: electron/main/ipc.ts:79-109
That substantially limits IPC access from remote webview content and compromised subframes.
###Remote browser isolation
Webview attachment forcibly removes preload access and applies sandboxing:
electron/main/index.ts:136-147
Popups are denied, navigation is restricted to credential-free HTTP(S), and browser-profile permissions are denied:
electron/main/index.ts:149-157electron/main/index.ts:858-860
Downloads have useful limits:
- user gesture required;
- 512 MiB per download;
- three concurrent downloads;
- 1 GiB/hour;
- unique filenames.
Source: electron/main/browser-downloads.ts:1-86
###Content rendering
Model-authored Markdown:
- does not enable raw HTML;
- replaces images with placeholders;
- permits only HTTP(S), mailto, and fragment links.
Source: src/components/MarkdownText.tsx:9-26, 46-104
This is a strong defense against transcript-driven renderer XSS and remote tracking pixels.
###Filesystem authorization
Project grants are canonicalized and tied to filesystem identity using device, inode, and birth time:
electron/main/projects.ts:19-65electron/main/projects.ts:100-125
Symlinked project roots are rejected. Broad root and home-folder grants are also rejected. Paths are revalidated before use, and per-harness grants are separated.
Git operations use fixed argument arrays and disable dangerous repository features such as:
- hooks;
- external diff commands;
- signing hooks;
- clean/smudge filters;
- global/system config injection.
Source: electron/main/git.ts:15-38, 158-260
This is significantly better than shelling out to git with user-supplied command strings.
###Process and transport safety
Subprocesses use shell: false, bounded input/output, timeouts, and TERM/KILL escalation:
electron/main/process-utils.ts:389-470
RPC commands have strict schemas and unknown-key rejection:
electron/main/agent-rpc/command-schema.ts:1-142
RPC streams have:
- 16 MiB read-frame limits;
- 2 MiB write-frame limits;
- bounded write queues;
- bounded pending requests;
- strict response correlation.
Sources:
electron/main/jsonl-limits.tselectron/main/agent-rpc/transport.tselectron/main/agent-rpc/runtime.ts
Terminal instances are owner-bound, project-authorized, capped at eight, and output-rate limited:
electron/main/terminal.ts:117-150electron/main/terminal.ts:238-259
###Secret handling
Voice keys use Electron safeStorage. Linux explicitly rejects Electron’s unprotected basic_text fallback:
electron/main/voice.ts:40-78
Secret files use mode 0600, and keys are not returned through status IPC. Self-hosted transcription rejects redirects and requires HTTPS except on loopback.
###Packaged-app hardening
Electron fuses disable:
- RunAsNode;
NODE_OPTIONS;- debug CLI arguments;
- extra
file://privileges; - loading outside the ASAR.
ASAR integrity and cookie encryption are enabled:
scripts/release/after-pack.cjs:16-29
The renderer also has a restrictive packaged CSP in electron/main/index.ts:77-78.
##Supply-chain assessment
###Good controls
package-lock.jsonincludes registry integrity hashes.- Prime Agent packages are vendored rather than downloaded from the original third-party bucket.
- Vendored production tarballs are mirrored in
scripts/release/dependency-pins.json. - CI tests verify tarball hashes against those pins.
- GitHub Actions are pinned to full commit SHAs.
- Release publishing receives write permission only in the final job.
- macOS releases require signing, notarization, stapling, and Gatekeeper verification.
- Release assets are intended to have
SHA256SUMS.txtand GitHub provenance attestations. - Package verification checks ASAR layout, Electron fuses, native-module allowlists, architecture, signatures, and size budgets.
###Residual risks
- The vendored Prime Agent codebase is large and powerful. It contains shell, package, plugin, network, and model-provider functionality. Vendoring establishes immutability, not trustworthiness.
- Running from source invokes install scripts for native packages and Prime Agent. The Prime Agent postinstall is dormant by default unless bootstrap environment variables are set, but
npm cistill executes the repository’s native dependency rebuild path. - The lockfile contains many transitive dependencies, including native binaries.
- Linux packages are not described as repository-signed. Checksums and attestations help only if users actually verify them.
- Windows releases are optional. If a release is unsigned, do not treat it as equivalent to the notarized macOS build.
The checked Prime Agent postinstall only bootstraps tools/Python when PRIME_AGENT_BOOTSTRAP_TOOLS_ON_INSTALL=1 or PRIME_AGENT_BOOTSTRAP_KERNEL_ON_INSTALL=1; otherwise it exits. I did not see a default surprise download there.
I could not run npm audit, the test suite, or the build because Node/npm were unavailable in this audit environment. Public advisory searches did not identify direct known issues in the exact node-pty 1.1.0 or zeromq 6.5.0 packages, but that is not a substitute for a full lockfile audit.
##Safe installation procedure
- Use GitHub Releases, not a third-party mirror.
- Verify the release tag and asset provenance.
- Verify the checksum:
sha256sum -c SHA256SUMS.txt - On macOS, verify signing and notarization:
codesign --verify --deep --strict --verbose=4 /Applications/GooeyPi.app spctl --assess --type execute --verbose=4 /Applications/GooeyPi.app xcrun stapler validate /Applications/GooeyPi.app - On Windows, inspect the installer’s Digital Signatures tab. Avoid unsigned installers.
- Start with:
- browser control disabled;
- computer use disabled;
- Ask User disabled;
- no third-party plugins;
- no MCP servers;
- no scheduled tasks.
- Add only a narrow project folder—never your home directory.
- Test first on a non-sensitive repository.
- Do not put production credentials, SSH keys,
.envsecrets, or cloud configuration inside the granted project unless the agent genuinely needs them. - For untrusted repositories, use a VM/container or a separate OS account with limited credentials.
##Bottom line
I would not classify the repository as malicious, and its Electron/IPC hardening is strong. The main reasons for caution are the inherent power of coding agents and the two lifecycle/resource-control gaps above. For routine personal development on trusted projects, an official verified release is defensible. For company source code, production credentials, or untrusted repositories, isolate it and disable browser/plugin capabilities until needed.
END OF POST