Skip to content

feat(sdk): select AES-CTR encryption for streaming media uploads#399

Merged
FSM1 merged 13 commits into
mainfrom
feat/sdk-ctr-upload
Mar 29, 2026
Merged

feat(sdk): select AES-CTR encryption for streaming media uploads#399
FSM1 merged 13 commits into
mainfrom
feat/sdk-ctr-upload

Conversation

@FSM1

@FSM1 FSM1 commented Mar 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • SDK uploadFile() now auto-selects AES-256-CTR encryption for media files >256KB (video/mp4, video/webm, audio/mpeg, audio/mp4, audio/flac, etc.), enabling the streaming playback pipeline (Service Worker interception, range-request decryption, encrypted badge)
  • Previously hardcoded to GCM for all files, so CTR streaming never activated for new uploads
  • Centralizes encryption mode selection in @cipherbox/sdk-core/encryption-mode.ts with input validation
  • Fixes pre-existing type mismatches in sdk-core test mocks and fragile sdk-core mock patterns in SDK tests
  • Un-skips the CTR encrypted badge E2E test

Test plan

  • sdk-core unit tests pass (130/130, including 13 new encryption-mode tests)
  • sdk unit tests pass (136/136, excluding pre-existing live integration test failures)
  • sdk + sdk-core typecheck clean
  • Streaming playback E2E suite passes locally (6/6 — upload CTR, preview, CTR badge, decrypt progress, GCM fallback, blob URL)
  • CI passes

🤖 Generated with Claude Code

FSM1 and others added 8 commits March 29, 2026 22:14
E2E fixes:
- batch-download: Extend beforeAll hook timeout from 30s default to
  120s — wallet login exceeds 30s in CI. Remove retry loop (was
  masking the timeout issue). Use clickClear() instead of Escape to
  dismiss selection bar after download.
- streaming-playback: Extend beforeAll hook timeout to 120s for the
  same reason.

CI optimization:
- Narrow the `desktop` change filter in ci.yml to only Rust-relevant
  paths (src-tauri/src, vendor, crates, Cargo.toml/lock, vectors).
  Removes TypeScript-only paths (desktop frontend, capabilities,
  resources, vite config) that don't affect cargo check/test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 123fec3c406d
- Match beforeAll hook timeout to suite timeout (180s) in streaming-playback
- Rename CTR badge test to reflect conditional behavior; skip with
  explicit message when streaming pipeline isn't active instead of
  silently passing
- Re-add .github/workflows/ci.yml to desktop filter so CI workflow
  changes trigger cargo checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: cdba1752d779
The decrypt SW was registered from /src/workers/decrypt-sw.ts with
scope '/', but browsers reject this without a Service-Worker-Allowed
header. Vite 7's dev server doesn't reliably send custom headers on
transformed file responses.

Fix: add public/decrypt-sw-dev.js wrapper that loads the real SW via
importScripts(). Files in public/ are served from the root scope, so
the browser allows scope: '/' without the header.

Also skip the CTR badge E2E test — SDK uploadFile() hardcodes GCM
encryption, so CTR mode is never triggered for new uploads. Tracked
as a TODO in .planning/TODO.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 682dcac6efa4
…on for media uploads

Entire-Checkpoint: 3327af6c8cb5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 5a4b0c7e875c
Switch decrypt-sw-dev.js from importScripts() (classic-only) to ES
module import, and register with type: 'module' in dev mode. This is
more robust — importScripts() can't load ES modules if the SW source
ever gains import statements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 53a15e4b4d21
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 3cc55a32ce04
SDK uploadFile() hardcoded AES-256-GCM for all files, preventing the
CTR streaming playback pipeline from activating. Now the SDK client
auto-selects CTR mode for media files >256KB (video/mp4, audio/*, etc.),
matching the web app's selectEncryptionMode() logic. Also fixes
pre-existing type mismatches in sdk-core test mocks and un-skips the
CTR badge E2E test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: bd28f7731561
@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown

Walkthrough

The pull request adds support for AES-CTR streaming encryption mode selection in SDK file uploads. It introduces encryption mode detection based on file type and size, wires this mode parameter through the upload pipeline, and re-enables a corresponding E2E test. Test mocks are updated to reflect API response shape changes.

Changes

Cohort / File(s) Summary
Planning Documentation
.planning/todos/done/2026-03-29-sdk-uploadfile-hardcodes-gcm-wire-ctr-encryption-for-media-uploads.md
Records bug where SDK uploadFile() hardcodes GCM encryption, blocking CTR usage for eligible media; outlines fix plan to add encryptionMode parameter and propagate it through upload pipeline; notes E2E test re-enablement.
Encryption Mode Selection
apps/web/src/services/streaming-crypto.service.ts, packages/sdk/src/client.ts
Introduced STREAMING_MIME_TYPES allowlist and selectEncryptionMode(mimeType, size) helper to determine CTR vs GCM mode based on file type and 256KB size threshold.
Upload Core
packages/sdk-core/src/upload/index.ts
Added optional encryptionMode parameter to uploadFile(); conditionally selects IV generator (generateIv for GCM vs generateCtrIv for CTR) and encryption function (encryptAesGcm vs encryptAesCtr); propagates mode to file metadata.
Test Mock Updates
packages/sdk-core/src/__tests__/ipns.test.ts, packages/sdk-core/src/__tests__/vault.test.ts
Removed cid field from IPNS publish mock payloads and source field from resolve responses; added size and recorded properties to addToIpfs mock return values.
E2E Test Re-enablement
tests/web-e2e/tests/streaming-playback.spec.ts
Activated previously-skipped CTR encrypted badge test to verify CTR encryption mode is correctly applied to large video files.

Sequence Diagram

sequenceDiagram
    actor User
    participant Client as Client.uploadFile()
    participant ModeSelector as selectEncryptionMode()
    participant SDKCore as sdkCore.uploadFile()
    participant Crypto as Encryption Pipeline
    
    User->>Client: Upload media file (type, size)
    Client->>ModeSelector: selectEncryptionMode(mimeType, fileSize)
    ModeSelector-->>Client: 'CTR' or 'GCM' based on type & size
    Client->>SDKCore: uploadFile({..., encryptionMode})
    SDKCore->>Crypto: Select IV: generateCtrIv() or generateIv()
    SDKCore->>Crypto: Select cipher: encryptAesCtr() or encryptAesGcm()
    Crypto->>Crypto: Encrypt file data
    SDKCore->>SDKCore: createFileMetadata({encryptionMode})
    SDKCore-->>Client: UploadResult
    Client-->>User: Upload complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(sdk): select AES-CTR encryption for streaming media uploads' directly and concisely summarizes the main change: enabling AES-CTR encryption selection for media file uploads to support streaming playback, which is the primary objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sdk-ctr-upload

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 and usage tips.

Add audio/flac to STREAMING_MIME_TYPES in both the SDK client and
web app's streaming-crypto service so FLAC files >256KB use AES-CTR
for random-access streaming decryption.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 4f6bef90d98a
@FSM1 FSM1 changed the base branch from fix/e2e-test-failures-v2 to main March 29, 2026 21:48
@codecov

codecov Bot commented Mar 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.27%. Comparing base (6e12701) to head (3bc2998).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #399      +/-   ##
==========================================
+ Coverage   61.14%   61.27%   +0.12%     
==========================================
  Files         132      133       +1     
  Lines        9758     9781      +23     
  Branches      980      985       +5     
==========================================
+ Hits         5967     5993      +26     
+ Misses       3577     3574       -3     
  Partials      214      214              
Flag Coverage Δ
api 84.37% <100.00%> (+0.11%) ⬆️
api-client 84.37% <100.00%> (+0.11%) ⬆️
core 84.37% <100.00%> (+0.11%) ⬆️
crypto 84.37% <100.00%> (+0.11%) ⬆️
sdk 84.37% <100.00%> (+0.11%) ⬆️
sdk-core 84.37% <100.00%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/sdk-core/src/encryption-mode.ts 100.00% <100.00%> (ø)
packages/sdk/src/client.ts 76.80% <100.00%> (+0.47%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Enables the SDK upload pipeline to choose AES-256-CTR for eligible large media files so the web streaming playback pipeline (range-request decryption + CTR badge) can activate for new uploads, while also aligning sdk-core test mocks with current API/client types.

Changes:

  • Add MIME-type/size-based encryption mode selection in packages/sdk and pass encryptionMode through to sdk-core.
  • Update sdk-core upload to encrypt with AES-CTR (and CTR IV) when requested, and persist the selected encryptionMode into file metadata.
  • Fix sdk-core unit test mocks to match current DTO/result shapes; un-skip the CTR badge E2E test.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/web-e2e/tests/streaming-playback.spec.ts Un-skips CTR badge test now that CTR uploads are enabled.
packages/sdk/src/client.ts Selects encryption mode at upload time and forwards it to sdk-core.
packages/sdk-core/src/upload/index.ts Adds encryptionMode param and switches between AES-GCM and AES-CTR encryption/IV generation.
packages/sdk-core/src/tests/vault.test.ts Updates addToIpfs mock to match IpfsAddResult shape.
packages/sdk-core/src/tests/ipns.test.ts Updates IPNS mock responses to match current DTO shapes.
apps/web/src/services/streaming-crypto.service.ts Expands streaming-eligible MIME types to include audio/flac.
.planning/todos/done/2026-03-29-sdk-uploadfile-hardcodes-gcm-wire-ctr-encryption-for-media-uploads.md Documents the resolved issue and implementation notes.

Comment thread packages/sdk-core/src/upload/index.ts Outdated
Comment thread packages/sdk/src/client.ts Outdated
@FSM1

FSM1 commented Mar 29, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai please review

@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown

@FSM1 Sure, I'll review the changes in this PR now!


🧠 Learnings used
Learnt from: FSM1
Repo: FSM1/cipher-box PR: 273
File: .github/copilot-instructions.md:82-89
Timestamp: 2026-03-06T02:05:31.070Z
Learning: Applies to **/*.{ts,tsx}: Use Web Crypto API for AES-256-GCM, AES-256-CTR, and HKDF-SHA256. Use eciesjs for ECIES secp256k1 key wrapping. Use noble/ed25519 for Ed25519 IPNS signing. Never implement custom cryptographic functions. AES-256-CTR is the approved streaming primitive (packages/crypto/src/aes/encrypt-ctr.ts, decrypt-ctr.ts) for large-file scenarios where GCM full-content buffering is impractical.

Learnt from: FSM1
Repo: FSM1/cipher-box PR: 273
File: .github/copilot-instructions.md:82-89
Timestamp: 2026-03-06T02:05:31.070Z
Learning: Applies to **/*.{ts,tsx,js,jsx}: Approved encryption primitives are: AES-256-GCM (file/metadata encryption), AES-256-CTR (streaming encryption for large files where GCM full-content buffering is not feasible — implemented in packages/crypto/src/aes/encrypt-ctr.ts and decrypt-ctr.ts; used by desktop FUSE and web media streaming), ECIES secp256k1 (key wrapping), HKDF-SHA256 (IPNS keypair derivation), Ed25519 (IPNS record signing via noble/ed25519). Never use: custom crypto, CBC mode, ECB mode, MD5, SHA1. Argon2 for server-side password hashing. AES-256-CTR is explicitly approved for streaming and must not be flagged as a policy violation.

Learnt from: CR
Repo: FSM1/cipher-box PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-06T02:23:13.531Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : For file uploads: 1) Generate random file key and IV, 2) encrypt file content with AES-256-GCM, 3) wrap file key with user's public key using ECIES, 4) upload encrypted file to backend → IPFS without plaintext filename, 5) create per-file FileMetadata IPNS record, 6) add FilePointer to folder metadata, 7) republish folder IPNS

Learnt from: CR
Repo: FSM1/cipher-box PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-06T02:06:06.291Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use only these encryption primitives: AES-256-GCM (file/metadata encryption), ECIES secp256k1 (key wrapping), ECDSA secp256k1 (IPNS signing/identity), HKDF-SHA256 (key derivation), and Argon2 (server-side password hashing) - never use custom crypto, CBC mode, ECB mode, MD5, or SHA1.

Learnt from: CR
Repo: FSM1/cipher-box PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-06T02:23:13.531Z
Learning: Applies to **/*.{ts,tsx} : Use only approved encryption algorithms: AES-256-GCM for content/metadata, AES-256-CTR for streaming, ECIES secp256k1 for key wrapping, HKDF-SHA256 for IPNS keypair derivation, Ed25519 for IPNS signing. Never use custom crypto, CBC mode, ECB mode, MD5, or SHA1

Learnt from: CR
Repo: FSM1/cipher-box PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-06T02:06:06.291Z
Learning: Applies to **/*.{ts,tsx} : Use Web Crypto API or libsodium.js for implementing AES-256-GCM, HKDF-SHA256 key derivation - for ECIES secp256k1 and ECDSA use ethers.js or libsodium.js; never implement custom cryptographic functions.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Move STREAMING_MIME_TYPES, CTR_SIZE_THRESHOLD, and selectEncryptionMode
to @cipherbox/sdk-core/encryption-mode so the SDK client and web app
share a single source of truth. Add normalizeEncryptionMode to validate
the encryptionMode param in uploadFile, coercing invalid values to GCM.

Addresses PR review feedback on duplication risk and input validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: cb7f6e5552c0

@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.

🧹 Nitpick comments (1)
packages/sdk/src/client.ts (1)

1249-1276: Consider centralizing STREAMING_MIME_TYPES to avoid drift.

The SDK now maintains its own copy of STREAMING_MIME_TYPES and CTR_SIZE_THRESHOLD, which must stay in sync with the web app's streaming-crypto.service.ts. While duplication is reasonable for keeping the SDK browser-agnostic, consider extracting these constants to a shared package (e.g., @cipherbox/core or @cipherbox/crypto) to ensure consistency and prevent accidental divergence when adding new MIME types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sdk/src/client.ts` around lines 1249 - 1276, The duplicated
constants STREAMING_MIME_TYPES and CTR_SIZE_THRESHOLD in selectEncryptionMode
should be centralized to avoid drift; refactor by moving these constants into a
shared module (e.g., a new export in `@cipherbox/core` or `@cipherbox/crypto`) and
import them into packages/sdk/src/client.ts, replacing the local
STREAMING_MIME_TYPES and CTR_SIZE_THRESHOLD definitions and keeping
selectEncryptionMode(mimeType, size) unchanged; update imports and remove the
local constants so both the SDK and web app consume the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/sdk/src/client.ts`:
- Around line 1249-1276: The duplicated constants STREAMING_MIME_TYPES and
CTR_SIZE_THRESHOLD in selectEncryptionMode should be centralized to avoid drift;
refactor by moving these constants into a shared module (e.g., a new export in
`@cipherbox/core` or `@cipherbox/crypto`) and import them into
packages/sdk/src/client.ts, replacing the local STREAMING_MIME_TYPES and
CTR_SIZE_THRESHOLD definitions and keeping selectEncryptionMode(mimeType, size)
unchanged; update imports and remove the local constants so both the SDK and web
app consume the same source of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13d726ee-9d94-4fe9-ac46-56c68ca3aea6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e12701 and 4bfd7b2.

📒 Files selected for processing (7)
  • .planning/todos/done/2026-03-29-sdk-uploadfile-hardcodes-gcm-wire-ctr-encryption-for-media-uploads.md
  • apps/web/src/services/streaming-crypto.service.ts
  • packages/sdk-core/src/__tests__/ipns.test.ts
  • packages/sdk-core/src/__tests__/vault.test.ts
  • packages/sdk-core/src/upload/index.ts
  • packages/sdk/src/client.ts
  • tests/web-e2e/tests/streaming-playback.spec.ts
💤 Files with no reviewable changes (1)
  • packages/sdk-core/src/tests/ipns.test.ts

… exports

The sdk-core mock in 5 test files used a bare factory that replaced
the entire module, dropping non-mocked exports like selectEncryptionMode.
Switch to importOriginal + spread so real exports survive alongside
mocked functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 4686e944596d

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment thread packages/sdk-core/src/encryption-mode.ts
Comment thread packages/sdk-core/src/encryption-mode.ts
…alization

Cover selectEncryptionMode (MIME type allowlist, size threshold boundary)
and normalizeEncryptionMode (CTR passthrough, GCM default, invalid input).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 4f9f60832913
@FSM1 FSM1 enabled auto-merge (squash) March 29, 2026 22:16
@FSM1 FSM1 merged commit a595e4b into main Mar 29, 2026
26 checks passed
@FSM1 FSM1 deleted the feat/sdk-ctr-upload branch April 14, 2026 00:04
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.

2 participants