DiskPushDiskPush

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 @revoked in known_hosts is refused.
  • Both plain and HMAC-SHA1 hashed known_hosts entries are understood, so a hashed file does not read as empty and make every host look new.
  • StrictHostKeyChecking=accept-new is 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.
  • --delete and friends in the pass-through section are refused. Passing them through would let a sync delete files without ever showing the preview whose entire purpose is to show you that.
  • --remove-source-files is 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.