This page answers a common security-questionnaire question: “Do you have a secure development lifecycle (SDL), and do you keep the software up to date?” Each section covers one stage and can be copied on its own. The numbers were measured on 2026-09-23 over the previous 92 days, and the last section says how. For network traffic, data storage, signing, and permissions, see the main trust and security page. Every statement here can be checked in the source code.
Short answer
- There’s a defined process, and it’s mostly automated. 135 automated checks (lints, custom code rules, and about 9,500 Rust tests, 10,500 user interface tests, and 371 end-to-end tests) run during development and again on GitHub after each change. It doesn’t follow a named framework like Microsoft SDL, and it has no formal sign-off steps.
- One maintainer, AI-assisted development. Cmdr is a one-person company, so there’s no second-person code review. The review section says what stands in for a reviewer.
- Releases are frequent: 22 releases in the last 92 days, a median of 2.8 days apart.
- Dependencies change every week, and a new dependency version waits three days after publication before Cmdr uses it. Known vulnerabilities in dependencies are scanned for automatically.
- Fixes reach users fast. The app checks for updates every three hours and installs them by itself. About 52% of active installs run a new release within 24 hours, and 71% by the third day. The two most recent dependency advisories reached a release 2 and 3 days after they were published.
- Gaps: no app-wide threat model, no fuzzing, no third-party audit, and checks on GitHub run after a change lands instead of before. The full list is in Not in place yet.
Design
- Written principles come first. The top two are “protect the user’s data” and “rock solid”: write to a temporary file and rename it into place, design for a crash in the middle of an operation, make everything cancelable, and handle the hostile case (a dead network drive, a huge folder). How this works for copies and deletes: How Cmdr protects your files.
- Larger features start as a written plan in the repository (
docs/specs/), and the work follows that plan. - Security and privacy decisions are written down in docs/security.md, each with its reasoning: why the app ships with no entitlements, why the AI assistant can propose file changes but never make them, exactly what an error report can contain, which data the AI assistant can send to a provider, and why there’s no per-window permission check on the app’s internal commands.
- One planned feature has a formal threat model: running file operations with administrator
rights (
docs/specs/elevated-file-operations.md). There’s no threat model for the app as a whole.
Code
- Memory-safe language for everything that touches files, the network, and the system. That code is Rust. The user interface is Svelte and TypeScript, runs in a webview with a strict content security policy, and can’t reach the network itself.
- Compiler warnings fail the build. The Rust lints run with warnings as errors. Among them:
leftover debug output and unfinished code are errors, a crash-prone
unwrap()outside tests needs a written reason, and holding a lock across anawaitis an error. -
unsafeRust: 469 blocks, mostly calls into macOS frameworks. Each of the 430 in Cmdr’s own code must carry a written safety reason, or the build fails. The other 39 are in a vendored copy of a library that watches for file changes. - Custom rules for mistakes that lints don’t catch. Cmdr has 135 checks in
total. The security-relevant ones:
- No code may decide what an error means by matching its text, in Rust or TypeScript. Text changes with the macOS language and with wording edits, so errors carry typed values instead.
- The code that writes files may not reference the AI assistant’s code.
-
Every GitHub Actions step from a third party is pinned to an exact commit, and the risky
pull_request_targettrigger and workflow-wideid-token: writeare banned. - Every dependency’s license notice must match the lockfiles.
- Secret scanning: GitHub secret scanning with push protection, and GitGuardian, check every push for leaked keys. Recent commits are signed.
Review
Cmdr’s development is AI-assisted. One person, David Veszelovszki, decides what to build and how it’s structured, owns every change that lands, and reviews everything users see: the interface, its text, and documents for people. Cmdr is a one-person company, so no second person reviews code changes.
What stands in for a reviewer:
- The automated checks in the test section, which run during development and again on GitHub.
-
The project’s written engineering rules, which are public:
AGENTS.md, the rules in
.claude/rules/, and a short guide next to each part of the code. - The source code is public, so anyone can review any change after it lands.
Test
What’s tested
- About 9,500 Rust tests. About 1,500 of them cover file operations (copy, move, delete, rename), including deliberately hostile cases: a copy stopped halfway, a device unplugged during a transfer, a server that fails.
- Integration tests against real SMB, SFTP, and WebDAV servers in containers, plus a real Nextcloud server, and a simulated phone for USB transfers.
- Property-based tests, which try many random inputs, for parts of the file index and the app.
- About 10,500 user interface tests in 879 files, and 371 end-to-end tests that drive the real app.
- A fixed sample set for the code that removes personal data from error reports, so a change that leaks a file name fails a test.
When tests run
- During development, on the maintainer’s Mac: the standard check run, which includes all unit tests and, when the dependencies changed, the dependency vulnerability scan. The macOS end-to-end suite and the slower checks run at milestones. Nothing forces this before a commit; it’s the written development process.
- After every push to the main branch, on GitHub (Linux): all lints and custom checks, all unit and integration tests, and the end-to-end suite in Docker. This runs after the change lands, so a failure means a fix follows; it doesn’t block the change.
- Every day, on GitHub: the Rust dependency vulnerability and license scans
(
cargo-auditandcargo-deny) against the locked versions. - Every six days, on GitHub:
govulncheckfor the Go build scripts, an unused-dependency scan, the slowest lints, and live tests against five AI providers’ real APIs. - Every release, on GitHub (macOS): a check that the built app uses no macOS framework or function newer than the oldest macOS version Cmdr supports.
- On demand: a 30-minute stress test of repeated copies from an SMB server.
A release waits for GitHub’s checks: before tagging, the release script requires a full, green run of every check on the exact commit being released. The script enforces this, not GitHub.
Not in place: fuzzing (random-input testing of the parsers for network protocols, archives, PDFs, and images) and automated tests of the macOS-only code on GitHub (the per-push runs use Linux).
Dependencies
- Vulnerability and license policy (
cargo-deny): any known vulnerability in a Rust crate that ships in the Mac app fails the check, at any depth. Licenses must be on an allowlist, and crates may only come from crates.io (no Git sources, no wildcard versions). Two advisories are ignored on purpose, each with a written reason: one is in a test-only crate, and one (a timing issue in RSA) affects only SFTP logins with an RSA key file. - When the scans run:
cargo-denyruns on the maintainer’s Mac whenever the dependencies or the policy change. On GitHub,cargo-denyandcargo-audit(which also covers Linux-only and test-only crates) check the locked versions every day. So a new advisory for a dependency that hasn’t changed shows up within a day. - A three-day wait: a new version of any dependency is used only once it’s been public for
three days. Renovate (the update bot) and pnpm (the JavaScript package manager) both enforce this. The
exceptions are two libraries the maintainer writes himself (
smb2andmtp-rs). - JavaScript packages: pnpm refuses a package whose publisher trust drops (for example after an ownership transfer), and runs install scripts only for packages on a short allowlist.
- Locked versions: both lockfiles are in the repository, and GitHub installs with the lockfile frozen. Tools and compilers have pinned versions too. A program downloaded during the build (the local AI server) is checked against a fixed SHA-256 hash.
- How updates happen: Renovate proposes minor updates every Monday and major ones monthly. Updates for the website and server merge automatically once their checks pass. Updates for the Mac app and all major versions go through a weekly manual pass, because they often need code changes. Security fixes skip the schedule: GitHub’s Dependabot alerts tell Renovate about a vulnerable dependency, and Renovate proposes the fixed version as soon as it’s three days old.
- In numbers: in the last 92 days, locked dependency versions changed in 139 commits on 63 different days, and at least once in every one of the 14 weeks.
Build and release
- Who can release: the maintainer, by pushing a version tag to GitHub. He’s the only person with write access to the repository.
- A guard step refuses a tag that would offer users an older version than the current one.
- Where it builds: GitHub-hosted macOS machines, a fresh one per build. The machine image is pinned, and the build stops if the macOS SDK isn’t the expected one. Three builds run in parallel (Apple Silicon, Intel, and universal). From tag to published release takes 35–50 minutes.
- Signing: each build is signed with Apple’s Developer ID, notarized by Apple, and signed again with a separate key for the updater. Each release publishes SHA-256 checksums. Details are on the main page: Signing, notarization, and updates.
- Provenance and SBOMs: once a release is published, a separate step downloads every file
back from it and signs a build attestation for each one (SLSA Build Level 2). It also publishes CycloneDX
SBOMs (software bills of materials) for the Rust and JavaScript dependencies, which from the next release on
are signed and tied to the builds they describe. Check any download with
gh attestation verify <file> --repo vdavid/cmdr. - Not in place: the signing keys are stored as GitHub repository secrets without a protected environment, release tags aren’t signed, and there’s no reproducible build.
Update delivery
- How: the app checks for a new version at launch and every three hours. It downloads the update, checks its signature, installs it in place, and asks the user to restart. It only ever installs a newer version. Users can turn this off per Mac; IT can’t yet control it centrally.
- How often: 22 releases in the last 92 days, from 0.30.0 to 0.46.1. The median gap between releases was 2.8 days, and the longest was 9.6 days.
- How fast users get it: across four recent releases, 52% of active installs ran the new version within 24 hours (91 of 175), and 71% on the third day (115 of 162). Cmdr is in open beta, so these are small numbers, and installs with usage stats turned off aren’t counted.
- From a fix to users: a fix ships with the next release (usually within a few days), the release is public 35–50 minutes after the tag, and running copies find it within three hours.
Vulnerability response
How to report a vulnerability and what response times to expect: Report a vulnerability, and the full policy in SECURITY.md. Security fixes are listed under “Security” in the changelog. All of them so far were found internally.
Every RustSec advisory in a Rust dependency that Cmdr fixed in the last 92 days, newest first:
- RUSTSEC-2026-0285
(
rustls): published 2026-09-14, fixed in 1c7057a09 on 2026-09-16, released in 0.46.0 on 2026-09-17. 3 days from advisory to release. - RUSTSEC-2026-0258
(
h2): published 2026-08-17, fixed in e21872235 on 2026-08-19, released in 0.39.0 on 2026-08-19. 2 days from advisory to release. - RUSTSEC-2026-0221
(
event-listener, an “unsound code” warning, not a known exploit): published 2026-07-13, fixed in 63a0858f3 on 2026-08-03, released in 0.38.0 on 2026-08-11. 29 days from advisory to release. - RUSTSEC-2026-0185
(
quinn-proto, found by the full scan; today’s Mac build doesn’t include this crate): published 2026-06-22, fixed in 584aa27fb on 2026-06-25, released in 0.30.0 on 2026-06-28. 6 days from advisory to release. - RUSTSEC-2026-0186
(
memmap2, an “unsound code” warning): published 2026-06-20, fixed in 584aa27fb on 2026-06-25, released in 0.30.0 on 2026-06-28. 8 days from advisory to release.
Supported platforms
- macOS 12 Monterey and later, on Apple Silicon and Intel Macs. macOS 10.15 Catalina and 11 Big Sur work on a best-effort basis: the app runs, but small visual issues there get fixed last.
- Only the latest version gets fixes. Automatic updates keep most installs on it.
- No automated test runs the app on an older macOS version. A release-time check catches system frameworks and functions that are too new for the oldest supported version. That check exists because two releases in this period (0.42.0 and 0.46.0) wouldn’t start on Catalina; both were fixed in a follow-up release.
- No Linux or Windows release. Much of the code also builds on Linux, which is where GitHub runs its tests, but that isn’t a supported product.
How the numbers were measured
- Releases: publish dates of GitHub releases.
-
Dependency changes: commits that changed
Cargo.lockorpnpm-lock.yaml. This includes dependencies added or removed with a feature, not only version updates. - Update adoption: Cmdr’s own usage stats. For each release, the installs active in a 24-hour window, and how many of them ran the new version at their last check-in. Four releases that had at least three days before the next one: 0.37.0, 0.39.0, 0.41.0, and 0.46.1.
- Advisories: publish dates from the RustSec advisory database, fix dates from Git, and release dates from GitHub.
- Test and code counts: a count of test functions and
unsafeblocks in the source. - Build time: the four most recent successful release runs on GitHub.
The exact commands are in the page’s source, apps/website/src/lib/trust-development.ts, so
anyone can repeat them.