# Security ## Reporting a vulnerability Report privately through GitHub's [private vulnerability reporting](https://github.com/jag-k/clipboard-transformer/security/advisories/new) on this repository. Please do not open a public issue for a security problem. This is a single-maintainer hobby project, not a product with an on-call rotation. Expect a first reply within about a week. There is no bounty program. If a report turns out to be valid, the fix ships in the next release and you get credit in the release notes unless you ask otherwise. Supported version: the latest release. Older versions do not receive backports. ## What the application actually does Clipboard Transformer is a local rule engine. It is worth being precise about the parts that sound alarming but are not, and about the parts that genuinely deserve care. ### Clipboard access The watcher polls the system change counter and reads the clipboard only when it changes. Everything stays on the machine: there is no telemetry, no analytics, and no network traffic in the clipboard path at all. It is not a password sniffer, and it does not try to be clever about guessing what is sensitive. Instead it honors the platform conventions that applications use to mark content as private, and it does so from clipboard *metadata* before any payload is read: - **macOS**: `org.nspasteboard.ConcealedType`, `org.nspasteboard.TransientType`, `org.nspasteboard.AutoGeneratedType`, plus the private types published by 1Password, KeeWeb, and TypeIt4Me. - **Windows**: `CanIncludeInClipboardHistory` or `ExcludeFromCloudClipboard` set to `0`, `ExcludeClipboardContentFromMonitorProcessing`, and `Clipboard Viewer Ignore`. - **Linux**: `x-kde-passwordManagerHint` with the value `secret`. An item carrying any of those markers is dropped without its contents being read. A password manager that follows its platform's convention — which the mainstream ones do — is therefore never seen by this application. One that copies a secret as ordinary unmarked text is indistinguishable from any other text, on any clipboard tool, including the OS clipboard history. Two further limits are available in the config and are applied before payload values are read: the global `apps` filter with `app_mode: whitelist` or `blacklist`, and `max_item_bytes`. Own writes are tagged with a private marker type and ignored on the next poll, so the application never re-processes its own output. ### What is written to disk Under ``, created with mode `0600` on Unix: | File | Default | Contents | | --- | --- | --- | | `history.cbor` | on, `recent_items_count: 5` | The last few transformations with their complete clipboard representations, so a tray entry can restore an original. Set `recent_items_count: 0` to disable persistence. | | `last-clipboard.cbor` | **off** (`persist_last_clipboard: false`) | One latest external clipboard snapshot, for the `inspect` command. | | `state.json` | on | Pause toggle and temporary rule-disable deadlines. No clipboard data. | | `clipboard-transformer.log` | on | Runtime events. Clipboard values are not logged. | So the default install does keep the last five clipboard items on disk, in your own user directory, readable only by you. If that is not acceptable for your threat model, set `recent_items_count: 0`. One thing to be aware of: a `regexp` rule's `message` template expands capture groups from the matched text, and that message is shown in a system notification. If you write a rule whose message includes matched content, that content reaches the notification centre and whatever it syncs with. This is entirely under your control — it only happens for templates you author. ### Rules imported from URLs `import` entries may point at `http:` or `https:` URLs. Imported files are declarative rules, not code: regular expressions, URL cleanup lists, and rule sets. They are cached under `/url-imports/`, and a failed download falls back to the cached copy rather than aborting startup. Treat a remote ruleset as trusted input anyway. Whoever controls that URL can change what your clipboard is rewritten into, and a pathological regular expression can make matching slow. ### Plugins `clipboard-transformer plugin install ` is deliberately unexciting: it downloads a file over HTTPS to a temporary path, rejects it if it exceeds the module size limit, parses the manifest embedded in the WASM custom section, and moves it to `/.wasm`. It refuses an id that another file already provides. No plugin code runs during installation, nothing registers itself, and nothing is granted. Discovery likewise reads the embedded manifest without executing the module. A downloaded plugin does nothing until you reference its rule types in your config. Capabilities are the intersection of what the manifest requests and what you granted in your own config; a requested capability that you did not grant is not available, and an undeclared grant warns instead of taking effect. Execution happens in Extism on Wasmtime with host-applied limits: a maximum memory page count, a five-second call timeout, a maximum HTTP response size, and a maximum variable size. WASI is enabled so the standard guest template works for clocks and randomness, but **no filesystem directories are preopened**, so a plugin has no filesystem access. Outbound HTTP is limited to the host patterns you allowed; with no HTTP grant there is no network access. A trap or timeout degrades into a structured issue and a warning rather than a crash or a startup failure. What the sandbox does *not* do: a plugin you granted HTTP access to can send whatever it sees to the hosts you allowed. Granting HTTP to a rule that receives clipboard content is a deliberate decision to let that plugin transmit that content. The `plugin inspect` and `plugin doctor` commands show exactly which capabilities a module requests and which are in effect. ## Trust boundaries In scope for a security report: - reading or persisting clipboard content that the platform marked as private; - a plugin escaping the declared capability set — filesystem access, network access to a host you did not allow, or reaching host state directly; - the `plugin install` path writing outside the plugins directory, or accepting a module whose embedded manifest does not validate; - a URL import or plugin manifest that can cause code execution during loading; - release artifacts that fail signature, notarization, or attestation verification. Out of scope, because they are the design: - **Your config is trusted input.** Anyone who can write your config file can add rules, add imports, and grant plugin capabilities. It is configuration for your own account, treated the same way a shell profile is. - **Another process with your user account.** The state files are `0600`, which protects them from other users, not from code already running as you. Anything running as you can read the clipboard directly through the OS. - Content copied as unmarked plain text by an application that does not use its platform's private-clipboard convention. - Denial of service caused by a ruleset you installed yourself. ## Dependencies and supply chain `cargo-deny` runs in CI over the advisory database, the license set, and duplicate versions; `just check-deny` runs the same checks locally. Dependabot watches the Cargo workspace, the example plugin, and the GitHub Actions pins. Release artifacts are built by the tag-driven workflow, carry GitHub build attestations, and on macOS are signed with a Developer ID certificate and notarized. Windows binaries are not code-signed yet. Verification steps are in [the packaging verification runbook](.agents/runbooks/release/packaging-verification.md). Only one third-party GitHub Action is used, and only in the credential-free CI workflow. The release workflows use official and repository-local actions.