# Security policy ## Reporting a vulnerability Email `security@keypaste.com` with the affected version or commit, a description, reproduction steps or proof of concept, and the expected impact. Reports in any language and anonymous reports are welcome. Do not use public issues, discussions or pull requests for vulnerabilities. | Response | Target | |---|---| | Acknowledgement | Within 72 hours | | Initial assessment | Within 7 days | | Fix or mitigation plan | Communicated when available, with a timeline | | Coordinated disclosure | Up to 90 days, shortened by agreement or active exploitation | Security reports receive the highest priority. Credit in advisories and the changelog is optional; there is no bug bounty. Breaches and serious shipped bugs receive prompt, full disclosure covering the incident, exposure and changes, as required by PRODUCT §3.10. All security-relevant fixes are disclosed. Reports cover this repository, its build and release pipeline, and dependencies on the secret path. The signup endpoint at `site/src/worker.js` is also in scope because it stores email addresses. Static site content and third-party deployments are outside this reporting scope. Report upstream dependency vulnerabilities upstream and here; vendored patches must be merged manually. ## Supported versions This page describes current source behavior unless a release is named. The public CLI/MCP release is `v0.3.0`; the desktop is available from source and has no public release. [RELEASE.md](docs/RELEASE.md) records distribution status. Replace `v0.1.0`: env export can delete your vault; env rm and env set can act on the wrong entry; get can return the wrong password; env pull can delete an edit it never imported; moving the clock back can revive an expired approval. These defects were fixed in `v0.2.0` and the current download is `v0.3.0` ([changelog](CHANGELOG.md#030)). Published archives are immutable, so the old downloads remain affected. **If you installed `v0.2.0`, replace it.** In that release, saving while another program saves the same vault can undo its change. A retry could overwrite the other program's save with Keypaste's older copy, and the lost change had no history entry; overlapping saves within about two seconds were enough. `v0.3.0` re-reads the vault between retries and refuses rather than overwriting. Two further `v0.2.0` defects lost nothing. On Windows 11 24H2 and Windows Server 2025, a save can fail when another keypaste or KeePass program is saving at the same time, because those builds refuse a temporary name whose 8.3 alias another transaction holds; `v0.3.0` gives each process a temporary directory nothing else names. And an agent asking for entry names on a loaded machine can be told to start keypaste agent for a process that is already running, because the connection budget could expire before the bridge tried the pipe; `v0.3.0` tries the pipe first. `v0.1.0` has the first defect too. Published archives are immutable, so both older downloads remain affected. | Version | Supported | Status | |---|---|---| | `main` | Yes | Fixes land here first | | `0.3.x` | Yes | Current release line | | `0.2.x` | No | Superseded; affected by the defects above | | `0.1.x` | No | Superseded; affected by the defects above | There is no long-term support line before 1.0. Fixes enter `main` and the next release; older tags remain unchanged. ## Verifying a release CLI/MCP downloads at `https://dl.keypaste.com/v/` include `SHA256SUMS`, per-asset `.sha256` files and corresponding source; `v0.3.0` adds a manifest, `keypaste-0.3.0-manifest.json`, and an attestation bundle, `keypaste-0.3.0-provenance.sigstore.jsonl`; `v0.2.0` and earlier have neither. `release.yml` tests the NativeAOT binaries it uploads, including KeePassXC compatibility in both directions. `ci.yml` runs the unit suites against an ordinary build of the same commit. Desktop artifacts have the separate publication requirements in [RELEASE.md](docs/RELEASE.md). The binaries are unsigned and un-notarized. Gatekeeper and SmartScreen behavior depends on the download path, machine and reputation. README documents the macOS quarantine limitation and manual workaround; its install blocks preserve quarantine. Signing and notarization remain O-0010. A Windows signature identifies a publisher but does not guarantee reputation-based prompts disappear. Checksums detect corrupted or truncated downloads. Because the archive and checksum share an origin, an attacker who replaces both defeats that check. `v0.3.0` also carries a GitHub build attestation, signed through Sigstore by `release.yml` running in `notinferred/keypaste` for the release tag, covering every asset and the manifest. `v0.2.0` and earlier have none. Checking one needs [GitHub CLI](https://cli.github.com/) (established with 2.86.0) and no GitHub account: ```sh v= curl -fLO "https://dl.keypaste.com/v$v/keypaste-$v-linux-x64.tar.gz" curl -fLO "https://dl.keypaste.com/v$v/keypaste-$v-provenance.sigstore.jsonl" gh attestation verify "keypaste-$v-linux-x64.tar.gz" \ --bundle "keypaste-$v-provenance.sigstore.jsonl" \ --repo notinferred/keypaste \ --signer-workflow notinferred/keypaste/.github/workflows/release.yml \ --source-ref "refs/tags/v$v" ``` Substitute any other asset name. A changed byte, another repository or another workflow fails the check. A version that publishes the desktop packages carries a second manifest and bundle, `keypaste-app--manifest.json` and `keypaste-app--provenance.sigstore.jsonl`, in the same prefix; an MSI or AppImage is checked with the same command against its own bundle, and the signer workflow is still `release.yml`, which attests what it publishes rather than what compiled it. The command fetches Sigstore's public trust root; to check offline, run `gh attestation trusted-root > trusted_root.jsonl` on a connected machine and add `--custom-trusted-root trusted_root.jsonl`. Without `--bundle`, `gh` fetches the attestation from GitHub and requires `gh auth login`. [verify-provenance.sh](scripts/verify-provenance.sh) `` downloads a whole release and checks the manifest and every asset this way (D-0138); `--component app` does the same for the desktop packages. An attestation shows which repository, workflow, tag and commit produced the bytes. It does not show that the source is safe, that the build is reproducible (O-0012) or that the release workflow was uncompromised ([THREATS.md](THREATS.md) T-21). README documents building from source with dependencies pinned by content hash in `packages.lock.json`. ## Security boundaries [PRODUCT §3](docs/PRODUCT.md) owns the security laws. The master key stays in the local process. Agent releases require human approval or, in source, a setting the person confirmed (in `v0.3.0`, a user-written policy), are limited to an authorized field and lifetime, and require a local audit record. Errors deny release. Keypaste uses library cryptography and collects no telemetry on secret content or entry names. The desktop app is to send crash reports to keypaste.com only once you choose to, at first run or in Settings, whose switch stops them (PRODUCT §3.5). In source, the app asks at its first start, after showing the terms of use, and a report holds the version, your system, the screen open and where in keypaste's code the failure happened, never a message, a value, a name, a path or an identifier for you or your installation; Settings › Advanced › Diagnostics shows each report waiting exactly as it would be sent. Only keypaste's packages send reports, a build from source keeps them and sends none, and the CLI, `keypaste approve` and the bridge send none (THREATS.md T-9). Explicit plaintext export and a saved attachment have the limits described below. [THREATS.md](THREATS.md) records the threat model, mitigations and residual risks. The current desktop, terminal approver and `keypaste run` do not share an unlock session. In source, one vault has one owner: the desktop and `keypaste approve` each claim the vault before its password is read, and the second is refused naming the first. While either holds the vault, a CLI verb that edits items sends the change to it, which saves it only once the person allows it there and asks for no master password, and the other verbs that save are refused the same way (THREATS.md T-29, T-43). Desktop lock ends its own session and stops serving agents; a terminal approver on another vault remains unlocked until stopped, and `run` separately opens and closes its vault before launching a child unless given `--session`, which asks the vault's owner and is released only when the person allows it there or under the timed grant they chose for that exact command (THREATS.md T-30, T-34). The shared-session behavior in [PRODUCT](docs/PRODUCT.md) is a delivery requirement, not a current guarantee. It will govern new releases and launches; it cannot recall copied values, erase client transcripts or revoke credentials at their issuers. ### What unlocks a vault A vault is unlocked by a master password, and in source by a master password and a keyfile, or by a keyfile alone where the vault was made that way elsewhere. keypaste opens every keyfile form KeePass accepts: an XML keyfile, a 32-byte file, a 64-character hex file, and any other file keyed by the hash of its contents. That last form is one edit away from losing the vault for good, so keypaste names it on stderr each time it opens such a vault and will never create one (T-28). The CLI names the file per command as `--keyfile ` or in `KEYPASTE_KEYFILE` and records nothing; the desktop, in source, remembers where the keyfile each vault last opened with is, beside the vault path in `recent.toml`, and never its contents. All three places are readable by anything running as the same user (T-27). A keyfile protects a vault whose file is copied away — a backup, a synced folder, a lost disk — and not a machine somebody is already executing on. Support cannot reconstruct a lost keyfile any more than a lost password. In source, `keypaste access` and the desktop's Settings change a vault's master password and attach, replace or remove a keyfile; the desktop asks for the current password first, even though the vault is open. It attaches a keyfile that already exists, in the XML, 32-byte or hex form; in source, the desktop also makes a KeePass XML keyfile when you ask, while creating a vault or changing its keyfile, written once where you choose, never over an existing file, and readable only by you outside Windows, which is the one key material keypaste writes outside a vault (D-0506). It never takes a password away from a vault that has one. keypaste checks at run time that it can read a known XML keyfile; a build that cannot refuses every XML keyfile rather than key the vault with the file's hash (D-0294). The file a change replaces, and every copy taken before it, still opens with the old credentials, so after changing a password or keyfile you believe exposed, delete those copies; a running `keypaste approve` keeps the vault it opened until restarted. In the current download, `v0.3.0`, there is no keyfile support and no verb that changes a vault's password or keyfile. In source, the desktop app also opens a vault that needs a YubiKey slot programmed for HMAC-SHA1 challenge-response, and its Settings add or remove one; the key is derived as KeePassXC derives it, so either program opens the other's vault (D-0366). The key is asked on every open and every save, and its last answer stays in memory until the vault locks (T-37). A lost or broken key locks the vault for good: only a second key programmed beforehand with the same secret opens it, and keypaste cannot copy a secret out of a key or recover one. Backups made while the key was required need it too. The CLI and `keypaste approve` cannot open such a vault. In source, the desktop app on Windows unlocks a vault again with Windows Hello after a lock you made, the idle lock, minimize-lock or closing into the tray, as KeePassXC 2.8 does; `keypaste lock`, another vault opened in its place, a changed password or keyfile and quitting need the master password again. After each unlock with the master password, Windows Hello asks to keep it: keypaste then holds the vault key's parts, the master password's SHA-256, the keyfile's key and a YubiKey's slot but never its answer, sealed with AES-256-GCM under the SHA-256 of a signature its Windows Hello key makes, in the app's memory only and never on disk (D-0550, D-0551). A resume derives the key again and asks a YubiKey for its touch again (T-37). While the app runs, whoever can pass Windows Hello as your Windows user, with the Windows PIN included, can reopen the vault after those locks, and the parts open the vault's file and its copies made under the same password and keyfile ([THREATS.md](THREATS.md) T-44). It reaches Windows Hello through the Windows SDK's WinRT projection, `Microsoft.Windows.SDK.NET` with C#/WinRT's `WinRT.Runtime`, MIT-licensed and published by Microsoft, which only the Windows package of the app carries, at the version the pinned .NET SDK selects; it is the supported way for .NET to call WinRT, and it carries a 32-byte challenge to Windows and a signature back, never the vault, the password or the parts, which keypaste seals itself (D-0549). The CLI does not change and cannot use Windows Hello. ### How a vault's key is derived KeePassLib derives each vault's key with the key derivation the vault names. In source, the app and the CLI on Windows and macOS carry `argon2.dll` or `libargon2.1.dylib`, the Argon2 reference implementation that CI compiles from release `20190702` of phc-winner-argon2, pinned by SHA-256, and KeePassLib's Argon2 runs in that native code; on Linux it uses the system's libargon2 where one is installed. The 32-byte composite key KeePassLib makes from the password, the keyfile and any hardware key's answer reaches the library only in memory allocated outside the .NET heap and zeroed afterwards, and the library wipes its own working memory before freeing it. The file is loaded by its full path beside the binary, never searched for, so whoever can replace it could already replace the binary. Where no library loads, as for a `keypaste` moved away from the file that came beside it, KeePassLib derives the same key with its own managed Argon2, more slowly (D-0522, D-0523). ### Secret input in the desktop app `ConsoleSecretPrompt` reads characters into a clearable buffer without forming a password string. Desktop input arrives from Avalonia as immutable strings that cannot be wiped, including multi-character input-method events. The OS keyboard layer, input methods and keyloggers remain outside this boundary. The desktop uses `MaskedInput` instead of `TextBox`, whose automation peer and undo history can expose or retain text. The control stores a character count and reports characters to a clearable buffer. Its accessibility surface exposes the placeholder and mask length without a value pattern; different secrets of the same length produce the same surface. Seven fields use it: the master password, the new master password and its confirmation when a vault is created, a new entry's password and a replacement for an existing one, and a new variable's value and a replacement for an existing one. Every one of them is held to that same-surface check. Each view model clears its buffers after a successful unlock, a wrong-password failure, a create whether it succeeded or was refused, a saved or cancelled entry or variable, and locking. Argon2 receives a span without an additional complete password string. The fields for entries, variables and custom fields answer `Ctrl/Cmd+V`, and so do the seven master-password fields: unlock, the two on the create form, a backup's on the restore panel, and the current, new and confirmation fields in Settings (D-0495). A master-password paste is read into a buffer of its own and added to the field only once the clipboard has answered, never while an unlock, create, check or change is reading it; keypaste clears only what it put on the clipboard, so a master password copied from elsewhere stays there until something replaces it. The import dialog's field for an imported vault's password answers the gesture the same way, never while that file is being unlocked. A paste arrives as one immutable string that cannot be wiped, as typed input already does. One trailing line break is dropped, because copying a token out of a terminal or a file usually brings one and a credential ending in a newline fails elsewhere. Anything else a keyboard cannot produce is refused outright and the field says so, rather than being stripped: a silently altered value would be stored as the secret without anything on screen showing the difference. A bidi override or zero-width character inside a secret is kept, because it is part of the secret; the rules below govern such characters where text is drawn. ### Values the desktop app shows, and values it copies Entry lists show titles, groups, usernames and URLs, and never a password, a note or a custom field's value (D-0488); selecting an entry shows its notes as well. Usernames, URLs and notes are drawn through a rule that keeps ordinary punctuation and line breaks and replaces anything that can make text misrepresent itself, including bidi overrides, zero-width and tag characters, and other control characters. Titles and group paths keep the stricter name rule, because they address an entry. The Secrets screen shows a password as dots; holding it reads the current password from the vault and draws it until release, and Copy reads it when requested. A replacement is entered into a masked field rather than shown. An environment value can be revealed individually while held, and so can a password an entry no longer uses, listed in that entry history beside the current values, and an entry's current TOTP code, computed from the vault at the hold and again when its period ends while it is held, and shown beside the seconds until the next code (D-0481); each of the four copies through the same clearing countdown. Its control avoids publishing the value through a `TextBlock` automation name, but screenshots, recordings, remote desktops and nearby observers can capture the visible value. Copied secrets are cleared after twenty seconds if the clipboard still contains the copied value. Locking, quitting and Clear now also clear them. A pending clipboard write is cleared when it completes; quitting waits for that handoff. In source, an error the desktop app did not expect on its screen thread locks the vault, clears a copied value and quits. Forced termination, a crash anywhere else in the process, power loss and logout can leave a copied value behind. Both Windows front ends request exclusion from Clipboard History and Cloud Clipboard. First-party consumers honor those formats; third-party managers can ignore them, and RDP or Citrix can copy the value to another machine. Windows does not restrict which local process may read the clipboard. The desktop also marks a copied secret concealed and transient for macOS pasteboard managers and as a secret for KDE's Klipper; managers may ignore these conventions, and none stops a process reading the clipboard. The CLI's copies on macOS and Linux carry no such marker. On X11 and Wayland, `xclip` or `wl-copy` can continue serving the value after Keypaste exits. ### Editing your vault from the app The app and CLI write through the same core library and KeePassXC compatibility boundary. If the file changes while the app holds an unlocked copy, the app refuses to save and offers Reload, which loads the other change and discards the edit it could not save, before you make yours again. This protects changes absent from the app's in-memory history. An already-open terminal approver also retains its snapshot until reopened. In the current download, `v0.3.0`, deletion permanently removes the entry and its history, and there is no recycle bin. In source, deleting moves the entry to the vault's KDBX recycle bin instead, so it keeps its identity, fields and history until it is purged or the bin is emptied; a vault whose owner turned the recycle bin off in KeePassXC still deletes permanently, and keypaste says which it did. The desktop's Trash screen restores one, and erases one, or empties the whole bin, behind a confirmation of its own that says what goes; the trash shows names and never a stored value. Erasing a value keypaste wrote therefore takes two deliberate acts rather than one. A vault that has recycled anything is written as KDBX 4.1 rather than 4.0, because the field recording where an entry came from exists only in 4.1; KeePassXC 2.7 and KeePass 2.48 and later read it, and older readers do not. There is no concurrent-edit merge. Entry history and the recycle bin are inside the vault, so neither protects against losing the file. In source, a save that replaces an existing vault first copies the file it is replacing into `.backups` beside it, keeping the last five; the first save after each unlock takes one, and no more often than once every fifteen minutes. A save whose copy cannot be written does not happen, and there is no setting that turns that off. The copies are the vault's own encrypted bytes, so each opens in KeePassXC with the master password it was made under — a later password change does not reach the copies already taken, and the change itself keeps one more under the old password — and none of them recovers a forgotten password. Dropping one past the fifth deletes a directory entry and no more, as every deletion here does. Five copies beside the file do not survive losing the disk or the directory: keep protected copies elsewhere as well. In source, the desktop restores one of those copies from its locked unlock screen, asking only for the master password that copy was made under, and replaces the vault with the copy's exact bytes; the file it replaces is kept as a backup first and nothing is pruned. A restored vault therefore opens with the backup's password, so somebody who knows an earlier master password and can write to the vault's directory can roll the vault back to that copy from the unlock screen. They could already read that copy in KeePassXC, the vault they replaced is kept beside it, and asking for the current password as well would block the recovery this exists for: a changed password nobody remembers. Before the confirmation the check shows a time and counts and never a name or a value (T-26). A kept file that was itself damaged takes one of the five places and can later displace a good copy, and a vault file that briefly cannot be read, as one another program is in the middle of saving cannot, is retried within the same retry budget a save has; only one still unreadable after that budget is left unreplaced, because a file that cannot be read cannot be kept. KeePassXC and a terminal `keypaste approve` hold their own copy of the vault and do not notice a restore. The desktop's export writes one more exact encrypted copy where the person says: another offline guessing target under the same master password, outside keypaste's retention and never deleted by it. ## Memory and authorization limits Clearable master-password buffers and zeroed derived bytes reduce retention without guaranteeing in-memory secrecy. Garbage collection can leave relocated copies; immutable strings, swap, hibernation and dumps can retain values. A debugger or process running as the same user can inspect memory. Keypaste does not use `SecureString`, which does not encrypt memory on Linux or macOS. A field is cached in a clearable buffer only when the person chose the timed allow, for `--max-ttl` (one hour by default) whatever the agent asked for; Allow once caches nothing, an entry whose own tag puts it in a protected environment is never cached (D-0345, D-0348, D-0371, D-0416), and neither is a TOTP code, which is offered no timed allow and is released without its seed (D-0480). Expiry, disconnect, disposal and, in source, an End on the desktop's Agents screen clear owned buffers, and lookup checks expiry before returning a value. Wall and monotonic clocks jointly prevent a clock rollback or suspension from extending the grant. The original vault value was an immutable string, and the released value crosses a local pipe as plaintext into the MCP client. Keypaste cannot erase the client's copies, revoke the credential at its issuer or invalidate sessions created with it. Automatic releases, and in `v0.3.0` policy releases, are evaluated on each request and do not populate this cache. Entry resolution currently materializes standard fields, including passwords, through `Vault.ReadEntries` before approval. Authorization restricts the field released in the response; denial does not establish that the approver never read a secret into memory. The terminal approver keeps its vault unlocked until it stops and has no idle auto-lock. In `v0.3.0` a rule in `policy.toml` releases matching credentials without a prompt, and `keypaste policy ls` explains a rule's meaning without listing the entries it covers. In source that file is no longer read: an agent the person set to Automatic for a project or environment, a change confirmed where the vault is unlocked, is released what it can see there without a prompt, and commands and protected environments are always asked ([agent settings](docs/policy.md)). The approver prints each such release and the audit records it as `automatic`; the agent's reason is recorded without human review. A rule or setting covers the vault's current contents, so anyone able to tag an entry into a covered project, or move one into a covered group, changes what it releases. Client labels are unauthenticated. A local program can start a bridge with another program's label and receive what that agent's settings allow, or in `v0.3.0` match its rules. In source, agents' settings are kept in the vault, so anyone who can open it, in KeePassXC for example, can change them without keypaste asking, and restoring or merging an older copy can bring an older record back; a narrowing made on a computer is also kept there in `~/.keypaste/clients.toml`, which only narrows. Keep `KEYPASTE_HOME` out of synced directories: redirecting it into one lets another machine change this machine's grants in `v0.3.0`, and in source change its ceilings, lowering them, or lifting them through a widening or a reset of `clients.toml` the person confirms on that other machine, which undoes a narrowing this machine kept only in its ceilings. `v0.3.0` rejects policy files writable by other users on Linux and macOS, with no equivalent check on Windows, and reads policy only at agent startup. ## Plaintext and file limits Inline values in `keypaste env set project KEY=value` can enter shell history and remain visible in process arguments. The command warns on stderr. Use `keypaste env set project KEY` with a prompt or pipe to avoid that argument exposure; warning suppression remains O-0009. Updating an existing variable preserves its previous value in encrypted KDBX history, subject to KeePass's ten-item history limit. KeePassXC can display it; the source-built Keypaste desktop can display and restore it through the corresponding entry's history pane. In `v0.3.0` removing the entry removes its history from the active vault. In source, removing it moves the entry and its history to the recycle bin, where both remain readable to anything that can open the vault until the entry is purged. Re-adding starts a new history either way. Credential rotation still requires revocation at the issuer. `keypaste run` closes the vault before starting the child and passes values in the child's environment. In source, the desktop's Run and Open terminal pass a project's set the same way, into the environment of the terminal they open on Windows and Linux and, on macOS, of the shell that the app bundle's own `keypaste run --session` starts in a Terminal window, which hands it to everything run there. On macOS that run gets the set from the app with no second question by presenting a single-use pass the confirmation issued, so Terminal never holds a value and the app records the release (THREATS.md T-42); on Windows and Linux the app records it before the terminal starts, and starts nothing it cannot record (D-0496). The confirmation names the command and directory from `~/.keypaste/projects.json`, which any program running as you can edit, so read it before pressing Start. `keypaste run --session` gets the set from the app or `keypaste approve` after you allow the command and directory it shows; those are the run's own claim, and a program running as you could claim one command and start another (THREATS.md T-30). `keypaste run --token` gets a scoped token's sets from the owner without a prompt, except a set of a protected profile or one holding an entry that its group or its own tag puts in a protected environment, which the token must allow and the owner asks about live (D-0391), and a token bundle, which never carries such a set, opens without any vault; `keypaste run` refuses it after its expiry, but whoever holds the bundle and token can decrypt it at any time, so retiring a bundle means rotating its values (THREATS.md T-32). A reference file chooses which values a run injects and may add literals beside them, so `run` uses a directory's `.env.keypaste` on its own only for the project `projects.json` maps there (THREATS.md T-31). Locking the desktop does not stop the child or erase its environment. Process inspection, descendants, crash reporters and application logs can expose them. Keypaste itself writes no environment file; `verify-run-injection.sh` checks that temporary directories remain empty. On Windows, closing the console can terminate Keypaste while leaving the child running. Keypaste forwards supported termination signals and waits for the child without escalating to a hard kill, so a child that ignores termination can keep it waiting. `keypaste env pull`, and the app's Import .env when Delete is pressed, delete only the imported source when its path and content still match. Changed, replaced, linked or removed sources are retained or reported. Removal first moves the file beside itself and verifies it under that name to protect an editor's intervening save. Hard links, bind mounts, `subst`, mapped-drive versus UNC aliases and Windows 8.3 names remain unresolved identities; content checks provide additional protection. Deletion removes a directory entry without overwriting storage. SSD remapping, copy-on-write filesystems, snapshots, backups, editor files and Git history can retain plaintext. Keypaste warns about a `.git` ancestor and offers no secure-erasure claim. Rotate credentials that were committed or shared. `keypaste share` encrypts one field, or an entry's username, password and URL together (`--field login`), on this machine and uploads only ciphertext; the link carries the key, so anyone holding the link can open it until its views or time run out, and the guarantee holds only while keypaste.com serves an honest viewer (THREATS.md T-33). The metadata `GET` carries a passphrase check, so whoever holds the link can test passphrase guesses offline at PBKDF2-SHA256 (600000) cost without spending a view; keypaste therefore refuses a passphrase under 20 characters, and only a generated one, such as `keypaste generate --words 6`, protects an intercepted link. `keypaste env export --dotenv` explicitly writes plaintext after the user selects a format, destination and confirmation. It refuses the whole export, naming each item, when an item has expired, as `keypaste run` does (D-0337). It warns before writing, refuses existing destinations unless `--force` is supplied, and identifies a `.git` ancestor. Linux and macOS create owner-readable files; Windows inherits directory permissions, which keypaste does not narrow (D-0515). Export refuses the source vault and any other KeePass vault, including with `--force`. Symbolic links, junctions and ancestor-directory links are resolved; the alias limits described above remain. Exported files inherit the storage and deletion risks of other plaintext files. In source, `keypaste attachment save` and an attachment's Save in the app write one attachment's bytes, unencrypted, to a new file at the path you choose, PRODUCT §3.4's exception for an attachment you ask keypaste to save (D-0532, D-0534). Both refuse a path where anything already is and never overwrite; on Linux and macOS the file is readable only by you, and on Windows it inherits its directory's permissions. The bytes are written under a hidden name beside that path and then moved onto it, so a crash between the two can leave that hidden copy there. The saved file carries the storage and deletion risks of other plaintext files above, and keypaste cannot take it back. The app never opens or runs an attachment or a saved copy (D-0536). `attachment add` and `replace`, and the app's Add attachment and Replace, take a file of at most 5 MiB and keep its bytes in memory under KeePassLib's memory protection and on disk only inside the vault (D-0533). What a replace or remove takes away stays in the item's history, readable to anything that opens the vault, unless the vault's history size limit, 6 MiB unless the vault sets another, drops that revision, which the command and the app then say; an item holding large attachments therefore keeps fewer revisions of its other values, as in KeePass and KeePassXC. ## Audit limits Keypaste appends agent-access records without rotating, trimming or rewriting the log. Hash chaining detects edits, removals, insertions and foreign records unless an attacker recomputes the chain. The chain has no secret, and deleting its tail leaves no following record to expose the deletion. `keypaste log verify --expect ` checks an independently retained anchor; Keypaste does not store that anchor beside the log. The log grows without bound: to start a new one, close the app, `keypaste approve` and every bridge, keep the log's latest hash, and move the file aside, which `keypaste log verify --audit-log --expect ` still checks ([archiving it](docs/mcp-setup.md#archiving-it)). Linux and macOS create owner-readable logs; Windows inherits directory permissions. The append-only log, the manual anchor and Windows' inherited permissions are kept by founder decision 2026-10-22 (D-0515). Audit arguments are sanitized and bounded. `args.entry` prefers the approver's resolved path and is capped at 128 characters. The requested TTL is recorded separately from the effective grant or remaining cache lifetime. The reason retains a 200-character excerpt, original length and SHA-256. Released field values are not deliberately logged, but agent-written arguments can contain sensitive text. Treat the local log as sensitive data. `v0.1.0` records the sanitized request argument, which can be an opaque handle. If the required audit record cannot be appended, Keypaste refuses the credential response, including an Automatic release, or in `v0.3.0` a policy-authorized one. Processes running as the user can still inspect memory, keystrokes and clipboard contents; Keypaste cannot defend a compromised local account. ## Maintainer note The reporting mailbox has been tested from an external address. GitHub private vulnerability reporting is currently disabled. KeePassLib provenance and local changes are recorded in [UPSTREAM.md](third_party/KeePassLib/UPSTREAM.md), and those of the BouncyCastle Twofish code it compiles in, which encrypts a Twofish vault, in [its own](third_party/bouncycastle-twofish/UPSTREAM.md). Reports to this project and upstream do not automatically reach one another.