---
title: "Open outside links only after checking the · Agent Wranglers"
url: https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/safe-external-links
id: DSK-12
level: Recommended
updated: 2026-10-22
tokens: 909
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Open outside links only after checking the address

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Web code inside desktop apps](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: DSK-12
- Level: Recommended
- Chapter: [Web code inside desktop apps](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps.md)
- Rule: Block navigation and new windows away from the app's own pages. Hand a link to the system browser only when its scheme is `https:` or `mailto:`.
- Check: A test clicks links to `file:///etc/passwd`, `smb://example.com/share` and `https://example.com`. Only the last one opens, and only in the system browser. The app window stays on its own page.
- How: Passing any address to `shell.openExternal` or the system opener can start other programs through schemes such as `file:` or `smb:`. Catch `will-navigate` and the window open handler, and deny by default. When the app registers its own scheme, such as `ledgerly://`, every incoming link is untrusted input. Check it as [check the sender and content of every message to the main process](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/validate-ipc.md) does for messages.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
const ALLOWED_SCHEMES = new Set(['https:', 'mailto:']);

app.on('web-contents-created', (_, contents) => {
  contents.on('will-navigate', event => event.preventDefault());
  contents.setWindowOpenHandler(({ url }) => {
    if (ALLOWED_SCHEMES.has(new URL(url).protocol)) shell.openExternal(url);
    return { action: 'deny' };
  });
});
```

### Plain text example

```rust
const ALLOWED_SCHEMES: [&str; 2] = ["https", "mailto"];

fn open_outside(app: &tauri::AppHandle, raw: &str) -> Result<(), String> {
    let url = url::Url::parse(raw).map_err(|_| "bad link")?;
    if !ALLOWED_SCHEMES.contains(&url.scheme()) {
        return Err("scheme not allowed".into());
    }
    app.opener().open_url(url.as_str(), None::<&str>).map_err(|e| e.to_string())
}
```

## Sources

- [Electron security checklist (limit navigation, new windows and openExternal)](https://www.electronjs.org/docs/latest/tutorial/security)

## Related rules

- DSK-11 [Check the sender and content of every message to the main process](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/validate-ipc.md) in Desktop apps, extensions and CLIs

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/safe-external-links">Agent Wranglers desktop apps, extensions and CLIs playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Check the sender and content of every message to the main process](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/validate-ipc.md)
- Next: [Keep tokens in the system keychain](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/desktop-keychain.md)
- [Web code inside desktop apps](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps.md)
