> ## Documentation Index
> Fetch the complete documentation index at: https://configurate.crystaworld.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Best Practices for tauri-plugin-configurate

> Learn the security model of tauri-plugin-configurate: directory restrictions, keyring usage, encryption key handling, YAML safety, and production tips.

`tauri-plugin-configurate` applies several layers of defence-in-depth: it restricts which filesystem directories IPC payloads may address, keeps sensitive fields out of config files via the OS keychain, limits the data it reads in one call, and uses atomic writes to prevent corruption. Understanding each layer helps you configure and use the plugin safely in production.

## BaseDirectory restrictions

By default the plugin allows IPC payloads to reference only app-scoped directories: `AppConfig`, `AppData`, `AppLocalData`, `AppCache`, `AppLog`, `Resource`, and `Temp`. Requests that name any other directory — such as `Home`, `Desktop`, or `Document` — are rejected before any filesystem operation runs.

This restriction prevents a compromised or misbehaving frontend from using the plugin's commands to read or overwrite arbitrary paths on the user's machine. The plugin validates the `baseDir` field on every inbound IPC payload against the configured allowlist.

If your application legitimately needs access to additional directories, expand the allowlist explicitly in Rust rather than disabling the restriction entirely:

```rust theme={null}
// src-tauri/src/lib.rs

// Allow specific additional directories
tauri_plugin_configurate::Builder::default()
    .allowed_base_directories([
        tauri::path::BaseDirectory::AppConfig,
        tauri::path::BaseDirectory::Document,
    ])
    .build()
```

If you need to disable the restriction entirely — for example, during development or for a power-user tool that intentionally manages arbitrary paths — you can opt out, but this is not recommended for consumer apps:

```rust theme={null}
// Disables all BaseDirectory validation — use with caution
tauri_plugin_configurate::Builder::default()
    .allow_any_base_directory()
    .build()
```

## Keyring security

Fields marked with `keyring()` in your schema are stored in the operating system's platform keychain rather than in the config file on disk:

* **macOS** — Keychain Services
* **Windows** — Windows Credential Manager
* **Linux** — Secret Service (via libsecret / KWallet)

This means a keyring field value is never written to the filesystem. When the plugin reads a keyring field from an IPC payload, it validates that the `value` property is empty — it will not accept a value supplied by the client and write it to the keychain as-is through the unlock path, preventing a client from spoofing a secret it does not already have.

The `configurate:allow-unlock` permission grants access to the `unlock` command, which reads secrets from the keychain and returns them to the frontend. This permission is **not** included in `configurate:default`. Grant it only to capability sets that genuinely require access to decrypted keyring values.

`UnlockedConfig.lock()` sets the internal data reference to `null`, revoking JavaScript-level access to decrypted values through that instance:

```typescript theme={null}
const unlocked = await config.load().unlock(KEYRING);
const secret = unlocked.data.database.password; // "secret"

unlocked.lock();
unlocked.data; // throws — access revoked
```

<Note>
  `UnlockedConfig.lock()` is an API-level guard, not a cryptographic memory wipe. JavaScript's garbage collector controls memory reclamation. Treat it as a way to enforce access discipline in your code, not as a guarantee that sensitive bytes are immediately zeroed.
</Note>

## Encryption key over IPC

When you use `BinaryProvider` with an `encryptionKey`, that key is included in the Tauri IPC payload only for commands that actually need to decrypt or encrypt content: `load`, `create`, `save`, and `patch`. It is deliberately excluded from the payloads for `delete` and `exists`, which operate on the file without reading its encrypted content.

The key never leaves the machine — Tauri IPC is a local in-process bridge, not a network transport. However, DevTools in a Tauri window can inspect IPC messages, which would expose the key to anyone with access to the developer console.

**Recommendation:** Disable or restrict DevTools in production builds to prevent IPC inspection. Tauri's `app.windows[].devtools` configuration and `#[cfg(debug_assertions)]` guards are the standard mechanisms for this.

## YAML import safety

YAML supports **anchors** and **aliases**, a feature that lets a single small document expand into an arbitrarily large in-memory structure when parsed. A malicious or accidentally crafted YAML file can cause the parser to allocate gigabytes of memory (a "billion laughs" attack).

<Warning>
  Never import YAML from untrusted sources. If your application allows users to supply configuration files, reject YAML and accept only JSON or TOML, both of which have no equivalent expansion mechanism. Even `maxReadBytes` will not protect you if the YAML expands significantly before the size check is applied.
</Warning>

```typescript theme={null}
// Safe: import from a known-good source
await config.importFrom(trustedJsonString, "json");

// Risky: user-supplied YAML can expand via anchors/aliases
// await config.importFrom(userSuppliedContent, "yml"); // avoid this
```

## File write safety

All file writes use an atomic replace strategy via `tempfile::persist` (including on Windows). The plugin writes the new content to a temporary file in the same directory, then atomically renames it over the target path. This means:

* A crash or power failure during a write leaves the previous file intact rather than producing a corrupt partial write.
* There is no window where the file exists in a half-written state.
* The rename is atomic at the filesystem level, which avoids the TOCTOU (time-of-check to time-of-use) race that would exist with a `remove` followed by a `rename`.

External processes writing to the same config file concurrently are outside the scope of the plugin's advisory file lock and can still produce conflicts. Avoid having multiple processes write to the same config file simultaneously.

For critical configuration, you can enable rolling backups by setting `backup: true` in the `Configurate` constructor. Before each write, the plugin copies the current file to `.bak1`, rotating older backups to `.bak2` and `.bak3`. Backup files are automatically deleted when the application exits cleanly.

```typescript theme={null}
const config = new Configurate({
  schema,
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
  backup: true, // creates .bak1–.bak3 before each write
});
```

## IPC safety limits

The plugin enforces several hard limits on inbound IPC data to prevent abuse or accidental runaway:

| Limit                     | Value            | What it protects                                              |
| ------------------------- | ---------------- | ------------------------------------------------------------- |
| Dot-path segments         | 64               | Prevents deeply nested paths from causing excessive recursion |
| Array index cap           | 10,000           | Bounds array traversal in keyring path resolution             |
| Batch entries per request | 128              | Caps the size of `loadAll` / `saveAll` / `patchAll` payloads  |
| Config file / import size | 16 MiB (default) | Prevents loading excessively large files into memory          |

You can raise or lower the read limit via the Rust builder or `tauri.conf.json`. Lowering it is appropriate if your configs are always small and you want an extra guard against unexpected growth:

```rust theme={null}
tauri_plugin_configurate::Builder::default()
    .max_read_bytes(4 * 1024 * 1024) // 4 MiB
    .build()
```

```json theme={null}
{
  "plugins": {
    "configurate": {
      "maxReadBytes": 4194304
    }
  }
}
```

## Production checklist

Use the checklist below before shipping a production build. Each item maps to one of the security properties described on this page.

<Tip>
  Work through this checklist for every app-facing capability file, not just the default one. Overly broad permissions granted to a secondary window are just as risky as broad permissions on the main window.
</Tip>

<Accordion title="Production security checklist">
  * **Grant only the permissions you need.** Start with `configurate:default` and add `configurate:allow-unlock` only if the window actually needs to read keyring-backed fields.

  * **Use app-scoped `BaseDirectory` values.** Stick to `AppConfig`, `AppData`, `AppLocalData`, `AppCache`, `AppLog`, `Resource`, or `Temp` unless you have a documented reason to expand the allowlist.

  * **Use `kdf: "argon2"` for user-supplied keys.** Any `encryptionKey` that originates from a user-typed password or low-entropy source must use Argon2id. The default `"sha256"` KDF is only safe for high-entropy random keys.

  * **Restrict DevTools in production builds.** Tauri DevTools allow inspection of IPC messages, which would expose the `encryptionKey` and keyring `service`/`account` values. Guard DevTools behind `#[cfg(debug_assertions)]` or Tauri's `devtools` window configuration.

  * **Never import YAML from untrusted sources.** Restrict import to JSON or TOML for any content supplied by users or fetched from the network.

  * **Enable `backup: true` for critical config files.** Rolling backups provide a recovery path if a write is interrupted or a migration produces unexpected results.

  * **Do not share config files between concurrent processes.** The plugin's advisory lock does not prevent external processes from writing to the same path simultaneously. Design your app so only one process owns each config file.
</Accordion>
