Skip to content

fix(security): harden auth and sharing subsystems#267

Merged
FSM1 merged 15 commits into
mainfrom
fix/security-review-hardening
Mar 4, 2026
Merged

fix(security): harden auth and sharing subsystems#267
FSM1 merged 15 commits into
mainfrom
fix/security-review-hardening

Conversation

@FSM1

@FSM1 FSM1 commented Mar 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • L1: /shares/lookup always returns 200 — eliminates public key enumeration via 200/404 difference
  • L4: Add pagination (limit/offset) to /shares/received and /shares/sent endpoints
  • M5: Surface user-visible notification toast when reWrapForRecipients fails (new Zustand store + component)
  • H-08: Encrypt device private key at rest in IndexedDB using HKDF-derived AES-256-GCM wrapping

Previously fixed (not in this PR): M-07 (scoped JWT), M-11 (Rust Debug redaction).
Known limitation: M1 (plaintext itemName in share record) documented as acceptable.

Test plan

  • 637 API unit tests pass
  • Web TypeScript compiles clean (tsc --noEmit)
  • Generated API client regenerated via pnpm api:generate
  • E2E: tests/e2e/tests/share-workflow.spec.ts (if available)
  • Manual: verify notification toast appears on simulated re-wrap failure
  • Manual: verify device key re-encrypted on next login (v1→v2 migration)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Paginated received/sent shares endpoints with limit/offset; client queries accept params.
    • In-app toast notifications with auto-dismiss, manual dismiss, and management UI.
  • Improvements

    • Device private key persisted encrypted and versioned; support for ephemeral vs persisted device identities.
    • Re-wrap operations now report failed recipients and trigger warning notifications.
    • User lookup returns structured existence response (exists: boolean).

FSM1 and others added 2 commits March 4, 2026 19:18
Address 4 findings from security review:

- L1: /shares/lookup always returns 200 (eliminates pubkey enumeration)
- L4: Add pagination (limit/offset) to share listing endpoints
- M5: Surface notification toast when reWrapForRecipients fails
- H-08: Encrypt device private key at rest in IndexedDB (HKDF + AES-256-GCM)

Previously fixed: M-07 (scoped JWT), M-11 (Rust Debug redaction).
Known limitation: M1 (plaintext itemName in share record) documented.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 68f538dc75d7
@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds paginated shares endpoints and DTOs, updates server and client signatures to return { shares, total }, introduces a LookupUserResponse DTO, adds a frontend notification store and toast, persists device private keys encrypted with a vault-derived wrapping key, and updates callsites/tests to use pagination and notifications.

Changes

Cohort / File(s) Summary
Backend DTOs & OpenAPI
apps/api/src/shares/dto/lookup-user-response.dto.ts, apps/api/src/shares/dto/pagination.dto.ts, packages/api-client/openapi.json
Add LookupUserResponseDto, PaginationQueryDto, PaginatedReceivedSharesDto, PaginatedSentSharesDto; OpenAPI updated to add limit/offset and switch shares responses to paginated schemas.
Backend Controller & Service
apps/api/src/shares/shares.controller.ts, apps/api/src/shares/shares.service.ts, apps/api/src/shares/shares.controller.spec.ts, apps/api/src/shares/shares.service.spec.ts
Controller methods for received/sent shares now accept pagination and return { shares, total }; service uses findAndCount; lookupUser returns LookupUserResponseDto; tests expanded for pagination and additional error cases.
Web API client & models
apps/web/src/api/models/*, apps/web/src/api/shares/shares.ts, apps/web/src/api/custom-instance.ts, apps/web/src/api/models/index.ts
Generated client models add paginated DTOs and param types; share client functions accept optional params and return paginated results; custom-instance params typing widened.
Frontend notification system
apps/web/src/stores/notification.store.ts, apps/web/src/components/NotificationToast.tsx, apps/web/src/components/layout/AppShell.tsx
Add useNotificationStore, NotificationToast component with auto-dismiss/accessibility, mount toast in AppShell, and clear notifications on sign-out.
Share service & callers (Web)
apps/web/src/services/share.service.ts, apps/web/src/hooks/useFileOperations.ts, apps/web/src/hooks/useFolderMutations.ts
fetch*Shares and client calls return { shares, total }; fetchAllSentShares added; reWrapForRecipients now returns { failedRecipients }; callers enqueue warning notifications when failures occur.
Client paging consumers
apps/web/src/components/file-browser/ShareDialog.tsx, apps/web/src/hooks/useSharedNavigation.ts
Replace single-call share fetches with offset-based paginated loops that aggregate pages into full result sets while preserving cancellation/loading semantics.
Device identity encryption & callers
apps/web/src/lib/device/identity.ts, apps/web/src/hooks/useAuth.ts, apps/web/src/hooks/useDeviceApproval.ts, apps/web/src/hooks/useMfa.ts
Persist device private key encrypted (AES-256-GCM) with HKDF-derived wrapping key from vault private key; storage versioning added; getOrCreateDeviceIdentity/loadDeviceKeypair/saveDeviceKeypair signatures updated; callsites pass vault keys or use ephemeral mode.
Integrations, cleanup & CI
apps/web/src/lib/clear-user-stores.ts, .github/workflows/*.yml
Notification store cleared on logout; CI adds coverage artifact upload and a Codecov base upload workflow; tests adjusted for pagination and errors.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Controller as "Shares Controller"
    participant Service as "Shares Service"
    participant Repo as "Repository / DB"

    Client->>Controller: GET /shares/received?limit=50&offset=0
    Controller->>Service: getReceivedShares(userId, 50, 0)
    Service->>Repo: findAndCount(where, skip=0, take=50)
    Repo-->>Service: [shares[], total]
    Service-->>Controller: { shares: Share[], total: number }
    Controller-->>Client: 200 PaginatedReceivedSharesDto
Loading
sequenceDiagram
    participant Service as "Share Service"
    participant Store as "Notification Store"
    participant Toast as "NotificationToast"
    participant User as "User"

    Service->>Service: reWrapForRecipients()
    alt some recipients failed
        Service->>Store: addNotification('warning', "X recipients failed")
        Store-->>Toast: notifications updated
        Toast->>User: render toast (auto-dismiss 8s)
        User->>Toast: click dismiss or timeout
        Toast->>Store: dismissNotification(id)
    else all succeeded
        Service-->>Store: (no notification)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(security): harden auth and sharing subsystems' directly and clearly summarizes the main objectives of the changeset, which include security hardening across authentication and sharing systems.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/security-review-hardening

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.

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

This PR implements four hardening improvements to the auth and sharing subsystems as part of a security review:

  • L1 fix: /shares/lookup now always returns HTTP 200 with { exists: boolean } instead of 200/404, eliminating public key enumeration via response code differences.
  • L4 fix: Adds limit/offset pagination to /shares/received and /shares/sent, with validated defaults (limit=50, max=100).
  • M5 fix: Adds a Zustand-backed NotificationToast component that surfaces user-visible warnings when reWrapForRecipients fails.
  • H-08 fix: Device Ed25519 private key is now encrypted with AES-256-GCM (HKDF-derived from vault private key) before being stored in IndexedDB, with a migration path that regenerates the keypair for v1 (plaintext) entries.

Changes:

  • API + OpenAPI spec updated for paginated shares endpoints and new lookup/pagination DTOs
  • New Zustand notification store + NotificationToast component
  • Device identity module now encrypts the private key at rest; getOrCreateDeviceIdentity now accepts optional vaultPrivateKey
  • All callers of fetchReceivedShares, fetchSentShares, and reWrapForRecipients updated to the new signatures

Reviewed changes

Copilot reviewed 27 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/api-client/openapi.json Adds pagination params for received/sent shares, removes 404 from lookup, adds new DTO schemas
apps/api/src/shares/shares.controller.ts Wires pagination params; changes lookupUser to always return 200+boolean
apps/api/src/shares/shares.service.ts getReceivedShares/getSentShares use findAndCount with take/skip
apps/api/src/shares/dto/pagination.dto.ts New PaginationQueryDto (limit 1–100, offset ≥0) + paginated response DTOs
apps/api/src/shares/dto/lookup-user-response.dto.ts New LookupUserResponseDto
apps/api/src/shares/shares.controller.spec.ts Tests updated for paginated response shape and new lookup behavior
apps/api/src/shares/shares.service.spec.ts Tests updated for findAndCount and paginated results
apps/web/src/stores/notification.store.ts New Zustand notification store
apps/web/src/components/NotificationToast.tsx New toast component with auto-dismiss
apps/web/src/components/layout/AppShell.tsx Mounts NotificationToast in both layout branches
apps/web/src/lib/clear-user-stores.ts Clears notification store on logout
apps/web/src/services/share.service.ts Pagination in fetch functions; reWrapForRecipients returns failedRecipients; ensureFreshSentShares uses limit=1000
apps/web/src/lib/device/identity.ts HKDF-derived wrapping key; v1→v2 migration; vaultPrivateKey param added
apps/web/src/hooks/useAuth.ts Passes vaultPrivateKey to getOrCreateDeviceIdentity
apps/web/src/hooks/useDeviceApproval.ts Passes vaultPrivateKey to getOrCreateDeviceIdentity in approve/deny handlers
apps/web/src/hooks/useFolderMutations.ts Shows warning toast on re-wrap failure
apps/web/src/hooks/useFileOperations.ts Shows warning toast on re-wrap failure
apps/web/src/hooks/useSharedNavigation.ts Destructures { shares } from paginated response
apps/web/src/components/file-browser/ShareDialog.tsx Uses limit: 100 for fetching sent shares
apps/web/src/api/shares/shares.ts Updated Orval-generated client for paginated + lookup changes
apps/web/src/api/models/*.ts New generated model files
.planning/todos/done/*.md Todo items moved to done

You can also share your feedback on Copilot code review. Take the survey.

Comment thread apps/web/src/services/share.service.ts Outdated
Comment thread apps/web/src/components/NotificationToast.tsx Outdated
Comment thread apps/web/src/components/NotificationToast.tsx Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/components/file-browser/ShareDialog.tsx (1)

195-205: ⚠️ Potential issue | 🟠 Major

Lookup flow still expects 404, but API now returns { exists: boolean } with 200.

This branch won’t show user not found reliably anymore and defers failure to createShare.

Suggested fix (check `exists` explicitly)
-    try {
-      // Verify recipient is a registered user
-      await sharesControllerLookupUser({ publicKey: key });
-    } catch (err: unknown) {
-      const status = (err as { status?: number }).status;
-      if (status === 404) {
-        setError('user not found');
-      } else {
-        setError('lookup failed, please try again');
-      }
-      setIsSharing(false);
-      return;
-    }
+    try {
+      // Verify recipient is a registered user
+      const lookup = await sharesControllerLookupUser({ publicKey: key });
+      if (!lookup.exists) {
+        setError('user not found');
+        setIsSharing(false);
+        return;
+      }
+    } catch {
+      setError('lookup failed, please try again');
+      setIsSharing(false);
+      return;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/file-browser/ShareDialog.tsx` around lines 195 - 205,
The lookup no longer returns 404 but a 200 payload with { exists: boolean }, so
change the flow in ShareDialog to inspect the lookup response instead of relying
on an error status: call sharesControllerLookupUser({ publicKey: key }) and if
the returned object has exists === false call setError('user not found'),
setIsSharing(false) and return; only proceed to createShare when exists ===
true; remove the status-based 404 check in the catch block (keep a generic error
handler to setError('lookup failed, please try again') and setIsSharing(false)
for unexpected exceptions).
🧹 Nitpick comments (2)
apps/web/src/components/NotificationToast.tsx (1)

6-16: Consider consolidating duplicate color maps.

borderColors and labelColors are identical. You could use a single shared map to reduce duplication.

♻️ Suggested consolidation
-const borderColors: Record<Notification['type'], string> = {
-  info: 'var(--color-green-primary)',
-  warning: 'var(--color-warning)',
-  error: 'var(--color-error)',
-};
-
-const labelColors: Record<Notification['type'], string> = {
+const typeColors: Record<Notification['type'], string> = {
   info: 'var(--color-green-primary)',
   warning: 'var(--color-warning)',
   error: 'var(--color-error)',
 };

Then use typeColors[n.type] for both border and label.

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

In `@apps/web/src/components/NotificationToast.tsx` around lines 6 - 16,
borderColors and labelColors in NotificationToast.tsx are identical; replace
them with a single shared map (e.g., typeColors) keyed by Notification['type']
and use that single map wherever both borderColors and labelColors are
referenced (update any usages like borderColors[n.type] and labelColors[n.type]
to typeColors[n.type]); ensure the new map preserves the same keys and values
and update imports/exports if either map was exported.
apps/api/src/shares/shares.controller.spec.ts (1)

143-157: Add a pagination pass-through test for getSentShares (parity with received).

Received path validates (userId, limit, offset) forwarding; sent path should assert the same contract.

Suggested test addition
   describe('getSentShares', () => {
     const pagination = { limit: 50, offset: 0 };

     it('should return paginated shares with recipientPublicKey', async () => {
       mockSharesService.getSentShares.mockResolvedValue({ shares: [mockShare], total: 1 });

       const result = await controller.getSentShares(mockReq, pagination);

       expect(result.shares).toHaveLength(1);
       expect(result.total).toBe(1);
       expect(result.shares[0].shareId).toBe(shareId);
       expect(result.shares[0].recipientPublicKey).toBe(recipientPublicKey);
       expect(result.shares[0].itemType).toBe('folder');
       expect(result.shares[0].itemName).toBe('My Folder');
     });
+
+    it('should pass pagination params to service', async () => {
+      mockSharesService.getSentShares.mockResolvedValue({ shares: [], total: 0 });
+      await controller.getSentShares(mockReq, { limit: 10, offset: 20 });
+      expect(mockSharesService.getSentShares).toHaveBeenCalledWith(userId, 10, 20);
+    });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/shares/shares.controller.spec.ts` around lines 143 - 157, Add a
test asserting that controller.getSentShares forwards pagination and userId to
the shares service: call controller.getSentShares(mockReq, pagination) and
verify mockSharesService.getSentShares was called with mockReq.user.userId (or
equivalent userId), pagination.limit and pagination.offset; keep existing
assertions on returned shares (shares/total) and use the same symbols
getSentShares, controller, mockSharesService.getSentShares, mockReq, and
pagination to locate where to add this parity test with the received path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/components/file-browser/ShareDialog.tsx`:
- Around line 124-129: The current call to sharesControllerGetSentShares({
limit: 100 }) only fetches a single page and can miss recipients; change the
logic in the ShareDialog code that uses sharesControllerGetSentShares (and the
SentShare result handling that filters by ipnsName) to paginate until no more
pages are returned: repeatedly call sharesControllerGetSentShares using the
API's pagination mechanism (e.g., nextCursor/offset/page token or incrementing
offset with limit) accumulating response.shares, break when the response
indicates no more data (empty page or missing next token), respect the cancelled
flag on each loop iteration, then perform the .filter((s) => s.ipnsName ===
ipnsName) on the full accumulated list so revoke/manage UI sees all recipients.

In `@apps/web/src/hooks/useDeviceApproval.ts`:
- Around line 366-367: The code currently passes vaultKeypair?.privateKey into
getOrCreateDeviceIdentity which allows creating a new ephemeral device identity
when vault state is missing; instead, check for a present vaultKeypair and its
privateKey before calling getOrCreateDeviceIdentity and fail fast (throw or
return an error) if absent so approve/deny actions cannot silently create
ephemeral identities. Update both usages (the one referencing vaultKeypair and
getOrCreateDeviceIdentity and the other occurrence at the later call) to
validate useAuthStore.getState().vaultKeypair and its privateKey, and only call
getOrCreateDeviceIdentity when a real privateKey exists; otherwise surface a
clear error/early return so respondedByDeviceId attribution remains stable.

In `@apps/web/src/hooks/useSharedNavigation.ts`:
- Line 122: The current call in useSharedNavigation (const { shares } = await
fetchReceivedShares()) only loads the first page (default 50); change the logic
to page through fetchReceivedShares until no more pages and aggregate all share
items before updating state: call fetchReceivedShares repeatedly (using its
pagination token/parameters) or adjust it to accept page params, accumulate
results into a single shares array, then set state with the combined array;
target the useSharedNavigation function and the fetchReceivedShares
call/response handling where shares is destructured.

In `@apps/web/src/lib/device/identity.ts`:
- Around line 89-114: The IndexedDB payload fetched for KEYPAIR_KEY is being
cast without validation and can feed malformed data into
deriveDeviceWrappingKey/crypto.subtle.decrypt; before calling
deriveDeviceWrappingKey or decrypt, explicitly validate that val is an object,
val.version === STORAGE_VERSION, and that val.publicKey, val.iv, and
val.encryptedPrivateKey are arrays (or typed arrays) of expected numeric bytes
(and non-empty/expected length), and if any check fails return null (or reject)
so decryption never runs on bad input; also guard the Uint8Array conversions so
they only run after these checks.

In `@apps/web/src/services/share.service.ts`:
- Around line 189-191: The call that uses fetchSentShares(1000) violates the
backend max page size and can break rotation; replace the single oversized fetch
with a safe pagination loop: repeatedly call fetchSentShares with the backend
max (100) and the appropriate paging parameter (offset, pageToken, or page
number as supported by fetchSentShares) until no more shares are returned,
aggregate all pages into one array, then call
useShareStore.getState().setSentShares(allShares); ensure you do not pass 1000
anywhere and handle empty/partial pages and errors from fetchSentShares during
the loop.

In `@packages/api-client/openapi.json`:
- Around line 1210-1230: The OpenAPI schemas for pagination and count fields
(parameters named "limit" and "offset" and any response/count properties
documented as type "number") must be changed to integer types to match backend
validation; update each schema where "type": "number" is used for
pagination/counts to "type": "integer" (optionally add "format": "int32" and
keep existing "default" values) for the "limit" and "offset" query parameters
and for any corresponding response/count properties in the same file so
generated clients no longer send decimals.
- Around line 1321-1334: The OpenAPI spec for the /shares/lookup operation is
missing a 400 response for invalid publicKey input; update
packages/api-client/openapi.json under the /shares/lookup operation (the
responses object that currently lists 200 and 401) to add a 400 response
describing "Bad Request" (invalid publicKey) and reference an appropriate error
schema (or create one) consistent with the controller behavior in
apps/api/src/shares/shares.controller.ts where BadRequestException is thrown;
ensure the new response entry is named "400" with a brief description and a
content/application/json schema that matches the API's error DTO so generated
clients will handle the 400 case.

---

Outside diff comments:
In `@apps/web/src/components/file-browser/ShareDialog.tsx`:
- Around line 195-205: The lookup no longer returns 404 but a 200 payload with {
exists: boolean }, so change the flow in ShareDialog to inspect the lookup
response instead of relying on an error status: call
sharesControllerLookupUser({ publicKey: key }) and if the returned object has
exists === false call setError('user not found'), setIsSharing(false) and
return; only proceed to createShare when exists === true; remove the
status-based 404 check in the catch block (keep a generic error handler to
setError('lookup failed, please try again') and setIsSharing(false) for
unexpected exceptions).

---

Nitpick comments:
In `@apps/api/src/shares/shares.controller.spec.ts`:
- Around line 143-157: Add a test asserting that controller.getSentShares
forwards pagination and userId to the shares service: call
controller.getSentShares(mockReq, pagination) and verify
mockSharesService.getSentShares was called with mockReq.user.userId (or
equivalent userId), pagination.limit and pagination.offset; keep existing
assertions on returned shares (shares/total) and use the same symbols
getSentShares, controller, mockSharesService.getSentShares, mockReq, and
pagination to locate where to add this parity test with the received path.

In `@apps/web/src/components/NotificationToast.tsx`:
- Around line 6-16: borderColors and labelColors in NotificationToast.tsx are
identical; replace them with a single shared map (e.g., typeColors) keyed by
Notification['type'] and use that single map wherever both borderColors and
labelColors are referenced (update any usages like borderColors[n.type] and
labelColors[n.type] to typeColors[n.type]); ensure the new map preserves the
same keys and values and update imports/exports if either map was exported.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e5b4c376-8b82-473b-863b-8b2a141aa417

📥 Commits

Reviewing files that changed from the base of the PR and between 11dada9 and aad544e.

📒 Files selected for processing (29)
  • .planning/todos/done/2026-02-15-security-review-medium-term-fixes.md
  • .planning/todos/done/2026-02-21-phase14-security-review-deferred.md
  • apps/api/src/shares/dto/lookup-user-response.dto.ts
  • apps/api/src/shares/dto/pagination.dto.ts
  • apps/api/src/shares/shares.controller.spec.ts
  • apps/api/src/shares/shares.controller.ts
  • apps/api/src/shares/shares.service.spec.ts
  • apps/api/src/shares/shares.service.ts
  • apps/web/src/api/custom-instance.ts
  • apps/web/src/api/models/index.ts
  • apps/web/src/api/models/lookupUserResponseDto.ts
  • apps/web/src/api/models/paginatedReceivedSharesDto.ts
  • apps/web/src/api/models/paginatedSentSharesDto.ts
  • apps/web/src/api/models/sharesControllerGetReceivedSharesParams.ts
  • apps/web/src/api/models/sharesControllerGetSentSharesParams.ts
  • apps/web/src/api/shares/shares.ts
  • apps/web/src/components/NotificationToast.tsx
  • apps/web/src/components/file-browser/ShareDialog.tsx
  • apps/web/src/components/layout/AppShell.tsx
  • apps/web/src/hooks/useAuth.ts
  • apps/web/src/hooks/useDeviceApproval.ts
  • apps/web/src/hooks/useFileOperations.ts
  • apps/web/src/hooks/useFolderMutations.ts
  • apps/web/src/hooks/useSharedNavigation.ts
  • apps/web/src/lib/clear-user-stores.ts
  • apps/web/src/lib/device/identity.ts
  • apps/web/src/services/share.service.ts
  • apps/web/src/stores/notification.store.ts
  • packages/api-client/openapi.json

Comment thread apps/web/src/components/file-browser/ShareDialog.tsx Outdated
Comment thread apps/web/src/hooks/useDeviceApproval.ts Outdated
Comment thread apps/web/src/hooks/useSharedNavigation.ts Outdated
Comment thread apps/web/src/lib/device/identity.ts
Comment thread apps/web/src/services/share.service.ts Outdated
Comment thread packages/api-client/openapi.json
Comment thread packages/api-client/openapi.json
FSM1 and others added 2 commits March 4, 2026 19:48
- Paginate all share fetches (API max 100/page) instead of limit=1000
- Use role="alert" + aria-live="assertive" for warning/error toasts
- Consolidate duplicate borderColors/labelColors into single typeColors map
- Fail fast in approver/deny when vault keypair unavailable
- Validate IndexedDB payload shape before decrypting device key
- Add 400 response to /shares/lookup OpenAPI spec
- Use integer types for pagination params and total counts in OpenAPI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 29de1d88b4dc
…tion test

- ShareDialog lookup now checks `lookup.exists` instead of catching 404
  (API always returns 200 after L1 fix)
- Add pagination pass-through test for getSentShares controller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: e24bba172048
@FSM1

FSM1 commented Mar 4, 2026

Copy link
Copy Markdown
Owner Author

Addressing remaining review items from CodeRabbit review:

Outside-diff-range comment (ShareDialog.tsx:195-205 — lookup still expects 404):
Fixed in 142ab19. The lookup flow now checks lookup.exists instead of catching a 404 status, matching the updated API that always returns 200.

Nitpick (controller.spec.ts — missing getSentShares pagination test):
Fixed in 142ab19. Added should pass pagination params to service test asserting getSentShares(userId, 10, 20) forwarding.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/services/share.service.ts (1)

292-330: ⚠️ Potential issue | 🟡 Minor

Deduplicate failed recipients before returning.

failedRecipients is currently appended per failed share. If one recipient is covered by multiple shares, UI warnings can over-count affected recipients.

💡 Proposed fix
-  const failedRecipients: string[] = [];
+  const failedRecipients = new Set<string>();
...
-      failedRecipients.push(share.recipientPublicKey);
+      failedRecipients.add(share.recipientPublicKey);
...
-  return { failedRecipients };
+  return { failedRecipients: Array.from(failedRecipients) };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/services/share.service.ts` around lines 292 - 330,
failedRecipients may contain duplicates if the same recipient appears in
multiple coveringShares; update the logic in the re-wrap loop (the block using
coveringShares, wrapKey, addShareKeys) to deduplicate before returning by using
a Set (or filtering unique values) when collecting push operations or just
convert failedRecipients to a unique array before the return; ensure you still
push share.recipientPublicKey on catch but return Array.from(new
Set(failedRecipients)) (or equivalent) so the function returns unique recipient
values.
🧹 Nitpick comments (1)
apps/web/src/components/NotificationToast.tsx (1)

35-41: Optional: avoid repeated linear scans when pruning timers.

You can reduce cleanup from repeated some(...) checks by precomputing active IDs once per effect run.

♻️ Proposed refactor
-    // Clean up timers for removed notifications
-    for (const [id, timer] of timers) {
-      if (!notifications.some((n) => n.id === id)) {
-        clearTimeout(timer);
-        timers.delete(id);
-      }
-    }
+    // Clean up timers for removed notifications
+    const activeIds = new Set(notifications.map((n) => n.id));
+    for (const [id, timer] of timers) {
+      if (!activeIds.has(id)) {
+        clearTimeout(timer);
+        timers.delete(id);
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/NotificationToast.tsx` around lines 35 - 41,
Precompute the set of active notification IDs once per effect and use it to
prune timers instead of calling notifications.some(...) for each timer;
specifically, build a Set (from notifications.map(n => n.id)) and then iterate
timers with for (const [id, timer] of timers) { if (!activeIds.has(id)) {
clearTimeout(timer); timers.delete(id); } }, referencing the existing timers,
notifications, id, timer, clearTimeout and timers.delete symbols to locate where
to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/lib/device/identity.ts`:
- Around line 213-218: The call to saveDeviceKeypair is awaited inside
getOrCreateDeviceIdentity which causes the function to throw if
IndexedDB/encryption fails; wrap the saveDeviceKeypair(...) call in a try/catch
so write/encryption errors are caught and do not abort identity creation, and
ensure the function still returns the newly generated keypair from
generateDeviceKeypair when persistence fails (log or processLogger.warn the
caught error). Locate generateDeviceKeypair, saveDeviceKeypair and
getOrCreateDeviceIdentity to implement a non-fatal fallback: attempt to persist,
catch and log the error, then continue returning the generated keypair.

---

Outside diff comments:
In `@apps/web/src/services/share.service.ts`:
- Around line 292-330: failedRecipients may contain duplicates if the same
recipient appears in multiple coveringShares; update the logic in the re-wrap
loop (the block using coveringShares, wrapKey, addShareKeys) to deduplicate
before returning by using a Set (or filtering unique values) when collecting
push operations or just convert failedRecipients to a unique array before the
return; ensure you still push share.recipientPublicKey on catch but return
Array.from(new Set(failedRecipients)) (or equivalent) so the function returns
unique recipient values.

---

Nitpick comments:
In `@apps/web/src/components/NotificationToast.tsx`:
- Around line 35-41: Precompute the set of active notification IDs once per
effect and use it to prune timers instead of calling notifications.some(...) for
each timer; specifically, build a Set (from notifications.map(n => n.id)) and
then iterate timers with for (const [id, timer] of timers) { if
(!activeIds.has(id)) { clearTimeout(timer); timers.delete(id); } }, referencing
the existing timers, notifications, id, timer, clearTimeout and timers.delete
symbols to locate where to change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9c3fba07-24f1-4a03-9892-ad6f35e2b06f

📥 Commits

Reviewing files that changed from the base of the PR and between aad544e and 253c604.

📒 Files selected for processing (13)
  • apps/api/src/shares/dto/pagination.dto.ts
  • apps/api/src/shares/shares.controller.ts
  • apps/web/src/api/models/paginatedReceivedSharesDto.ts
  • apps/web/src/api/models/paginatedSentSharesDto.ts
  • apps/web/src/api/models/sharesControllerGetReceivedSharesParams.ts
  • apps/web/src/api/models/sharesControllerGetSentSharesParams.ts
  • apps/web/src/components/NotificationToast.tsx
  • apps/web/src/components/file-browser/ShareDialog.tsx
  • apps/web/src/hooks/useDeviceApproval.ts
  • apps/web/src/hooks/useSharedNavigation.ts
  • apps/web/src/lib/device/identity.ts
  • apps/web/src/services/share.service.ts
  • packages/api-client/openapi.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/src/api/models/paginatedSentSharesDto.ts
  • apps/web/src/hooks/useSharedNavigation.ts
  • apps/api/src/shares/dto/pagination.dto.ts
  • apps/web/src/api/models/sharesControllerGetReceivedSharesParams.ts
  • apps/web/src/api/models/sharesControllerGetSentSharesParams.ts

Comment thread apps/web/src/lib/device/identity.ts Outdated
…ation

Wrap saveDeviceKeypair in try/catch so identity creation succeeds
even if IndexedDB persistence fails (e.g. incognito mode).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: b19f546ec5be

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 27 out of 29 changed files in this pull request and generated 3 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment thread apps/api/src/shares/shares.controller.spec.ts
Comment thread apps/web/src/services/share.service.ts
Comment thread apps/web/src/hooks/useSharedNavigation.ts Outdated
Prevents unnecessary API calls after component unmount during
multi-page share fetching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 6ab41206d086

@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: 1

🧹 Nitpick comments (1)
apps/api/src/shares/shares.controller.spec.ts (1)

144-163: Optional: add an explicit empty-result test for sent shares.

Lines 146-163 cover happy-path pagination and parameter forwarding. For parity with getReceivedShares, consider asserting { shares: [], total: 0 } for getSentShares too.

Proposed test addition
 describe('getSentShares', () => {
   const pagination = { limit: 50, offset: 0 };
@@
   it('should pass pagination params to service', async () => {
     mockSharesService.getSentShares.mockResolvedValue({ shares: [], total: 0 });
     await controller.getSentShares(mockReq, { limit: 10, offset: 20 });
     expect(mockSharesService.getSentShares).toHaveBeenCalledWith(userId, 10, 20);
   });
+
+  it('should return empty array when no sent shares', async () => {
+    mockSharesService.getSentShares.mockResolvedValue({ shares: [], total: 0 });
+    const result = await controller.getSentShares(mockReq, pagination);
+    expect(result.shares).toEqual([]);
+    expect(result.total).toBe(0);
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/shares/shares.controller.spec.ts` around lines 144 - 163, Add a
new unit test that asserts getSentShares returns an explicit empty result ({
shares: [], total: 0 }) and that controller.getSentShares forwards pagination
parameters to mockSharesService.getSentShares; specifically, call
controller.getSentShares(mockReq, { limit: 10, offset: 20 }) after mocking
mockSharesService.getSentShares.mockResolvedValue({ shares: [], total: 0 }) and
assert the returned value or that mockSharesService.getSentShares was called
with (userId, 10, 20) to mirror the getReceivedShares empty-result parity and
ensure parameter forwarding from controller.getSentShares.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/lib/device/identity.ts`:
- Around line 193-200: getOrCreateDeviceIdentity currently silently returns an
ephemeral keypair when vaultPrivateKey is omitted; change its API to a
discriminated-union input (e.g., { vaultPrivateKey: Uint8Array } | { ephemeral:
true }) so callers must opt in to ephemeral behavior, update the function body
to switch on the discriminant and preserve existing behavior for the vault path,
and update call sites that intentionally want ephemeral keys (useMfa.ts at
useMfa and useDeviceApproval.ts at useDeviceApproval) to pass the explicit {
ephemeral: true } variant while other callers pass { vaultPrivateKey } so
accidental omission no longer rotates deviceId; also update the JSDoc above
getOrCreateDeviceIdentity to describe the new union parameters.

---

Nitpick comments:
In `@apps/api/src/shares/shares.controller.spec.ts`:
- Around line 144-163: Add a new unit test that asserts getSentShares returns an
explicit empty result ({ shares: [], total: 0 }) and that
controller.getSentShares forwards pagination parameters to
mockSharesService.getSentShares; specifically, call
controller.getSentShares(mockReq, { limit: 10, offset: 20 }) after mocking
mockSharesService.getSentShares.mockResolvedValue({ shares: [], total: 0 }) and
assert the returned value or that mockSharesService.getSentShares was called
with (userId, 10, 20) to mirror the getReceivedShares empty-result parity and
ensure parameter forwarding from controller.getSentShares.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5cf44b8d-ff86-4b30-82fa-25d224d5864a

📥 Commits

Reviewing files that changed from the base of the PR and between 253c604 and d2bfb84.

📒 Files selected for processing (4)
  • apps/api/src/shares/shares.controller.spec.ts
  • apps/web/src/components/file-browser/ShareDialog.tsx
  • apps/web/src/hooks/useSharedNavigation.ts
  • apps/web/src/lib/device/identity.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/hooks/useSharedNavigation.ts

Comment thread apps/web/src/lib/device/identity.ts
FSM1 and others added 2 commits March 4, 2026 20:14
Codecov needs coverage data on the base branch (main) to compute
PR diffs and post coverage comments. The CI workflow only triggered
on pull_request, so no baseline was ever uploaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: e9f7e2c1c467
Codecov needs coverage data on main to compute PR diffs. Rather than
re-running the full CI on push to main, cache the coverage artifact
from the PR CI run and re-upload it in a lightweight job.

- ci.yml: revert push trigger, add coverage artifact upload step
- codecov-base.yml: new workflow on push to main that downloads the
  cached artifact and uploads to codecov

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: d109f435fc30
@FSM1 FSM1 enabled auto-merge (squash) March 4, 2026 19:20
@FSM1 FSM1 disabled auto-merge March 4, 2026 19:24
Replace optional `vaultPrivateKey?` parameter with a discriminated union
type (`{ mode: 'persisted', vaultPrivateKey }` | `{ mode: 'ephemeral' }`)
so callers must explicitly choose ephemeral mode. This prevents accidental
key rotation from forgetting to pass the vault key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 3041e592ee03
@codecov

codecov Bot commented Mar 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.18%. Comparing base (ef90514) to head (5f42aae).
⚠️ Report is 16 commits behind head on main.

Files with missing lines Patch % Lines
apps/api/src/shares/shares.controller.ts 68.75% 0 Missing and 5 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #267      +/-   ##
==========================================
+ Coverage   46.31%   48.18%   +1.87%     
==========================================
  Files         106      110       +4     
  Lines        8266     8997     +731     
  Branches      591      642      +51     
==========================================
+ Hits         3828     4335     +507     
- Misses       4271     4490     +219     
- Partials      167      172       +5     
Flag Coverage Δ
api 85.05% <75.00%> (+1.18%) ⬆️
crypto 85.05% <75.00%> (+1.18%) ⬆️
desktop 28.75% <ø> (+3.29%) ⬆️

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

Files with missing lines Coverage Δ
apps/api/src/shares/shares.service.ts 97.72% <100.00%> (+0.05%) ⬆️
apps/api/src/shares/shares.controller.ts 67.18% <68.75%> (-0.06%) ⬇️

... and 4 files with indirect coverage changes

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

…d edge cases

- Error propagation: NotFoundException, ForbiddenException, ConflictException
  for createShare, getShareKeys, addShareKeys, revokeShare, hideShare,
  updateShareEncryptedKey, and completeRotation
- Response shape: verify internal fields (sharerId, recipientId, etc.) not leaked
- Edge cases: empty results, multiple items, null/undefined publicKey,
  wrong prefix, too-long keys, case-insensitive hex

Total shares controller tests: 18 → 46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 9f51bf05c2e5

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/codecov-base.yml:
- Around line 53-54: The base Codecov upload currently only includes the API and
crypto reports/flags (the files line referencing apps/api/coverage/lcov.info and
packages/crypto/coverage/lcov.info and the flags line noting "api,crypto");
update the upload step to also include the desktop coverage file (e.g.,
packages/desktop/coverage/lcov.info) and add the "desktop" flag to the flags
list so the base-branch upload contains api, crypto, and desktop to match
project.default.flags in codecov.yml.
- Around line 12-22: The workflow job "upload-coverage" is missing an explicit
Actions API permission; add a permissions block for that job with "actions:
read" so the gh api calls for listing workflow runs and downloading artifacts
run deterministically; update the "upload-coverage" job (job name
upload-coverage) to include a permissions: actions: read entry scoped to the
job.

In `@apps/web/src/lib/device/identity.ts`:
- Around line 227-236: The saveDeviceKeypair failure currently swallows errors
and leaves no session-stable identity; modify the catch block after
generateDeviceKeypair/saveDeviceKeypair to store the generated keypair in a
module-scoped session fallback (e.g., sessionFallbackKeypair) so subsequent
persisted-mode calls in the same session reuse it; ensure functions that load or
create identity (where generateDeviceKeypair and saveDeviceKeypair are used)
first check and return sessionFallbackKeypair when IndexedDB persistence failed,
and set sessionFallbackKeypair = { publicKey: keypair.publicKey, privateKey:
keypair.privateKey } inside the catch so the identity remains stable for the
session.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5ed4c0d2-b685-4c8d-83a5-88f3115e00df

📥 Commits

Reviewing files that changed from the base of the PR and between d2bfb84 and b1d25f2.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • .github/workflows/codecov-base.yml
  • apps/api/src/shares/shares.controller.spec.ts
  • apps/web/src/hooks/useAuth.ts
  • apps/web/src/hooks/useDeviceApproval.ts
  • apps/web/src/hooks/useMfa.ts
  • apps/web/src/lib/device/identity.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/hooks/useAuth.ts

Comment thread .github/workflows/codecov-base.yml
Comment thread .github/workflows/codecov-base.yml
Comment thread apps/web/src/lib/device/identity.ts
…entity fallback

- Add explicit `permissions: actions: read` to codecov-base.yml for
  deterministic behavior when calling Actions API endpoints
- Add module-scoped session fallback in device identity so repeated
  persisted-mode calls return the same keypair when IndexedDB is
  unavailable, preventing deviceId churn within a session

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 94fe8778a376

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/lib/device/identity.ts`:
- Around line 180-181: The module-scoped variable sessionFallback is leaking a
device key across vaults; change the implementation to scope fallback state to
the active vault/context (e.g., replace sessionFallback with a Map keyed by
activeVaultId or move it into the React context/Zustand store) and update any
callers (e.g., the loadDeviceIdentity/null-load path) to: 1) look up the
fallback by current vault id, 2) validate the fallback belongs to the current
persisted request/account before returning, and 3) ensure the fallback is fully
cleared on logout or vault-switch to prevent cross-account reuse.
- Around line 223-253: Concurrent callers can race in the persisted create path
(generateDeviceKeypair, saveDeviceKeypair, loadDeviceKeypair) causing multiple
different keypairs to be created; serialize this with a shared in-process
lock/promise so only one creator performs generate+save and others wait and then
return the saved canonical identity. Implement a module-scoped variable (e.g.,
creatingDeviceKeypairPromise) that is set when entering the creation branch;
other callers await it; inside the promise perform
loadDeviceKeypair(vaultPrivateKey) again (to short-circuit if another writer
completed), otherwise generateDeviceKeypair(), attempt saveDeviceKeypair(...,
vaultPrivateKey) and set sessionFallback only on save failure, then resolve with
the final canonical identity (use deriveDeviceId on the returned publicKey);
always clear the creatingDeviceKeypairPromise in finally so future calls
proceed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eff4e217-3fc2-41ac-89b8-99b74f11fc77

📥 Commits

Reviewing files that changed from the base of the PR and between b1d25f2 and 1f2f7b8.

📒 Files selected for processing (2)
  • .github/workflows/codecov-base.yml
  • apps/web/src/lib/device/identity.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/codecov-base.yml

Comment thread apps/web/src/lib/device/identity.ts Outdated
Comment thread apps/web/src/lib/device/identity.ts
…urrent calls

- Scope sessionFallback to vault key hash so a different user in the
  same tab cannot receive a stale device identity from a previous login
- Deduplicate concurrent persisted-mode calls via shared in-flight
  promise so only one keypair is generated and saved to IndexedDB
- Extract resolvePersistedIdentity() for clean separation of concerns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: c5e71c9330a8

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 30 out of 32 changed files in this pull request and generated 3 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment thread apps/web/src/lib/device/identity.ts Outdated
Comment thread apps/web/src/lib/device/identity.ts Outdated
Comment thread .github/workflows/codecov-base.yml
…validation

- Scope persistedInFlight promise to vault key hash so concurrent calls
  with different vault keys don't return the wrong identity
- Tighten encrypted private key validation from length > 0 to length >= 48
  (32-byte Ed25519 seed + 16-byte AES-GCM auth tag)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: be85ed171786

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 30 out of 32 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

apps/web/src/hooks/useSharedNavigation.ts:143

  • After the while loop exits (all pages collected), the calls to useShareStore.getState().setReceivedShares(shares) (line 135) and setSharedItems(items) (line 143) are not guarded by a cancelled check. If the component unmounts after the last page fetch but before these lines execute, setSharedItems will attempt to update state on an unmounted component. A if (cancelled) return; guard should be placed immediately after the while loop (before line 135) to match the pattern used inside the loop.
        useShareStore.getState().setReceivedShares(shares);

        const items: SharedListItem[] = shares.map((share) => ({
          share,
          children: null,
          folderKey: null,
        }));

        setSharedItems(items);

You can also share your feedback on Copilot code review. Take the survey.

Comment thread apps/web/src/lib/device/identity.ts Outdated
Comment thread apps/web/src/components/file-browser/ShareDialog.tsx
FSM1 and others added 2 commits March 4, 2026 22:47
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@FSM1 FSM1 enabled auto-merge (squash) March 4, 2026 21:50
@FSM1 FSM1 merged commit 4f53611 into main Mar 4, 2026
14 of 15 checks passed
@FSM1 FSM1 deleted the fix/security-review-hardening branch March 5, 2026 14:50
This was referenced Mar 21, 2026
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