Address security vulnerabilities and localization gaps in remote JSON import#24
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…r handling - Add protocol validation (http/https only) to validateRemoteUrl - Enforce hard byte limit while reading response in fetchTrpcJson - Prevent redirect-based SSRF by setting redirect: 'manual' - Add all missing translation keys for hardcoded UI strings - Change placeholder URL from spliit.app to neutral example.com - Wrap syncAssociatedGroups in try/catch for best-effort handling - Improve passkey error handling to differentiate error types (rate limit, not found, user cancel) - Run prettier to fix inconsistent indentation Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
Pull request overview
Improves the remote JSON import flow by adding SSRF mitigations on the server route, localizing previously hardcoded UI strings in the import dialog, and refining error handling for anonymous/passkey authentication and group association syncing.
Changes:
- Hardened remote JSON import endpoint (protocol restriction, redirect prevention, response-size hard limit while streaming).
- Localized Import-from-URL/file UI strings and related error messages; updated example placeholder URL.
- Made associated-group syncing best-effort and improved passkey error differentiation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/components/import-json-button.tsx | Uses next-intl keys for import UI/errors and makes group association sync best-effort. |
| src/components/anonymous-auth-menu.tsx | Refines passkey auth error handling and includes formatting/indent consistency updates. |
| src/app/groups/json/import/remote/route.ts | Adds SSRF mitigations and enforces a hard response-size cap while reading remote responses. |
| messages/en-US.json | Adds JSONImport.* translation keys used by the import UI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function isPrivateIP(hostname: string): boolean { | ||
| // Check for localhost | ||
| if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') { | ||
| return true | ||
| // Check for localhost | ||
| if ( | ||
| hostname === 'localhost' || | ||
| hostname === '127.0.0.1' || | ||
| hostname === '::1' | ||
| ) { | ||
| return true | ||
| } | ||
|
|
||
| // Check for private IPv4 ranges | ||
| const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ | ||
| const match = hostname.match(ipv4Regex) | ||
| if (match && match.length === 5) { | ||
| const a = Number(match[1]) | ||
| const b = Number(match[2]) | ||
| const c = Number(match[3]) | ||
| const d = Number(match[4]) | ||
|
|
||
| // Validate octets are in valid range | ||
| if (a > 255 || b > 255 || c > 255 || d > 255) { | ||
| return true // Invalid IP, treat as unsafe | ||
| } | ||
|
|
||
| // Check for private IPv4 ranges | ||
| const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ | ||
| const match = hostname.match(ipv4Regex) | ||
| if (match && match.length === 5) { | ||
| const a = Number(match[1]) | ||
| const b = Number(match[2]) | ||
| const c = Number(match[3]) | ||
| const d = Number(match[4]) | ||
|
|
||
| // Validate octets are in valid range | ||
| if (a > 255 || b > 255 || c > 255 || d > 255) { | ||
| return true // Invalid IP, treat as unsafe | ||
| } | ||
|
|
||
| // 127.0.0.0/8 - Loopback | ||
| if (a === 127) return true | ||
|
|
||
| // 10.0.0.0/8 - Private | ||
| if (a === 10) return true | ||
|
|
||
| // 172.16.0.0/12 - Private | ||
| if (a === 172 && b >= 16 && b <= 31) return true | ||
|
|
||
| // 192.168.0.0/16 - Private | ||
| if (a === 192 && b === 168) return true | ||
|
|
||
| // 169.254.0.0/16 - Link-local (includes cloud metadata endpoint) | ||
| if (a === 169 && b === 254) return true | ||
| } | ||
| // 127.0.0.0/8 - Loopback | ||
| if (a === 127) return true | ||
|
|
||
| return false | ||
| } | ||
| // 10.0.0.0/8 - Private | ||
| if (a === 10) return true | ||
|
|
||
| function validateRemoteUrl(url: URL, localOrigin: string): { valid: boolean; error?: string } { | ||
| // Check if URL is same origin | ||
| if (url.origin === localOrigin) { | ||
| return { valid: false, error: 'Remote URL must be different from this site.' } | ||
| } | ||
| // 172.16.0.0/12 - Private | ||
| if (a === 172 && b >= 16 && b <= 31) return true | ||
|
|
||
| // Check for private IPs and localhost | ||
| if (isPrivateIP(url.hostname)) { | ||
| return { valid: false, error: 'Cannot fetch from private or local addresses.' } | ||
| } | ||
| // 192.168.0.0/16 - Private | ||
| if (a === 192 && b === 168) return true | ||
|
|
||
| return { valid: true } | ||
| // 169.254.0.0/16 - Link-local (includes cloud metadata endpoint) | ||
| if (a === 169 && b === 254) return true | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| function deserializeTrpcData(entry: TrpcBatchEntry) { | ||
| if (entry.error?.message) { | ||
| throw new Error(entry.error.message) | ||
| function validateRemoteUrl( | ||
| url: URL, | ||
| localOrigin: string, | ||
| ): { valid: boolean; error?: string } { | ||
| // Only allow http/https protocols to reduce SSRF risk | ||
| if (url.protocol !== 'http:' && url.protocol !== 'https:') { | ||
| return { | ||
| valid: false, | ||
| error: 'Only http and https protocols are allowed.', | ||
| } | ||
| if (!entry.result?.data) { | ||
| throw new Error('Remote response is missing data.') | ||
| } | ||
|
|
||
| // Check if URL is same origin | ||
| if (url.origin === localOrigin) { | ||
| return { | ||
| valid: false, | ||
| error: 'Remote URL must be different from this site.', | ||
| } | ||
| const data = entry.result.data as { json?: unknown; meta?: unknown } | ||
| if (data && typeof data === 'object' && 'json' in data) { | ||
| try { | ||
| return superjson.deserialize(data as SuperJSONResult) | ||
| } catch (error) { | ||
| return (data as { json?: unknown }).json | ||
| } | ||
| } | ||
|
|
||
| // Check for private IPs and localhost | ||
| if (isPrivateIP(url.hostname)) { | ||
| return { | ||
| valid: false, | ||
| error: 'Cannot fetch from private or local addresses.', | ||
| } |
There was a problem hiding this comment.
SSRF mitigation is incomplete: isPrivateIP only checks localhost + a few private IPv4 ranges and does not cover private/link-local IPv6 ranges (e.g. fc00::/7, fe80::/10). Also, validateRemoteUrl only inspects url.hostname and does not resolve DNS, so hostnames that resolve to private IPs (e.g. 127.0.0.1.nip.io) would still be allowed. Consider resolving A/AAAA records and rejecting any private/link-local results, and expanding the IPv6 checks.
| const decoder = new TextDecoder() | ||
| let text = '' | ||
| let totalBytes = 0 | ||
|
|
||
| try { | ||
| const response = await fetch(url, { | ||
| method: 'GET', | ||
| headers: { accept: 'application/json' }, | ||
| signal: controller.signal, | ||
| }) | ||
| while (true) { | ||
| const { done, value } = await reader.read() | ||
| if (done) break | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`Remote request failed with status ${response.status}.`) | ||
| totalBytes += value.length | ||
| if (totalBytes > MAX_RESPONSE_SIZE) { | ||
| throw new Error('Response size exceeds maximum allowed limit.') | ||
| } | ||
|
|
||
| // Check response size | ||
| const contentLength = response.headers.get('content-length') | ||
| if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_SIZE) { | ||
| throw new Error('Response size exceeds maximum allowed limit.') | ||
| } | ||
| text += decoder.decode(value, { stream: true }) | ||
| } | ||
|
|
||
| return response.json() as Promise<unknown> | ||
| // Final decode to flush any remaining bytes | ||
| text += decoder.decode() | ||
|
|
There was a problem hiding this comment.
fetchTrpcJson builds the response body via text += decoder.decode(...) inside a loop. For multi-megabyte payloads this can be quadratic due to repeated string reallocation and increase memory/CPU despite the 10MB cap. Consider collecting decoded chunks (or Uint8Array chunks) and joining once, or using an incremental buffer approach before JSON.parse.
| } catch (error) { | ||
| console.error('Passkey authentication error:', error) | ||
|
|
||
| // Detect user cancellation (NotAllowedError) | ||
| if (error instanceof Error && error.name === 'NotAllowedError') { | ||
| toast({ | ||
| title: 'Authentication cancelled', | ||
| description: 'Passkey authentication was cancelled.', | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| // For not-linked users without username, suggest adding username | ||
| if (!isLinked && !username.trim()) { | ||
| toast({ | ||
| title: 'Passkey not found', | ||
| description: 'Try entering your username and retrying passkey login.', | ||
| }) | ||
| } else { |
There was a problem hiding this comment.
In handleAuthenticatePasskey, the catch block shows a "Passkey not found" toast whenever the user is not linked and no username is entered. That will also happen for unrelated failures after options retrieval (e.g. /auth-verify failing, network errors), which contradicts the earlier status-based handling and can mislead users. Consider only showing the "not found" guidance when the options request actually returned 404 (e.g. track the status / throw a typed error), and otherwise show the real error.
* add anonymous account * feat: implement database-backed sessions and improve group management Add database-backed session storage to fix session expiration on server restart - Create AnonymousSession table with token, userId, expiresAt, and challenge fields - Replace in-memory Map with Prisma queries in SessionStore - Sessions now persist across server restarts and deployments Fix group recovery and sign-out behavior - Fix naming conflict between setRecentGroups state setter and helper function - Groups now properly save to localStorage when logging in - Sign-out only clears localStorage, preserving groups in database for recovery - Groups restore automatically when logging back in with same account Improve UI for logged-in users - Move Restore from backup and Import from JSON to account dropdown menu - Hide restore/import options from groups page for logged-in users - Better organization of account management features Add server-side authorization to group deletion - Validate user session before allowing group deletion - Verify group ownership through anonymousUserGroup table - Return appropriate error codes (401 UNAUTHORIZED, 403 FORBIDDEN) - Prevents unauthorized deletion even if UI is bypassed * update sigin UI * feat: support multiple passkeys per account with names Add ability to register multiple passkeys per anonymous account with custom names - Create new Passkey table with name, credentialId, publicKey, counter, lastUsedAt fields - Migrate from single passkey storage on AnonymousUser to separate Passkey table - Add passkey name input dialog when registering new passkeys - Display list of all registered passkeys with creation dates - Add individual passkey deletion with confirmation dialog - Update lastUsedAt timestamp when passkey is used for authentication - Add /api/anonymous-users/passkey/list endpoint to fetch user's passkeys - Maintain backward compatibility with deprecated passkey fields on AnonymousUser UI improvements: - Show passkey list with name and creation date in account dialog - Add 'Add passkey' button that opens name input dialog - Individual remove button for each passkey - Better UX with 'New account' button highlighted when generating username - Changed 'Recover account' to 'Existing account' for clarity * fix: update auth-options to use new Passkey table - Change auth-options endpoint to fetch credentials from Passkey table - Support multiple passkeys by returning all credential IDs for allowCredentials - Fixes 'No passkey registered' error when authenticating * fix: ensure user exists before creating passkey auth session - Add upsert to create AnonymousUser if not exists before session creation - Prevents foreign key constraint violation in AnonymousSession table - Handle both authenticated and discoverable credential flows * feat: enforce passphrase security requirements and separate change/reset flows - Always require current passphrase for normal passphrase change - Add separate reset flow for passkey-authenticated users - Reset with passkey only requires new passphrase (no current) - Add hasPassphrase flag to groups API response - Update UI to conditionally show options based on passphrase state - Add passkeyResetMode state to differentiate between change and reset - Server-side validation enforces security requirements * Address code review feedback: authorization, error handling, and cleanup optimization (#20) * Initial plan * Address code review comments: fix security and reliability issues Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Refactor: extract session retrieval helper and add temp user cleanup Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Improve error messages and add error logging for debugging Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Polish error message for clarity Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * test passkey fix * try no username first * test IOS * update ux * update UI remove import button * finalise permissions * additional permissoins * Add remote JSON import flow with auto-association and iOS icon fixes * Address security vulnerabilities and translations in remote JSON import (#22) * Initial plan * Add security improvements to remote JSON import route - Add rate limiting to prevent SSRF abuse - Add timeout (30s) to fetch operations - Add groupId validation to prevent injection/path traversal - Add SSRF protection (block private IPs, localhost, metadata endpoints) - Add response size limit (10MB) and max expenses limit (10k) - Add error handling to normalizeDate function - Improve error logging in import-json-button.tsx - Extract ASSOCIATED_GROUPS_KEY constant to shared file Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Translate import success messages to all supported languages - pt-BR: Portuguese (Brazil) - ru-RU: Russian - ko: Korean - id: Indonesian - fr-FR: French - zh-TW: Chinese Traditional - ro: Romanian - ja-JP: Japanese - eu: Basque - zh-CN: Chinese Simplified - nl-NL: Dutch - he: Hebrew - cs-CZ: Czech - uk-UA: Ukrainian - tr-TR: Turkish - pt: Portuguese - pl-PL: Polish - es: Spanish - de-DE: German - ca: Catalan - fi: Finnish - it-IT: Italian Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Address code review feedback on security improvements - Fix IP validation to properly handle octets and validate ranges - Remove redundant cloud metadata endpoint check (covered by link-local) - Fix normalizeDate to throw error instead of using epoch as default - Filter out expenses with invalid dates instead of setting to epoch - Sanitize logging to avoid exposing sensitive data Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Fix log injection vulnerability in normalizeDate Use template literal instead of string concatenation to prevent potential log injection when logging the type of invalid date values. Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * fix json * empty username when accessing the login screen * Address security vulnerabilities and localization gaps in remote JSON import (#24) * Initial plan * Address code review feedback: SSRF protection, translations, and error handling - Add protocol validation (http/https only) to validateRemoteUrl - Enforce hard byte limit while reading response in fetchTrpcJson - Prevent redirect-based SSRF by setting redirect: 'manual' - Add all missing translation keys for hardcoded UI strings - Change placeholder URL from spliit.app to neutral example.com - Wrap syncAssociatedGroups in try/catch for best-effort handling - Improve passkey error handling to differentiate error types (rate limit, not found, user cancel) - Run prettier to fix inconsistent indentation Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Fix duplicate imports causing build failure in import-json-button.tsx (#25) * Initial plan * Fix duplicate imports in import-json-button.tsx Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>
* add anonymous account * feat: implement database-backed sessions and improve group management Add database-backed session storage to fix session expiration on server restart - Create AnonymousSession table with token, userId, expiresAt, and challenge fields - Replace in-memory Map with Prisma queries in SessionStore - Sessions now persist across server restarts and deployments Fix group recovery and sign-out behavior - Fix naming conflict between setRecentGroups state setter and helper function - Groups now properly save to localStorage when logging in - Sign-out only clears localStorage, preserving groups in database for recovery - Groups restore automatically when logging back in with same account Improve UI for logged-in users - Move Restore from backup and Import from JSON to account dropdown menu - Hide restore/import options from groups page for logged-in users - Better organization of account management features Add server-side authorization to group deletion - Validate user session before allowing group deletion - Verify group ownership through anonymousUserGroup table - Return appropriate error codes (401 UNAUTHORIZED, 403 FORBIDDEN) - Prevents unauthorized deletion even if UI is bypassed * update sigin UI * feat: support multiple passkeys per account with names Add ability to register multiple passkeys per anonymous account with custom names - Create new Passkey table with name, credentialId, publicKey, counter, lastUsedAt fields - Migrate from single passkey storage on AnonymousUser to separate Passkey table - Add passkey name input dialog when registering new passkeys - Display list of all registered passkeys with creation dates - Add individual passkey deletion with confirmation dialog - Update lastUsedAt timestamp when passkey is used for authentication - Add /api/anonymous-users/passkey/list endpoint to fetch user's passkeys - Maintain backward compatibility with deprecated passkey fields on AnonymousUser UI improvements: - Show passkey list with name and creation date in account dialog - Add 'Add passkey' button that opens name input dialog - Individual remove button for each passkey - Better UX with 'New account' button highlighted when generating username - Changed 'Recover account' to 'Existing account' for clarity * fix: update auth-options to use new Passkey table - Change auth-options endpoint to fetch credentials from Passkey table - Support multiple passkeys by returning all credential IDs for allowCredentials - Fixes 'No passkey registered' error when authenticating * fix: ensure user exists before creating passkey auth session - Add upsert to create AnonymousUser if not exists before session creation - Prevents foreign key constraint violation in AnonymousSession table - Handle both authenticated and discoverable credential flows * feat: enforce passphrase security requirements and separate change/reset flows - Always require current passphrase for normal passphrase change - Add separate reset flow for passkey-authenticated users - Reset with passkey only requires new passphrase (no current) - Add hasPassphrase flag to groups API response - Update UI to conditionally show options based on passphrase state - Add passkeyResetMode state to differentiate between change and reset - Server-side validation enforces security requirements * Address code review feedback: authorization, error handling, and cleanup optimization (#20) * Initial plan * Address code review comments: fix security and reliability issues Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Refactor: extract session retrieval helper and add temp user cleanup Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Improve error messages and add error logging for debugging Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Polish error message for clarity Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * test passkey fix * try no username first * test IOS * update ux * update UI remove import button * finalise permissions * additional permissoins * Add remote JSON import flow with auto-association and iOS icon fixes * Address security vulnerabilities and translations in remote JSON import (#22) * Initial plan * Add security improvements to remote JSON import route - Add rate limiting to prevent SSRF abuse - Add timeout (30s) to fetch operations - Add groupId validation to prevent injection/path traversal - Add SSRF protection (block private IPs, localhost, metadata endpoints) - Add response size limit (10MB) and max expenses limit (10k) - Add error handling to normalizeDate function - Improve error logging in import-json-button.tsx - Extract ASSOCIATED_GROUPS_KEY constant to shared file Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Translate import success messages to all supported languages - pt-BR: Portuguese (Brazil) - ru-RU: Russian - ko: Korean - id: Indonesian - fr-FR: French - zh-TW: Chinese Traditional - ro: Romanian - ja-JP: Japanese - eu: Basque - zh-CN: Chinese Simplified - nl-NL: Dutch - he: Hebrew - cs-CZ: Czech - uk-UA: Ukrainian - tr-TR: Turkish - pt: Portuguese - pl-PL: Polish - es: Spanish - de-DE: German - ca: Catalan - fi: Finnish - it-IT: Italian Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Address code review feedback on security improvements - Fix IP validation to properly handle octets and validate ranges - Remove redundant cloud metadata endpoint check (covered by link-local) - Fix normalizeDate to throw error instead of using epoch as default - Filter out expenses with invalid dates instead of setting to epoch - Sanitize logging to avoid exposing sensitive data Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Fix log injection vulnerability in normalizeDate Use template literal instead of string concatenation to prevent potential log injection when logging the type of invalid date values. Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * fix json * empty username when accessing the login screen * Address security vulnerabilities and localization gaps in remote JSON import (#24) * Initial plan * Address code review feedback: SSRF protection, translations, and error handling - Add protocol validation (http/https only) to validateRemoteUrl - Enforce hard byte limit while reading response in fetchTrpcJson - Prevent redirect-based SSRF by setting redirect: 'manual' - Add all missing translation keys for hardcoded UI strings - Change placeholder URL from spliit.app to neutral example.com - Wrap syncAssociatedGroups in try/catch for best-effort handling - Improve passkey error handling to differentiate error types (rate limit, not found, user cancel) - Run prettier to fix inconsistent indentation Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Fix duplicate imports causing build failure in import-json-button.tsx (#25) * Initial plan * Fix duplicate imports in import-json-button.tsx Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * add whats new * add whats new * [WIP] Add end-user documentation and UX improvements (#27) * Initial plan * Address PR review comments: fix unused imports, update links, improve security and error handling Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Improve icon choice and clarify HTML stripping behavior Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>
* add anonymous account * feat: implement database-backed sessions and improve group management Add database-backed session storage to fix session expiration on server restart - Create AnonymousSession table with token, userId, expiresAt, and challenge fields - Replace in-memory Map with Prisma queries in SessionStore - Sessions now persist across server restarts and deployments Fix group recovery and sign-out behavior - Fix naming conflict between setRecentGroups state setter and helper function - Groups now properly save to localStorage when logging in - Sign-out only clears localStorage, preserving groups in database for recovery - Groups restore automatically when logging back in with same account Improve UI for logged-in users - Move Restore from backup and Import from JSON to account dropdown menu - Hide restore/import options from groups page for logged-in users - Better organization of account management features Add server-side authorization to group deletion - Validate user session before allowing group deletion - Verify group ownership through anonymousUserGroup table - Return appropriate error codes (401 UNAUTHORIZED, 403 FORBIDDEN) - Prevents unauthorized deletion even if UI is bypassed * update sigin UI * feat: support multiple passkeys per account with names Add ability to register multiple passkeys per anonymous account with custom names - Create new Passkey table with name, credentialId, publicKey, counter, lastUsedAt fields - Migrate from single passkey storage on AnonymousUser to separate Passkey table - Add passkey name input dialog when registering new passkeys - Display list of all registered passkeys with creation dates - Add individual passkey deletion with confirmation dialog - Update lastUsedAt timestamp when passkey is used for authentication - Add /api/anonymous-users/passkey/list endpoint to fetch user's passkeys - Maintain backward compatibility with deprecated passkey fields on AnonymousUser UI improvements: - Show passkey list with name and creation date in account dialog - Add 'Add passkey' button that opens name input dialog - Individual remove button for each passkey - Better UX with 'New account' button highlighted when generating username - Changed 'Recover account' to 'Existing account' for clarity * fix: update auth-options to use new Passkey table - Change auth-options endpoint to fetch credentials from Passkey table - Support multiple passkeys by returning all credential IDs for allowCredentials - Fixes 'No passkey registered' error when authenticating * fix: ensure user exists before creating passkey auth session - Add upsert to create AnonymousUser if not exists before session creation - Prevents foreign key constraint violation in AnonymousSession table - Handle both authenticated and discoverable credential flows * feat: enforce passphrase security requirements and separate change/reset flows - Always require current passphrase for normal passphrase change - Add separate reset flow for passkey-authenticated users - Reset with passkey only requires new passphrase (no current) - Add hasPassphrase flag to groups API response - Update UI to conditionally show options based on passphrase state - Add passkeyResetMode state to differentiate between change and reset - Server-side validation enforces security requirements * Address code review feedback: authorization, error handling, and cleanup optimization (#20) * Initial plan * Address code review comments: fix security and reliability issues Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Refactor: extract session retrieval helper and add temp user cleanup Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Improve error messages and add error logging for debugging Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Polish error message for clarity Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * test passkey fix * try no username first * test IOS * update ux * update UI remove import button * finalise permissions * additional permissoins * Add remote JSON import flow with auto-association and iOS icon fixes * Address security vulnerabilities and translations in remote JSON import (#22) * Initial plan * Add security improvements to remote JSON import route - Add rate limiting to prevent SSRF abuse - Add timeout (30s) to fetch operations - Add groupId validation to prevent injection/path traversal - Add SSRF protection (block private IPs, localhost, metadata endpoints) - Add response size limit (10MB) and max expenses limit (10k) - Add error handling to normalizeDate function - Improve error logging in import-json-button.tsx - Extract ASSOCIATED_GROUPS_KEY constant to shared file Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Translate import success messages to all supported languages - pt-BR: Portuguese (Brazil) - ru-RU: Russian - ko: Korean - id: Indonesian - fr-FR: French - zh-TW: Chinese Traditional - ro: Romanian - ja-JP: Japanese - eu: Basque - zh-CN: Chinese Simplified - nl-NL: Dutch - he: Hebrew - cs-CZ: Czech - uk-UA: Ukrainian - tr-TR: Turkish - pt: Portuguese - pl-PL: Polish - es: Spanish - de-DE: German - ca: Catalan - fi: Finnish - it-IT: Italian Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Address code review feedback on security improvements - Fix IP validation to properly handle octets and validate ranges - Remove redundant cloud metadata endpoint check (covered by link-local) - Fix normalizeDate to throw error instead of using epoch as default - Filter out expenses with invalid dates instead of setting to epoch - Sanitize logging to avoid exposing sensitive data Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Fix log injection vulnerability in normalizeDate Use template literal instead of string concatenation to prevent potential log injection when logging the type of invalid date values. Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * fix json * empty username when accessing the login screen * Address security vulnerabilities and localization gaps in remote JSON import (#24) * Initial plan * Address code review feedback: SSRF protection, translations, and error handling - Add protocol validation (http/https only) to validateRemoteUrl - Enforce hard byte limit while reading response in fetchTrpcJson - Prevent redirect-based SSRF by setting redirect: 'manual' - Add all missing translation keys for hardcoded UI strings - Change placeholder URL from spliit.app to neutral example.com - Wrap syncAssociatedGroups in try/catch for best-effort handling - Improve passkey error handling to differentiate error types (rate limit, not found, user cancel) - Run prettier to fix inconsistent indentation Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Fix duplicate imports causing build failure in import-json-button.tsx (#25) * Initial plan * Fix duplicate imports in import-json-button.tsx Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * add whats new * add whats new * [WIP] Add end-user documentation and UX improvements (#27) * Initial plan * Address PR review comments: fix unused imports, update links, improve security and error handling Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * Improve icon choice and clarify HTML stripping behavior Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com> * change icon * update update with links to forked instance --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>
Addresses code review feedback on PR #23 regarding SSRF vulnerabilities, missing translations, and error handling in the remote JSON import feature.
Security (SSRF Mitigation)
redirect: 'manual'to prevent redirect-based SSRF attacksLocalization
spliit.appto neutralexample.comError Handling
Code Quality
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.