---
title: "Give each Tauri window only the · Agent Wranglers"
url: https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/tauri-capabilities
id: DSK-34
level: Recommended
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1275
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Give each Tauri window only the permissions it uses

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-27.

- ID: DSK-34
- Level: Recommended
- Chapter: [Web code inside desktop apps](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps.md)
- Rule: In a Tauri app, list every capability in `src-tauri/capabilities` and in `app.security.capabilities` in `tauri.conf.json`. Each capability names its windows by label and grants named permissions, with file and URL scopes limited to the app's own folders and hosts.
- Check: A CI test reads every capability file. It fails on a capability without a `windows` list and on a `remote.urls` host outside an approved list. It also fails on an unlisted permission and on a file scope of `$HOME/**` or `/**`.
- How: Tauri checks every call from a page against the capabilities that cover its window. A broad capability turns a cross-site scripting hole into file and shell access. That is what [keep web content away from the operating system](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/isolate-web-content.md) stops. When `tauri.conf.json` lists capability identifiers, only those are built in, so a stray file adds nothing. By default only code packaged with the app reaches the API. A `remote.urls` entry opens it to a website, so leave it out unless that site is yours and has its own content security policy. Give a settings window and a document viewer separate capabilities. Scope file permissions to paths such as `$APPDATA/**`, and deny folders such as `$HOME/.ssh/**`. Your own commands still check their arguments, 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) says.

## 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.

### JSON example

```json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    { "identifier": "fs:allow-read-text-file", "allow": [{ "path": "$APPDATA/invoices/**" }] }
  ]
}
```

### JavaScript example

```js
// capabilities.test.js: fail the build on a broad or unscoped capability
const BROAD_SCOPES = ['$HOME/**', '/**'];

for (const file of readdirSync('src-tauri/capabilities')) {
  const capability = JSON.parse(readFileSync(`src-tauri/capabilities/${file}`, 'utf8'));
  assert.ok(capability.windows?.length, `${file} names no window`);
  const paths = capability.permissions.flatMap(permission => permission.allow ?? []).map(scope => scope.path);
  assert.ok(!paths.some(path => BROAD_SCOPES.includes(path)), `${file} grants a broad file scope`);
}
```

### Plain text example

```rust
// build.rs: refuse to build with a capability that names no window or grants a broad file scope
const BROAD_SCOPES: [&str; 2] = ["$HOME/**", "/**"];

fn main() {
    for entry in std::fs::read_dir("capabilities").expect("capabilities folder") {
        let text = std::fs::read_to_string(entry.unwrap().path()).unwrap();
        let capability: serde_json::Value = serde_json::from_str(&text).unwrap();
        assert!(capability["windows"].as_array().is_some_and(|windows| !windows.is_empty()), "capability names no window");
        assert!(!BROAD_SCOPES.iter().any(|scope| text.contains(&format!("\"{scope}\""))), "broad file scope");
    }
    tauri_build::build();
}
```

## Sources

- [Tauri, Capabilities](https://v2.tauri.app/security/capabilities/)
- [Tauri, Command scopes](https://v2.tauri.app/security/scope/)

## Related rules

- DSK-10 [Keep web content away from the operating system](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/isolate-web-content.md) in Desktop apps and extensions
- 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 and extensions

**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/tauri-capabilities">Agent Wranglers desktop apps and extensions playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Turn off the Electron fuses the app does not use](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/electron-fuses.md)
- [Web code inside desktop apps](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps.md)
