Skip to content

feat: add recipient-keyed encrypted drops and CLI#1

Open
EmmanuelKeifala wants to merge 7 commits into
abdullah4tech:mainfrom
EmmanuelKeifala:main
Open

feat: add recipient-keyed encrypted drops and CLI#1
EmmanuelKeifala wants to merge 7 commits into
abdullah4tech:mainfrom
EmmanuelKeifala:main

Conversation

@EmmanuelKeifala

@EmmanuelKeifala EmmanuelKeifala commented Jul 16, 2026

Copy link
Copy Markdown

Summary

  • Add atomic recipient-aware drop open and metadata endpoints.
  • Update the web flow to use recipient verification before consuming a view.
  • Add the Zig CLI for encrypted environment-file sharing, with module and critical-path documentation.

Test Plan

  • go test ./...
  • zig build test
  • pnpm check
  • Fixture regeneration matches protocol-v1.json

Follow-up

  • Add Go coverage for concurrent one-view POST /open requests.

Summary by CodeRabbit

  • New Features
    • Enhanced secret creation to support recipient-keyed drops, including recipient public key support.
    • Added/updated metadata and open flows for recipient-protected secrets across the CLI and web.
    • Added stricter client-side handling for endpoints, request sizing, and redirect behavior.
  • Security
    • Stronger public key validation and safer handling of partial/invalid recipient material.
    • Continued authenticated encryption with hardened, atomic output writing.
  • Documentation
    • Expanded CLI README with updated usage, endpoint selection, and share/pull behavior.

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

@EmmanuelKeifala is attempting to deploy a commit to the Abdullah Mustapha's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57674656-9638-4f4e-bb47-e44ef26db1c2

📥 Commits

Reviewing files that changed from the base of the PR and between 9aff94f and ec96b30.

📒 Files selected for processing (10)
  • ashdrop/api/main.go
  • ashdrop/api/main_test.go
  • ashdrop/api/store.go
  • ashdrop/cli/src/api.zig
  • ashdrop/cli/src/commands.zig
  • ashdrop/cli/src/config.zig
  • ashdrop/cli/src/files.zig
  • ashdrop/cli/src/identity.zig
  • ashdrop/cli/src/test_server.zig
  • ashdrop/web/src/routes/s/[id]/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (9)
  • ashdrop/cli/src/test_server.zig
  • ashdrop/api/store.go
  • ashdrop/web/src/routes/s/[id]/+page.svelte
  • ashdrop/cli/src/config.zig
  • ashdrop/cli/src/files.zig
  • ashdrop/cli/src/api.zig
  • ashdrop/api/main.go
  • ashdrop/cli/src/identity.zig
  • ashdrop/cli/src/commands.zig

📝 Walkthrough

Walkthrough

The change adds recipient-keyed secret support across the Go API, web client, and a new Zig CLI. It includes P-256/ECDH encryption, persistent identities, canonical links, HTTP handling, safe atomic output, command flows, endpoint configuration, tests, fixtures, and documentation.

Changes

Recipient-keyed drops

Layer / File(s) Summary
API and storage contracts
ashdrop/api/*.go
The API validates and persists recipient public keys, exposes recipient-aware metadata and open responses, migrates SQLite storage, and restricts rate limiting to secret creation.
Crypto, identity, links, and endpoint resolution
ashdrop/cli/src/crypto.zig, identity.zig, links.zig, config.zig
The CLI implements recipient encryption/decryption, secure P-256 identity persistence, endpoint validation and precedence, and canonical receive/drop references.
CLI transport and safe file handling
ashdrop/cli/src/api.zig, files.zig, test_server.zig, testdata/*, build.zig*
The CLI adds bounded HTTP responses, error mapping, redirect rejection, atomic 0600 output, build steps, protocol fixtures, and HTTP test infrastructure.
Share and pull command flows
ashdrop/cli/src/commands.zig
The CLI parses commands, encrypts and uploads files, verifies recipient metadata before opening, decrypts responses, validates UTF-8, and writes output atomically.
CLI entry point and web recipient flow
ashdrop/cli/src/main.zig, ashdrop/web/src/**, ashdrop/cli/README.md, .gitignore
The CLI wires address/share/pull commands and diagnostics; the web client sends recipient keys and opens recipient-keyed drops on reveal; documentation and ignore rules are added.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Sender
  participant CLI
  participant API
  participant Recipient
  Sender->>CLI: Read and seal plaintext for recipient
  CLI->>API: Create secret with ciphertext, ephemeralPub, and recipientPub
  Recipient->>API: Request metadata
  API-->>Recipient: Recipient public key and viewsLeft
  Recipient->>API: Open recipient-keyed secret
  API-->>Recipient: Ciphertext, IV, and ephemeralPub
  Recipient->>Recipient: Derive key, decrypt, and write plaintext atomically
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: recipient-keyed encrypted drops plus the new CLI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
ashdrop/cli/src/test_server.zig (1)

78-85: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Read request bodies up to the API limit

The fixed 8 KiB buffer truncates any body larger than that, while files.max_api_body_size allows up to 96 KiB. Use a bounded allocating or streaming reader capped at files.max_api_body_size so valid create requests don’t fail JSON parsing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ashdrop/cli/src/test_server.zig` around lines 78 - 85, Update the
request-body reading logic in the test server around request.readerExpectNone
and body_buffer so it can read up to files.max_api_body_size (96 KiB) using a
bounded allocating or streaming reader instead of the fixed 8192-byte buffer.
Preserve the existing TestServerReadFailed handling and JSON validation via
std.json.parseFromSlice, ensuring complete valid create-request bodies are
parsed.
ashdrop/web/src/routes/s/[id]/+page.svelte (1)

54-62: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Legacy fetchSecret fallback skips exact recipient-key verification.

When metadata.recipientKeyed is true but metadata.recipientPub is falsy, control falls through to the pre-metadata fetchSecret path, which gates recipient-sealed vs no-local-key solely on hasMyKeyPair() — there's no recipientPub on FetchedSecret to check against, so this path can't perform the exact-match verification the metadata path just added. This should be unreachable if the API always populates recipientPub for recipient-keyed drops, but if that invariant is ever violated, it silently bypasses the new recipient-verification guarantee this PR introduces.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ashdrop/web/src/routes/s/`[id]/+page.svelte around lines 54 - 62, Update the
legacy fetchSecret flow so recipient-keyed secrets are not classified using
hasMyKeyPair() alone. Propagate recipientPub through FetchedSecret/fetchSecret
and reuse the exact recipient-key verification used by the metadata path; if
recipientPub is missing, fail closed instead of entering recipient-sealed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ashdrop/api/main.go`:
- Line 7: Update validPublicKey and the key-creation/recipient-metadata
verification flows to parse both SEC1 keys as P-256 points before storing or
accepting them. Reject any key that is not a valid on-curve P-256 point,
including ephemeralPub, rather than validating only the canonical 65-byte
uncompressed format.

In `@ashdrop/cli/src/api.zig`:
- Around line 117-129: Add a per-request deadline around the http_client.fetch
call in the request flow, ensuring stalled header or body reads terminate rather
than blocking indefinitely. Use the available Zig snapshot-compatible mechanism,
preserve the existing redirect behavior and error mapping, and ensure the
deadline is applied to every HTTP request.

In `@ashdrop/cli/src/config.zig`:
- Around line 22-36: Update validateEndpoint to reject plain HTTP for
non-loopback hosts while continuing to allow HTTPS and explicit loopback HTTP
addresses (localhost and loopback IPs). Reuse the parsed host/address data to
identify loopback endpoints, and add a validation test covering rejection of
remote http:// endpoints.

In `@ashdrop/cli/src/files.zig`:
- Around line 119-130: Update writeAtomically after the output.atomic_file
link/replace operation to sync the destination’s parent directory before
returning success. Preserve the existing force and OutputExists handling, and
use the atomic file’s parent-directory sync facility.

In `@ashdrop/cli/src/identity.zig`:
- Around line 33-66: Update save to write the encoded identity to a private
temporary file in the target directory, sync and close it, then atomically
install it as filename without replacing an existing identity. Preserve
IdentityAlreadyExists mapping for an existing destination, clean up the
temporary file on failure, and sync dir after installation before create
returns.

---

Nitpick comments:
In `@ashdrop/cli/src/test_server.zig`:
- Around line 78-85: Update the request-body reading logic in the test server
around request.readerExpectNone and body_buffer so it can read up to
files.max_api_body_size (96 KiB) using a bounded allocating or streaming reader
instead of the fixed 8192-byte buffer. Preserve the existing
TestServerReadFailed handling and JSON validation via std.json.parseFromSlice,
ensuring complete valid create-request bodies are parsed.

In `@ashdrop/web/src/routes/s/`[id]/+page.svelte:
- Around line 54-62: Update the legacy fetchSecret flow so recipient-keyed
secrets are not classified using hasMyKeyPair() alone. Propagate recipientPub
through FetchedSecret/fetchSecret and reuse the exact recipient-key verification
used by the metadata path; if recipientPub is missing, fail closed instead of
entering recipient-sealed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e3b4fdd-c482-4a20-94a6-9de022c1d89d

📥 Commits

Reviewing files that changed from the base of the PR and between 772497a and 9aff94f.

📒 Files selected for processing (22)
  • .gitignore
  • ashdrop/api/main.go
  • ashdrop/api/store.go
  • ashdrop/cli/README.md
  • ashdrop/cli/build.zig
  • ashdrop/cli/build.zig.zon
  • ashdrop/cli/src/api.zig
  • ashdrop/cli/src/commands.zig
  • ashdrop/cli/src/config.zig
  • ashdrop/cli/src/crypto.zig
  • ashdrop/cli/src/crypto_test.zig
  • ashdrop/cli/src/files.zig
  • ashdrop/cli/src/identity.zig
  • ashdrop/cli/src/links.zig
  • ashdrop/cli/src/main.zig
  • ashdrop/cli/src/test_server.zig
  • ashdrop/cli/test.zig
  • ashdrop/cli/testdata/generate-protocol-v1.mjs
  • ashdrop/cli/testdata/protocol-v1.json
  • ashdrop/web/src/lib/api.ts
  • ashdrop/web/src/routes/drop-for/[pubkey]/+page.svelte
  • ashdrop/web/src/routes/s/[id]/+page.svelte

Comment thread ashdrop/api/main.go
Comment thread ashdrop/cli/src/api.zig
Comment thread ashdrop/cli/src/config.zig Outdated
Comment thread ashdrop/cli/src/files.zig
Comment thread ashdrop/cli/src/identity.zig Outdated
@EmmanuelKeifala

EmmanuelKeifala commented Jul 16, 2026

Copy link
Copy Markdown
Author

Review follow-up fixed in ec96b30:

  • The CLI test server now reads JSON bodies up to the 96 KiB API cap, with a regression that sends a create request larger than 8 KiB.
  • Recipient-keyed metadata without a recipient identity now fails closed in the web flow. The API also rejects half-specified key pairs and treats legacy partial key records as unavailable.
  • The test-server shutdown path now wakes and joins the accept loop before closing its listener.

@abdullah4tech

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant