Security
DiskPush runs a program that copies files between machines, with credentials, sometimes with a delete flag. This is the reasoning behind how it does that.
No shell
spawn('rsync', validatedArgs, { shell: false })
Never:
exec(`rsync ${userInput}`)
Every argument comes from a typed option, a parsed endpoint, or the user's
explicit pass-through list. Nothing is interpolated into a string that a shell
will read back. A directory named weird $(touch /tmp/pwned); \id` 'q' & dir`
is copied, with that name, and nothing executes. There is a test that asserts
exactly this against the real rsync binary.
The one exception, and how it is handled
ssh host command... always runs the command through the remote login shell,
joining argv with spaces first. Server-to-server orchestration cannot avoid
that shell, so DiskPush quotes for it: every token of the remote rsync command
is POSIX single-quoted (' becomes '\'') and the whole command is passed as
one argv element. This is the only place in the codebase permitted to build a
shell string, it is one small function, and it is tested with hostile paths.
Remote arguments and the remote shell
This one is easy to miss. When rsync talks to a remote host, it historically
passed the remote path on the remote command line, where the remote login
shell expanded it. A path containing $(...) was therefore remote code
execution, in rsync itself, with no help from the client.
rsync 3.0.0 added --protect-args to send arguments through the protocol
instead. rsync 3.2.4 made that the default and renamed it --secluded-args.
DiskPush gates on the version:
| Local rsync | Behaviour |
|---|---|
| 3.2.4 or newer | Already the default. No flag added. |
| 3.0.0 to 3.2.3 | --protect-args passed explicitly. |
| Older than 3.0.0 | Not possible. DiskPush warns that paths with shell metacharacters are unsafe against that host. |
| Unknown | --protect-args passed anyway, so an old rsync fails loudly instead of silently handing the path to a shell. |
Host keys
- Host keys are verified on every connection.
- A new host prompts once, showing the
SHA256:fingerprint, and is recorded on acceptance. - A changed host key blocks the connection. It is not a prompt. If the change was expected, the old entry has to be removed deliberately.
- A key marked
@revokedinknown_hostsis refused. - Both plain and HMAC-SHA1 hashed
known_hostsentries are understood, so a hashed file does not read as empty and make every host look new. StrictHostKeyChecking=accept-newis the weakest position DiskPush takes. There is no setting that disables host key checking globally.
Credentials
- No passwords or key passphrases are stored in the local database. Look at
packages/database/src/migrations.ts: there is no column for them. - SSH agent authentication is preferred, and stores nothing at all.
- Where a secret must persist, it belongs in OS-backed secure storage, not in the SQLite file that a backup would sweep up.
- Credentials are never written to logs. "Copy command" redacts.
Destructive operations
- No delete flag is generated by default, by any command except
mirror. - A delete-enabled job cannot be constructed as a live job until its dry run has been reviewed and confirmed. This is enforced in the argument builder, not in the UI, so every surface inherits it.
--deleteand friends in the pass-through section are refused. Passing them through would let asyncdelete files without ever showing the preview whose entire purpose is to show you that.--remove-source-filesis refused. DiskPush copies; it does not move.- Unattended mirroring is possible, per profile, and off by default.
Agent forwarding
Off by default, opt-in per connection.
While a forwarded session is open, anyone with root on the intermediate host can use the agent to authenticate as you, anywhere the agent's keys are accepted. That is a real and specific cost. For unattended server-to-server work, a dedicated restricted key on the source host is the better answer.
Electron
nodeIntegration: false,contextIsolation: true, sandboxed renderer.- The renderer gets no filesystem or process APIs, only narrowly scoped IPC
operations over
contextBridge. - Every IPC input is validated with Zod in the main process. Renderer-supplied paths, flags and IDs are treated as untrusted input, because a renderer compromise should not become a shell.
- Strict CSP, no
eval, no remote content, navigation restricted to app pages.
What DiskPush does not do
- It does not upload your files anywhere.
- It does not relay server-to-server payloads through the desktop or through any hosted service, and will not silently start.
- It does not phone home. Analytics on the website never receives connection names, hostnames, paths, filenames, or anything about a transfer.
Reporting
Security issues: security@profullstack.com. Please do not open a public issue for anything exploitable.

