No description
  • Scheme 87.7%
  • Shell 6.9%
  • Makefile 3.6%
  • C 1.7%
Find a file
2026-07-30 15:46:23 -06:00
.builds ci: set umask 022 in package/verify tasks too (jerbuild creates group-writable cache dirs during pkg operations) 2026-07-22 13:10:55 -06:00
.jerboa Harden drive release evidence privacy 2026-06-26 13:41:31 -06:00
bin Security hardening and release readiness 2026-06-23 10:49:27 -06:00
docs Harden credential storage and release packaging 2026-07-11 17:22:04 -06:00
protonstorage client: range-block overlap helper; document pre-offset skip blocker 2026-07-22 16:53:35 -06:00
scripts Harden credential storage and release packaging 2026-07-11 17:22:04 -06:00
support Harden credential storage and release packaging 2026-07-11 17:22:04 -06:00
test client: range-block overlap helper; document pre-offset skip blocker 2026-07-22 16:53:35 -06:00
.gitignore Security hardening and release readiness 2026-06-23 10:49:27 -06:00
.jerbuild.release-darwin Harden credential storage and release packaging 2026-07-11 17:22:04 -06:00
.jerbuild.release-elf Harden credential storage and release packaging 2026-07-11 17:22:04 -06:00
AGENT.md Initial Jerboa Proton Drive implementation 2026-06-04 19:52:10 -06:00
AGENTS.md docs: remove jerboa-emacs restriction 2026-07-30 15:46:23 -06:00
dependency-lock.tsv Harden credential storage and release packaging 2026-07-11 17:22:04 -06:00
jpkg.sexp Add jpkg packaging 2026-07-21 15:09:23 -06:00
LICENSE Switch to MIT license 2026-07-21 13:41:55 -06:00
main.ss Security hardening and release readiness 2026-06-23 10:49:27 -06:00
Makefile build: add lint target 2026-07-21 11:57:17 -06:00
README.md docs: align README with implemented features, move unimplemented to planned section 2026-07-21 12:05:55 -06:00
SECURITY.md Harden drive release evidence privacy 2026-06-26 13:41:31 -06:00

jerboa-drive

Jerboa-native encrypted remote drive and storage access.

This project is intended to become a single deployable Jerboa application for remote storage, with YubiKey-gated access and no rclone or Go helper in the runtime path. The Proton Drive backend is currently experimental; the primary backend is S3-compatible storage with client-side encryption.

User Documentation

Start with docs/user-guide.md for installation, local state layout, authentication, encrypted credential vault setup, listing remote files, reading and writing Drive content, FUSE mounts, configuration variables, and troubleshooting. See docs/security-model.md for credential, local state, FUSE, native-code, and release-gate expectations.

Short installed-binary form:

jdrive login --username you@example.com
jdrive ls --username you@example.com
jdrive creds store --profile default --username you@example.com
jdrive tree --profile default --max-depth 3

The protonstorage command and historical drive ... prefix still work, but neither is required. If Proton requires TOTP and you did not pass --totp-code, jdrive prompts for the current code.

After jdrive creds store, commands such as jdrive ls, jdrive volumes, and jdrive mount use the selected profile's encrypted credentials automatically when you do not pass --username or token options.

Encrypted S3 Backend

The S3 backend stores local profile state under ~/.jdrive, not XDG. It uses AWS-compatible credentials from the normal AWS environment/profile files, wraps a random drive key in a local scrypt + chacha20-poly1305 vault, encrypts the remote manifest, and stores file contents under opaque S3 object keys.

Bootstrap a profile:

jdrive s3 init --bucket BUCKET --prefix jdrive --region us-east-1
jdrive s3 profile-status
jdrive s3 unlock-test

For S3-compatible providers, add an HTTPS --endpoint URL --path-style when needed. Plaintext S3 endpoints are rejected. Use --aws-profile NAME to select an AWS profile.

Add optional built-in YubiKey PIV unlock material:

jdrive s3 init --bucket BUCKET --yubikey-mode piv

Daily operations:

jdrive s3 mkdir /backups
jdrive s3 cp ./notes.txt /backups/notes.txt
jdrive s3 cp -r ./backup-dir /backups
jdrive s3 cp /backups/notes.txt ./notes-restored.txt
jdrive s3 cp -r /backups ./restored-backups
jdrive s3 cp -r /backups /archive
jdrive s3 mv /archive/backups/notes.txt /archive/notes.txt
jdrive s3 sync ./backup-dir /backups --delete
jdrive s3 check ./backup-dir /backups
jdrive s3 ls /backups
jdrive s3 cat /backups/notes.txt
jdrive s3 cat /backups/notes.txt --offset 0 --size 4096
jdrive s3 get /backups/notes.txt ./notes-restored.txt
jdrive s3 rm -r /backups/backup-dir

Set JDRIVE_VAULT_PASSWORD or pass --vault-password-env ENV_NAME for automation. Set JDRIVE_PIV_PIN or pass --piv-pin-env ENV_NAME when the profile was initialized with --yubikey-mode piv.

Files are stored as independently encrypted chunks by default. Override the 64 MiB default with --chunk-size N for testing or provider tuning. Encrypted chunk objects larger than 16 MiB use S3 multipart upload internally, so the bucket provider still only sees encrypted object parts. Manifest commits use S3 conditional writes against the manifest ETag; concurrent writers fail with a retryable conflict instead of silently overwriting each other.

sync and cp treat paths beginning with / as encrypted remote paths unless the source is an existing local absolute path. That supports local-to-remote, remote-to-local, and remote-to-remote encrypted copies. check and sync support simple * wildcards through repeated --include PATTERN and --exclude PATTERN.

Decrypted remote names are validated again when a manifest is loaded. File output — single-file get and recursive downloads alike — uses a retained destination-directory descriptor, no-follow component walks, and atomic file publication, so traversal components and concurrent symlink replacement cannot redirect output outside the selected local root.

Runtime Status

Current runtime code is Scheme/Jerboa only:

  • main.ss - script entry point.
  • protonstorage/cli.ss - command dispatch.
  • protonstorage/drive/api.ss - direct Proton Drive HTTPS API calls through Jerboa HTTPS/JSON, including block upload reservation and multipart encrypted-block upload.
  • protonstorage/drive/auth.ss - native SRP, FIDO2/YubiKey, and TOTP auth flow.
  • protonstorage/drive/discovery.ss - active volume/share/root resolution.
  • protonstorage/drive/crypto.ss - Drive name hashing, key/passphrase decrypt wrappers, disabled raw content-session-key and block decrypt calls, disabled OpenPGP write helpers, and encrypted-block SHA-256 verification. The disabled OpenPGP/Drive crypto paths fail closed until the Proton backend is RustSec-clean.
  • protonstorage/drive/client.ss - end-to-end root unlock, decrypted traversal, file read pipeline, folder-path resolution, folder creation, file upload, and revision replacement.
  • protonstorage/drive/fs.ss - recursive FUSE metadata mapping with reader, mkdir, buffered create/write, unlink/rmdir, and rename/move callbacks through jerboa-fuse.
  • protonstorage/drive/local.ss - local CLI/FUSE profile paths, cache/staging directory setup, and foreground daemon marker status.
  • protonstorage/drive/write.ss - create-folder and create-file request payload assembly from unlocked keys and generated Drive crypto, encrypted block upload plans, encrypted xattrs, manifest signatures, and revision commit payloads. The live crypto assembly paths currently fail closed while Proton OpenPGP/Drive crypto is disabled.
  • protonstorage/s3/config.ss - S3-compatible endpoint, credential, region, path-style, and object-key helpers.
  • protonstorage/s3/drive.ss - encrypted S3 drive profile/vault, encrypted manifest, encrypted object upload/download/list, chunked encrypted storage, ranged reads, sync/check helpers, checksum verification, retry wrappers, and optional YubiKey PIV unlock material through jerboa-yubikey.

The cloned Go/rclone/Proton client projects under ~/mine are references only. They are used to map endpoint shapes and crypto behavior while porting.

Implemented API Surface

The Jerboa module (protonstorage drive api) currently covers:

  • /core/v4/users
  • /core/v4/keys/salts
  • /core/v4/addresses
  • /core/v4/addresses/{addressID}
  • /drive/volumes
  • /drive/shares
  • /drive/shares/{shareID}
  • /drive/shares/{shareID}/links/{linkID}
  • /drive/shares/{shareID}/folders/{linkID}/children
  • /drive/shares/{shareID}/links/{linkID}/checkAvailableHashes
  • /drive/shares/{shareID}/links/{linkID}/move
  • /drive/shares/{shareID}/folders
  • /drive/shares/{shareID}/files
  • /drive/shares/{shareID}/files/{linkID}/revisions
  • /drive/shares/{shareID}/files/{linkID}/revisions/{revisionID}
  • /drive/shares/{shareID}/folders/{linkID}/trash_multiple
  • /drive/shares/{shareID}/folders/{linkID}/delete_multiple
  • /drive/shares/{shareID}/trash
  • /drive/blocks

Inspect the native surface:

make run ARGS='drive status'

Authenticated calls use Proton session material from the environment:

export PROTONDRIVE_UID='...'
export PROTONDRIVE_ACCESS_TOKEN='...'

Examples:

make run ARGS='drive user'
make run ARGS='drive volumes'
make run ARGS='drive shares'
make run ARGS='drive link --share SHARE_ID --link LINK_ID'
make run ARGS='drive children --share SHARE_ID --link LINK_ID'

Fresh native login uses SRP and FIDO2/YubiKey when Proton requests it:

export PROTONDRIVE_PASSWORD='...'
make run ARGS='drive login --username you@example.com'
make run ARGS='drive root-children-decrypted --username you@example.com'
make run ARGS='drive tree-decrypted --username you@example.com --max-depth 3'

For local CLI use, Proton login credentials can be stored in the selected profile as an encrypted vault protected by a separate vault password. The vault uses scrypt and chacha20-poly1305; the vault password is still required to unlock stored credentials. After the first successful full login, the encrypted vault also stores Proton's reusable session tokens and key pass so later commands can refresh the session without TOTP. Profiles live under ~/.jproton/profiles/<profile>/ by default.

make run ARGS='drive credentials store --profile default'
make run ARGS='drive credentials status --profile default'
make run ARGS='drive credentials unlock-test --profile default'
make run ARGS='drive root-children-decrypted --profile default'

For automation, set PROTONSTORAGE_VAULT_PASSWORD or pass --vault-password-env ENV_NAME instead of typing the vault password.

The default FUSE command mounts a decrypted, read-only Drive tree:

mkdir -p /tmp/proton-root
make run ARGS='drive mount-root --username you@example.com --mountpoint /tmp/proton-root'

The writable mount buffers created file data in memory and commits it through the native encrypted upload/revision pipeline when the file handle is flushed or released. It also supports folder creation, unlink/rmdir through Proton trash, and rename/move through the Proton move endpoint:

mkdir -p /tmp/proton-rw
make run ARGS='drive mount-root-write --username you@example.com --mountpoint /tmp/proton-rw'

The product-level FUSE CLI surface also has a profile-aware mount command and local state inspection commands. drive daemon start currently runs the same FUSE daemon in the foreground so it can be supervised by a service manager; a background control socket is not implemented yet (see Planned / Not Yet Implemented).

make run ARGS='drive cache init --profile default'
make run ARGS='drive cache status --profile default'
make run ARGS='drive daemon status --profile default'
make run ARGS='drive mount --username you@example.com --mountpoint /tmp/proton-root'
make run ARGS='drive mount --username you@example.com --mountpoint /tmp/proton-rw --writable'
make run ARGS='drive daemon start --username you@example.com --mountpoint /tmp/proton-rw --writable --foreground'

File reads use the same recursive pipeline and write plaintext bytes to stdout:

make run ARGS='drive read-file --username you@example.com --link FILE_LINK_ID --offset 0 --size 4096'

The current read path resolves the root, unlocks the file node key, decrypts and verifies Proton Drive ContentKeyPacket values, selects the active revision, downloads storage blocks, verifies each encrypted block hash, and decrypts block packets with the content session key.

The write-side surface can now generate locked Drive node keys, encrypt signed link names, encrypt/sign node passphrases, reserve upload blocks, and submit encrypted block payloads using Proton's Block multipart field. It can also generate ContentKeyPacket values and encrypt content blocks with the generated session key, assemble create-folder/create-file payloads, build encrypted block metadata with encrypted detached signatures, encrypt revision xattrs, sign revision manifests, upload blocks, and commit file revisions under decrypted folder paths.

Write commands require a fresh native login because Drive key unlock needs mailbox key material:

make run ARGS='drive create-folder --username you@example.com --parent-path / --name docs'
make run ARGS='drive upload-file --username you@example.com --parent-path /docs --path ./notes.txt --mime-type text/plain'
make run ARGS='drive rename-child --username you@example.com --parent-path /docs --name notes.txt --new-name notes-renamed.txt'
make run ARGS='drive move-child --username you@example.com --parent-path /docs --name notes-renamed.txt --new-parent-path /archive'
make run ARGS='drive trash-child --username you@example.com --parent-path /archive --name notes-renamed.txt'

drive create-root-folder and drive upload-root-file remain as root-folder aliases. If a file already has an unfinished draft revision, uploads stop by default. Pass --replace-existing-draft to delete that draft revision and create a new one for an active file. Cross-folder move and FUSE rename re-encrypt the moved node passphrase for the destination parent key.

Reference Clones

Relevant reference projects cloned under ~/mine:

  • /Users/user/mine/rclone
  • /Users/user/mine/Proton-API-Bridge
  • /Users/user/mine/rclone-go-proton-api
  • /Users/user/mine/go-proton-api
  • /Users/user/mine/gopenpgp
  • /Users/user/mine/go-crypto
  • /Users/user/mine/go-srp
  • /Users/user/mine/proton-drive-upstream
  • /Users/user/mine/proton-ios-drive
  • /Users/user/mine/proton-android-drive
  • /Users/user/mine/proton-webclients
  • /Users/user/mine/jerboa-proton-bridge
  • /Users/user/mine/jerboa-yubikey
  • /Users/user/mine/jerboa-crypto
  • /Users/user/mine/jerboa-fuse

Build and Test

make
make binary
make linux-amd64
make linux-arm64
make freebsd-amd64
make test
make integration
make s3-integration
make doctor
make bundle
make release-evidence

make integration skips unless PROTONDRIVE_INTEGRATION=1 is set. When enabled, it uses PROTONDRIVE_USERNAME and PROTONDRIVE_PASSWORD, creates a disposable folder under PROTONDRIVE_TEST_PARENT_PATH or /, uploads one small file, moves it between two disposable folders, and trashes the disposable root during cleanup.

make s3-integration skips unless JDRIVE_S3_INTEGRATION=1 is set. When enabled, it requires JDRIVE_S3_TEST_BUCKET, uses standard AWS credentials or JDRIVE_S3_TEST_AWS_PROFILE, writes under JDRIVE_S3_TEST_PREFIX or a disposable prefix, syncs two encrypted chunked files, checks them, reads a range, restores a file, and removes remote test entries. Set JDRIVE_S3_TEST_MULTIPART=1 to also upload a large encrypted object through provider multipart upload.

make bundle builds the host binary and writes dist/protonstorage-<os>-<arch>.tar.gz with the binary, native Jerboa libraries, and a native launcher. The release binary records only bundle-relative native-library paths. The launcher validates the bundle's ownership and modes and removes inherited loader and development-native selectors before starting it; development make run keeps its explicit local library workflow.

Release dependency and provenance evidence is documented in docs/dependency-provenance.md and docs/release-evidence.md. make release-evidence writes the local evidence bundle under dist/release-evidence/ and fails closed while upstream dependency gates remain blocked.

Planned / Not Yet Implemented

The following are not implemented yet. They are listed here so the README does not overstate current capability:

  • Daemon control socket / background lifecycle. drive daemon start runs the FUSE daemon in the foreground only, so it can be supervised by a service manager. There is no background daemon and no control socket.
  • Durable operation queue. Create/upload/rename/move/trash operations are not journaled; there is no persistent queue that survives a crash or restart.
  • Multi-writer merge. Concurrent writers are handled fail-fast: manifest commits use S3 conditional writes against the manifest ETag, so a conflicting writer fails with a retryable error instead of being merged. There is no merge/conflict-resolution policy beyond that.
  • Indexed cache states. On-demand cache-state tracking (online-only, available, pinned, dirty, uploading) is not implemented. drive cache init and drive cache status only create and report local cache/staging paths; metadata.db and indexed cache state are planned persistence layers.

Other remaining work:

  • Prove the encrypted S3 live integration against each target provider.
  • Add richer metadata update operations beyond size-changing writes and rename/move.
  • Collapse the native dynamic library bundle further when Jerboa supports a fully static bridge path.