Skip to content

fix(security): restrict renderer top-level navigation to trusted origins - #368

Open
khawarahemad wants to merge 1 commit into
aiko-chan-ai:electron-v3from
khawarahemad:fix/restrict-renderer-navigation
Open

khawarahemad wants to merge 1 commit into
aiko-chan-ai:electron-v3from
khawarahemad:fix/restrict-renderer-navigation

Conversation

@khawarahemad

Copy link
Copy Markdown
Contributor

Summary

This PR adds a will-navigate event listener to this.win.webContents to restrict in-frame top-level navigation to trusted application origins.

Impact

While popup windows (setWindowOpenHandler) were handled, in-frame navigations (location.href, target="_self", or redirects) to external domains were unconstrained. Navigating the top-level frame to an external URL allowed untrusted web content to execute inside a BrowserWindow with webSecurity: false and access privileged window.BotClientNative and window.protoAPI preload bindings.

Root Cause

this.win.webContents lacked a will-navigate handler to intercept and cancel top-level navigation events targeting external domains.

Fix

Added a will-navigate handler that verifies the navigation target's hostname (Constants.CustomDiscordDomain, localhost, or 127.0.0.1) and protocol (https:, or http: for local hosts). External URLs are prevented from navigating in-frame and are delegated to the OS default browser via shell.openExternal().

Validation

  • Validated with npm run test:typescript and npm run build:ts.
  • Verified at runtime that setting window.location.href = "https://google.com" in the renderer is blocked in-frame and opened safely in the external browser.

@aiko-chan-ai

Copy link
Copy Markdown
Owner

I'll review this PR later, it's already quite late over here.

@aiko-chan-ai
aiko-chan-ai requested a lite review from Copilot August 25, 2026 20:17
@aiko-chan-ai aiko-chan-ai added the AI This PR looks AI-assisted. That's fine, this is just a marker. label Aug 25, 2026

Copilot AI 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.

🟡 Changes recommended

The navigation hardening is incomplete for redirects (needs will-redirect handling) and should avoid delegating non-http(s) schemes via shell.openExternal().

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR aims to harden the Electron renderer navigation surface by preventing top-level in-frame navigations from escaping the app’s trusted origins, mitigating exposure when webSecurity: false and privileged preload APIs are present.

Changes:

  • Add a webContents navigation guard intended to block top-level navigation to untrusted origins and delegate external URLs to the OS browser.
  • Make Vencord extension loading optional by checking for existence and logging failures/missing path instead of always attempting to load.
  • Adjust Express Request type augmentation to use global namespace Express merging.
File summaries
File Description
src/AppCore/index.ts Adds will-navigate handling for navigation restriction; also changes Vencord extension loading behavior and logging.
src/overrides.d.ts Updates Express Request typing augmentation approach (and removes the file header).
Review details
  • Files reviewed: 1/2 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/AppCore/index.ts
Comment on lines 471 to +488
// WebContents Event
this.win.webContents
.on("will-navigate", (event, navigationUrl) => {
try {
const parsed = new URL(navigationUrl);
const isAllowedHost =
parsed.hostname === Constants.CustomDiscordDomain ||
parsed.hostname === "localhost" ||
parsed.hostname === "127.0.0.1";
const isAllowedProtocol = parsed.protocol === "https:" || (parsed.protocol === "http:" && parsed.hostname !== Constants.CustomDiscordDomain);
if (!isAllowedHost || !isAllowedProtocol) {
event.preventDefault();
shell.openExternal(navigationUrl);
}
} catch {
event.preventDefault();
}
})
Comment thread src/AppCore/index.ts
Comment on lines +480 to +484
const isAllowedProtocol = parsed.protocol === "https:" || (parsed.protocol === "http:" && parsed.hostname !== Constants.CustomDiscordDomain);
if (!isAllowedHost || !isAllowedProtocol) {
event.preventDefault();
shell.openExternal(navigationUrl);
}
Comment thread src/AppCore/index.ts
Comment on lines 336 to +346
// Load Vencord-Web Extension
const extension = await this.session.extensions.loadExtension(Constants.VencordExtensionPath);
this.logger.info(`Loaded Vencord Extension v${extension.version} from ${Constants.VencordExtensionPath}`);
if (fs.existsSync(Constants.VencordExtensionPath)) {
try {
const extension = await this.session.extensions.loadExtension(Constants.VencordExtensionPath);
this.logger.info(`Loaded Vencord Extension v${extension.version} from ${Constants.VencordExtensionPath}`);
} catch (err) {
this.logger.error("Failed to load Vencord extension:", err);
}
} else {
this.logger.warn(`Vencord extension not found at ${Constants.VencordExtensionPath}, running without extension.`);
}

@aiko-chan-ai aiko-chan-ai left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Isn't this a bit overly defensive? Running the Discord client inside this app mostly opens popups to handle external URLs anyway, and in-app redirects are only reachable if DevTools is toggled on (location.href = "<url>")

I doubt the users of this app don't know that; they're all Discord bot devs after all, right?

Comment thread src/AppCore/index.ts
this.logger.error("Failed to load Vencord extension:", err);
}
} else {
this.logger.warn(`Vencord extension not found at ${Constants.VencordExtensionPath}, running without extension.`);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Technically, this app is entirely unusable without Vencord.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI This PR looks AI-assisted. That's fine, this is just a marker.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants