Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/mobile-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ jobs:
packages/mobile-sdk-alpha/dist
key: built-deps-${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('common/**/*', 'packages/mobile-sdk-alpha/**/*', '!common/dist/**', '!packages/mobile-sdk-alpha/dist/**') }}
- name: Test
run: yarn test
run: |
# Always ensure dependencies are built before running tests
echo "Building dependencies before running tests..."
yarn workspace @selfxyz/mobile-app run build:deps
yarn test
working-directory: ./app
build-ios:
runs-on: macos-latest-large
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
shell: bash
run: yarn workspace @selfxyz/common build
- name: Build web app
run: yarn web:build
run: yarn workspace @selfxyz/mobile-app web:build
2 changes: 1 addition & 1 deletion app/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
preset: 'react-native',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz)/)',
'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry)/)',
],
setupFiles: ['<rootDir>/jest.setup.js'],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$',
Expand Down
43 changes: 43 additions & 0 deletions app/src/components/homeScreen/SvgXmlWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React, { createElement, forwardRef } from 'react';
import { Platform } from 'react-native';

// Platform-specific SvgXml component
const SvgXmlWrapper = forwardRef<
HTMLDivElement | SVGSVGElement,
{
xml: string;
width?: number;
height?: number;
style?: React.CSSProperties;
}
>(({ xml, width, height, style, ...props }, ref) => {
if (Platform.OS === 'web') {
// Use our mock for web
return createElement('div', {
ref,
style: {
width: width || 'auto',
height: height || 'auto',
display: 'inline-block',
...style,
},
dangerouslySetInnerHTML: { __html: xml },
...props,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

XSS risk: dangerouslySetInnerHTML with untrusted SVG.

Rendering raw XML with dangerouslySetInnerHTML is exploitable if any xml originates outside of trusted, static strings. Mitigate by sanitizing on web and isolating web/native via platform files to keep mobile builds clean.

Recommended approach:

  • Split into platform files to safely import a sanitizer only on web.
  • Sanitize with DOMPurify on web.

Add these files:

SvgXmlWrapper.web.tsx

// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
import React, { createElement, forwardRef } from 'react';
import DOMPurify from 'dompurify';

type Props = {
  xml: string;
  width?: number;
  height?: number;
  style?: React.CSSProperties;
} & React.HTMLAttributes<HTMLDivElement>;

export const SvgXml = forwardRef<HTMLDivElement, Props>(({ xml, width, height, style, ...props }, ref) => {
  const safe = DOMPurify.sanitize(xml, { USE_PROFILES: { svg: true, svgFilters: true } });
  return createElement('div', {
    ref,
    style: { width: width || 'auto', height: height || 'auto', display: 'inline-block', ...style },
    dangerouslySetInnerHTML: { __html: safe },
    ...props,
  });
});
SvgXml.displayName = 'SvgXml';
export default SvgXml;

SvgXmlWrapper.native.tsx

// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
import React, { forwardRef } from 'react';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { SvgXml: RNSvgXml } = require('react-native-svg');

type Props = {
  xml: string;
  width?: number;
  height?: number;
  style?: unknown;
};

export const SvgXml = forwardRef<any, Props>((p, ref) => <RNSvgXml ref={ref} {...p} />);
SvgXml.displayName = 'SvgXml';
export default SvgXml;

Also add dompurify to web workspace devDeps and map @/components/homeScreen/SvgXmlWrapper imports to these platform files automatically.

Happy to open a PR commit adding the files and dependency.

🧰 Tools
🪛 ast-grep (0.38.6)

[warning] 27-27: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
In app/src/components/homeScreen/SvgXmlWrapper.tsx around lines 20-31 the code
uses dangerouslySetInnerHTML with raw xml (XSS risk). Replace this single
platform file with two platform-specific files: SvgXmlWrapper.web.tsx — import
DOMPurify, sanitize the xml (use profiles for svg/svgFilters) and render with
dangerouslySetInnerHTML using the sanitized string; SvgXmlWrapper.native.tsx —
re-export/forward to react-native-svg's SvgXml (no DOMPurify) to keep mobile
bundle clean. Add dompurify to the web workspace devDeps and ensure module
resolution maps imports of "@/components/homeScreen/SvgXmlWrapper" to the
platform files so existing imports remain unchanged.


// Use the real SvgXml for native platforms
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { SvgXml } = require('react-native-svg');
return (
<SvgXml xml={xml} width={width} height={height} style={style} {...props} />
);
});

SvgXmlWrapper.displayName = 'SvgXmlWrapper';

export { SvgXmlWrapper as SvgXml };
10 changes: 5 additions & 5 deletions app/src/components/homeScreen/idCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React, { useCallback, useState } from 'react';

Check warning on line 5 in app/src/components/homeScreen/idCard.tsx

View workflow job for this annotation

GitHub Actions / build-deps

'useState' is defined but never used

Check warning on line 5 in app/src/components/homeScreen/idCard.tsx

View workflow job for this annotation

GitHub Actions / build-deps

'useCallback' is defined but never used
import { Dimensions, Pressable } from 'react-native';

Check warning on line 6 in app/src/components/homeScreen/idCard.tsx

View workflow job for this annotation

GitHub Actions / build-deps

'Pressable' is defined but never used
import { SvgXml } from 'react-native-svg';
import { Button, Image, Separator, Text, XStack, YStack } from 'tamagui';

Check warning on line 7 in app/src/components/homeScreen/idCard.tsx

View workflow job for this annotation

GitHub Actions / build-deps

'Image' is defined but never used

Check warning on line 7 in app/src/components/homeScreen/idCard.tsx

View workflow job for this annotation

GitHub Actions / build-deps

'Button' is defined but never used
import { useFocusEffect } from '@react-navigation/native';

Check warning on line 8 in app/src/components/homeScreen/idCard.tsx

View workflow job for this annotation

GitHub Actions / build-deps

'useFocusEffect' is defined but never used

import {
attributeToPosition,
attributeToPosition_ID,
formatMrz,
PassportData,
} from '@selfxyz/common/dist/esm';
import { pad } from '@selfxyz/common/dist/esm/src/utils/passports/passport';
} from '@selfxyz/common/constants';
import { PassportData } from '@selfxyz/common/types';
import { formatMrz } from '@selfxyz/common/utils';
import { pad } from '@selfxyz/common/utils/passports/passport';

import { SvgXml } from '@/components/homeScreen/SvgXmlWrapper';
import EPassport from '@/images/icons/epassport.svg';
import LogoGray from '@/images/logo_gray.svg';
import LogoInversed from '@/images/logo_inversed.svg';
Expand Down
31 changes: 31 additions & 0 deletions app/src/mocks/react-native-community-blur.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';

// Mock BlurView component for web builds
export const BlurView = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
blurType?: string;
blurAmount?: number;
reducedTransparencyFallbackColor?: string;
}
>(({ children, style, ...props }, ref) => {
return React.createElement(
'div',
{
ref,
style: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
...style,
},
...props,
},
children,
);
});

BlurView.displayName = 'BlurView';
67 changes: 67 additions & 0 deletions app/src/mocks/react-native-svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';

export const Circle = React.forwardRef<
SVGCircleElement,
React.SVGProps<SVGCircleElement>
>((props, ref) => {
return React.createElement('circle', { ref, ...props });
});

Circle.displayName = 'Circle';

export const Path = React.forwardRef<
SVGPathElement,
React.SVGProps<SVGPathElement>
>((props, ref) => {
return React.createElement('path', { ref, ...props });
});

Path.displayName = 'Path';

export const Rect = React.forwardRef<
SVGRectElement,
React.SVGProps<SVGRectElement>
>((props, ref) => {
return React.createElement('rect', { ref, ...props });
});

Rect.displayName = 'Rect';

// Re-export other common SVG components that might be used
export const Svg = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return React.createElement('svg', { ref, ...props });
});

Svg.displayName = 'Svg';

// Mock SvgXml component for web builds
export const SvgXml = React.forwardRef<
HTMLDivElement,
{
xml: string;
width?: number;
height?: number;
style?: React.CSSProperties;
}
>(({ xml, width, height, style, ...props }, ref) => {
return React.createElement('div', {
ref,
style: {
width: width || 'auto',
height: height || 'auto',
display: 'inline-block',
...style,
},
dangerouslySetInnerHTML: { __html: xml },
...props,
});
});

SvgXml.displayName = 'SvgXml';
6 changes: 1 addition & 5 deletions app/src/navigation/aesop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { lazy } from 'react';
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';

import { ProgressNavBar } from '@/components/NavBar';
import { shouldShowAesopRedesign } from '@/hooks/useAesopRedesign';
import DocumentOnboardingScreen from '@/screens/aesop/DocumentOnboardingScreen';
import { white } from '@/utils/colors';

const DocumentOnboardingScreen = lazy(
() => import('@/screens/aesop/DocumentOnboardingScreen'),
);

const aesopScreens = {
DocumentOnboarding: {
screen: DocumentOnboardingScreen,
Expand Down
22 changes: 6 additions & 16 deletions app/src/navigation/devTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,16 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { lazy } from 'react';
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';

import CreateMockScreen from '@/screens/dev/CreateMockScreen';
import CreateMockScreenDeepLink from '@/screens/dev/CreateMockScreenDeepLink';
import DevFeatureFlagsScreen from '@/screens/dev/DevFeatureFlagsScreen';
import DevHapticFeedbackScreen from '@/screens/dev/DevHapticFeedbackScreen';
import DevPrivateKeyScreen from '@/screens/dev/DevPrivateKeyScreen';
import DevSettingsScreen from '@/screens/dev/DevSettingsScreen';
import { black, white } from '@/utils/colors';

const DevFeatureFlagsScreen = lazy(
() => import('@/screens/dev/DevFeatureFlagsScreen'),
);
const DevHapticFeedbackScreen = lazy(
() => import('@/screens/dev/DevHapticFeedbackScreen'),
);
const DevPrivateKeyScreen = lazy(
() => import('@/screens/dev/DevPrivateKeyScreen'),
);
const DevSettingsScreen = lazy(() => import('@/screens/dev/DevSettingsScreen'));
const CreateMockScreen = lazy(() => import('@/screens/dev/CreateMockScreen'));
const CreateMockScreenDeepLink = lazy(
() => import('@/screens/dev/CreateMockScreenDeepLink'),
);

const devHeaderOptions: NativeStackNavigationOptions = {
headerStyle: {
backgroundColor: black,
Expand Down
29 changes: 7 additions & 22 deletions app/src/navigation/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,15 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { lazy } from 'react';
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';

const DocumentCameraScreen = lazy(
() => import('@/screens/document/DocumentCameraScreen'),
);
const DocumentCameraTroubleScreen = lazy(
() => import('@/screens/document/DocumentCameraTroubleScreen'),
);
const DocumentNFCScanScreen = lazy(
() => import('@/screens/document/DocumentNFCScanScreen'),
);
const DocumentNFCTroubleScreen = lazy(
() => import('@/screens/document/DocumentNFCTroubleScreen'),
);
const DocumentOnboardingScreen = lazy(
() => import('@/screens/document/DocumentOnboardingScreen'),
);
const UnsupportedDocumentScreen = lazy(
() => import('@/screens/document/UnsupportedDocumentScreen'),
);
const DocumentNFCMethodSelectionScreen = lazy(
() => import('@/screens/document/DocumentNFCMethodSelectionScreen'),
);
import DocumentCameraScreen from '@/screens/document/DocumentCameraScreen';
import DocumentCameraTroubleScreen from '@/screens/document/DocumentCameraTroubleScreen';
import DocumentNFCMethodSelectionScreen from '@/screens/document/DocumentNFCMethodSelectionScreen';
import DocumentNFCScanScreen from '@/screens/document/DocumentNFCScanScreen';
import DocumentNFCTroubleScreen from '@/screens/document/DocumentNFCTroubleScreen';
import DocumentOnboardingScreen from '@/screens/document/DocumentOnboardingScreen';
import UnsupportedDocumentScreen from '@/screens/document/UnsupportedDocumentScreen';

const documentScreens = {
DocumentCamera: {
Expand Down
15 changes: 5 additions & 10 deletions app/src/navigation/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { lazy } from 'react';
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';

import { HomeNavBar, IdDetailsNavBar } from '@/components/NavBar';
import DisclaimerScreen from '@/screens/home/DisclaimerScreen';
import HomeScreen from '@/screens/home/HomeScreen';
import IdDetailsScreen from '@/screens/home/IdDetailsScreen';
import ProofHistoryDetailScreen from '@/screens/home/ProofHistoryDetailScreen';
import ProofHistoryScreen from '@/screens/home/ProofHistoryScreen';

const DisclaimerScreen = lazy(() => import('@/screens/home/DisclaimerScreen'));
const HomeScreen = lazy(() => import('@/screens/home/HomeScreen'));
const ProofHistoryDetailScreen = lazy(
() => import('@/screens/home/ProofHistoryDetailScreen'),
);
const ProofHistoryScreen = lazy(
() => import('@/screens/home/ProofHistoryScreen'),
);
const IdDetailsScreen = lazy(() => import('@/screens/home/IdDetailsScreen'));
const homeScreens = {
Disclaimer: {
screen: DisclaimerScreen,
Expand Down
20 changes: 3 additions & 17 deletions app/src/navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React, { Suspense, useEffect } from 'react';
import { Platform, View } from 'react-native';
import React, { useEffect } from 'react';
import { Platform } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Text } from 'tamagui';
import type { StaticParamList } from '@react-navigation/native';
import {
createNavigationContainerRef,
Expand Down Expand Up @@ -63,17 +62,6 @@ declare global {
const { trackScreenView } = analytics();
const Navigation = createStaticNavigation(AppNavigation);

const SuspenseFallback = () => {
if (Platform.OS === 'web') {
return <div>Loading...</div>;
}
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Loading...</Text>
</View>
);
};

const NavigationWithTracking = () => {
const trackScreen = () => {
const currentRoute = navigationRef.getCurrentRoute();
Expand All @@ -96,9 +84,7 @@ const NavigationWithTracking = () => {

return (
<GestureHandlerRootView>
<Suspense fallback={<SuspenseFallback />}>
<Navigation ref={navigationRef} onStateChange={trackScreen} />
</Suspense>
<Navigation ref={navigationRef} onStateChange={trackScreen} />
</GestureHandlerRootView>
);
};
Expand Down
19 changes: 0 additions & 19 deletions app/src/navigation/lazyWithPreload.ts

This file was deleted.

Loading
Loading