Skip to content

fix(security): restrict dev asset bridge, electron navigation, and secret file access - #1

Closed
devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1785255390-security-hardening
Closed

fix(security): restrict dev asset bridge, electron navigation, and secret file access#1
devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1785255390-security-hardening

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Security scan of the repo found four issues worth fixing; the dev asset bridge is the significant one.

1. Dev asset bridge was an unauthenticated local file-read API with wildcard CORS (src-tauri/src/dev_asset_bridge.rs). It serves /load-text-asset?rootPath=…&assetPath=…, /load-image-data-url?path=…, etc. from arbitrary caller-supplied paths and answered every request with Access-Control-Allow-Origin: *, so any page the developer visited could read local files through http://127.0.0.1:5187, and MODFORGE_EVENT_ASSET_BRIDGE_ADDR could put it on a routable interface. Now:

let bind_addr = resolve_bind_addr(&bind_addr)?;            // loopback-only bind, else hard errorif !is_loopback_host(headers.get("host")) || !is_allowed_origin(origin) {
    return write_response(, 403, "Forbidden",, None, "…only serves loopback callers.");
}
// responses echo the validated loopback origin (or no CORS headers at all) + `Vary: Origin`

The Host check is what blocks DNS rebinding (evil.example127.0.0.1); the Origin check blocks a plain cross-origin read from a remote page. A missing Origin (curl, non-browser) is allowed, matching prior local-tooling behavior.

2. Electron renderer could navigate anywhere while holding the privileged preload bridge (electron/main.ts). preload.cjs exposes invokeCommand(command, args) plus dialogs and a modforge-asset://local/<abs path> protocol that reads any absolute path, and ipcMain handlers do not check the sender — so a navigation to a remote page (mod link, redirect) would have inherited all of it. New electron/navigation-policy.ts + wiring keeps the renderer on its own document, sends http(s) targets to the system browser, and denies webview attachment:

window.webContents.setWindowOpenHandler(({ url }) => { openInSystemBrowser(url); return { action: 'deny' } })
window.webContents.on('will-navigate', (event, url) => { if (!isInternalNavigationUrl(url, navigationPolicy)) { event.preventDefault(); openInSystemBrowser(url) } })
window.webContents.on('will-attach-webview', (event) => event.preventDefault())

openInSystemBrowser only forwards http:/https:, so javascript:/file:/modforge-asset: targets are dropped rather than handed to shell.openExternal.

3. tauri.conf.json had "csp": null, i.e. no CSP for the Tauri host. Set a policy that keeps script-src to 'self', allows asset:/data:/blob: images and ipc: connections, and blocks object-src/frame-src/form-action. Worth a sanity check in a packaged Windows/macOS build — I can only run the Electron host on Linux, so this policy is reviewed but not runtime-verified.

4. Launcher settings hold the Nexus API key in plaintext with default permissions. AI provider keys already go to the OS keyring, but launcher-settings.json keeps nexusApiKey on disk, previously world-readable. write_secret_file (new, infrastructure/fs/secret_file.rs) writes it and then chmods to 0600 on Unix, no-op on Windows.

Also scanned, no change needed

  • SQL injection: rusqlite queries in localization/usage.rs and knowledge/store.rs build WHERE clauses from static column literals with ? placeholders and params_from_iter; facets are matched against an allowlist. No user data reaches SQL text.
  • Hardcoded secrets: none; matches are test fixtures ("nexus-key", "secret-marker"). Launcher logs record apiKeyPresent flags, not values.
  • Archive extraction / URL handling: sanitize_archive_entry_path rejects absolute and .. components for zip/tar/7z/rar; open_launcher_url_in_browser allows only http/https; NexusModsBbcode.sanitizeUrl allows only http/https plus restricted image data URIs.
  • Dependencies: pnpm audit reports 28 advisories (9 high), but every finding is a dev-only transitive — the vulnerable electron@23.3.13 is react-devtools' own copy, not the shipped electron@^43; the rest are electron-builder/tailwind-lint/vite-plus subtrees (brace-expansion, postcss, tar, fast-uri, cross-spawn, got, esbuild). Fixing them means overriding transitives across major lines (e.g. brace-expansion 1.x/2.x → 5.0.8, published today), so I left the lockfile alone rather than risk the toolchain for non-shipped code.

Follow-ups not taken here

  • ipcMain handlers still trust any sender; with navigation locked down this is defense-in-depth, but a senderFrame/command-allowlist check would close it properly.
  • modforge-asset://local/… reads any absolute path; scoping it to the game/app data roots would remove the primitive entirely.
  • Moving nexusApiKey to the keyring (like AI keys) would beat file permissions, but needs a migration.
  • sandbox: false on the main BrowserWindow.

Validation

  • cargo fmt + cargo check --all-features: clean (one pre-existing unused import: Context warning in infrastructure/shell.rs).
  • cargo test --lib dev_asset_bridge (5), secret_file_tests (3), launcher settings_tests (5): pass.
  • vp test run electronNavigationPolicy (3): pass. vp lint .: 0 errors (2 pre-existing warnings). vp fmt --check: clean. tsc -p tsconfig.electron.json and tsconfig.app.json: clean.

Link to Devin session: https://app.devin.ai/sessions/f8f86045ce3a43a1a372bd7b205b411e
Requested by: @Arborsm

…cret file access

Reject non-loopback callers on the dev asset bridge, keep the Electron renderer on its own document, add a Tauri CSP, and store launcher API keys owner-only.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@Arborsm Arborsm closed this Jul 28, 2026
@Arborsm
Arborsm deleted the devin/1785255390-security-hardening branch August 13, 2026 15:55
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.

1 participant