From d810fb497ec5457d00a49f91713c9fc8f8228020 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:19:15 +0000 Subject: [PATCH 1/3] Initial plan From e623974d36bf857790165b14bf4d46a031e79f7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:29:16 +0000 Subject: [PATCH 2/3] Fix security vulnerabilities: URL scheme validation, social auth race condition, AppsFlyer App Store ID in environment config Agent-Logs-Url: https://github.com/numbersprotocol/capture-cam/sessions/52acdecb-2a28-4f28-8677-40a5d875ca7a --- .../shared/apps-flyer/apps-flyer.service.ts | 3 +- .../media/media-viewer/media-viewer.page.ts | 15 +++++++-- .../shared/social-auth/social-auth.service.ts | 33 +++++++++---------- src/environments/environment.prod.ts | 1 + src/environments/environment.ts | 1 + 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/app/shared/apps-flyer/apps-flyer.service.ts b/src/app/shared/apps-flyer/apps-flyer.service.ts index 8b5192493..15d67931d 100644 --- a/src/app/shared/apps-flyer/apps-flyer.service.ts +++ b/src/app/shared/apps-flyer/apps-flyer.service.ts @@ -3,6 +3,7 @@ import { AdvertisingId } from '@capacitor-community/advertising-id'; import { Capacitor } from '@capacitor/core'; import { Platform } from '@ionic/angular'; import { AFEvent, AFInit, AppsFlyer } from 'appsflyer-capacitor-plugin'; +import { environment } from '../../../environments/environment'; import { APPS_FLYER_DEV_KEY } from '../dia-backend/secret'; import { CCamCustomEventType } from './apps-flyer-enums'; @@ -12,7 +13,7 @@ import { CCamCustomEventType } from './apps-flyer-enums'; export class AppsFlyerService { private readonly INIT_TIMEOUT_MS = 2000; private readonly afConfig: AFInit = { - appID: '1536388009', // AppStore Application ID. For iOS only. + appID: environment.appsFlyerAppId, // AppStore Application ID. For iOS only. devKey: APPS_FLYER_DEV_KEY, isDebug: false, waitForATTUserAuthorization: 15, // for iOS 14 and higher diff --git a/src/app/shared/media/media-viewer/media-viewer.page.ts b/src/app/shared/media/media-viewer/media-viewer.page.ts index 9fd330f3e..23f5b4b66 100644 --- a/src/app/shared/media/media-viewer/media-viewer.page.ts +++ b/src/app/shared/media/media-viewer/media-viewer.page.ts @@ -1,10 +1,12 @@ import { Component } from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { NavController } from '@ionic/angular'; -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map } from 'rxjs/operators'; import { isNonNullable } from '../../../utils/rx-operators/rx-operators'; +const ALLOWED_URL_SCHEMES = ['blob:', 'https:', 'capacitor:']; + @Component({ selector: 'app-media-viewer', templateUrl: './media-viewer.page.html', @@ -15,7 +17,14 @@ export class MediaViewerPage { map(params => params.get('src')), isNonNullable(), distinctUntilChanged(), - map(src => this.sanitizer.bypassSecurityTrustUrl(src)) + map((src): SafeUrl | null => { + if (!ALLOWED_URL_SCHEMES.some(scheme => src.startsWith(scheme))) { + this.navController.back(); + return null; + } + return this.sanitizer.bypassSecurityTrustUrl(src); + }), + filter((src): src is SafeUrl => src !== null) ); private readonly mimeType$ = this.route.paramMap.pipe( diff --git a/src/app/shared/social-auth/social-auth.service.ts b/src/app/shared/social-auth/social-auth.service.ts index 1a506ff63..b5b40db57 100644 --- a/src/app/shared/social-auth/social-auth.service.ts +++ b/src/app/shared/social-auth/social-auth.service.ts @@ -20,7 +20,7 @@ export interface SocialUser { }) export class SocialAuthService { private initialized = false; - private initializing = false; + private initializationPromise: Promise | undefined; // No dependencies needed for this service constructor() { @@ -28,36 +28,33 @@ export class SocialAuthService { } private async initSocialAuth(): Promise { - // Prevent multiple initializations - if (this.initialized || this.initializing) { - return; + if (this.initialized) { + return Promise.resolve(); } - this.initializing = true; - - try { - // Initialize the SocialLogin plugin with Google authentication credentials - // For iOS, we use a specific iOS client ID - // For Android and Web, we use the web client ID - await SocialLogin.initialize({ + if (!this.initializationPromise) { + this.initializationPromise = SocialLogin.initialize({ google: { iOSClientId: GOOGLE_IOS_CLIENT_ID, webClientId: GOOGLE_WEB_CLIENT_ID, }, - }); - this.initialized = true; - } finally { - this.initializing = false; + }) + .then(() => { + this.initialized = true; + }) + .catch((err: unknown) => { + this.initializationPromise = undefined; + throw err; + }); } + + return this.initializationPromise; } /** * Method to ensure the service is initialized */ ensureInitialized$(): Observable { - if (this.initialized) { - return from(Promise.resolve()); - } return from(this.initSocialAuth()); } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index c9669790b..48a8d8b0a 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,3 +1,4 @@ export const environment = { production: true, + appsFlyerAppId: '1536388009', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 31cb7855f..a030efbd6 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -4,6 +4,7 @@ export const environment = { production: false, + appsFlyerAppId: '1536388009', }; /* From 6f77130e1d9b0d3b287eb27bb7a832f88a351bcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:31:33 +0000 Subject: [PATCH 3/3] Add error logging for blocked media URL schemes in media-viewer Agent-Logs-Url: https://github.com/numbersprotocol/capture-cam/sessions/52acdecb-2a28-4f28-8677-40a5d875ca7a --- src/app/shared/media/media-viewer/media-viewer.page.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/shared/media/media-viewer/media-viewer.page.ts b/src/app/shared/media/media-viewer/media-viewer.page.ts index 23f5b4b66..5d4872bf3 100644 --- a/src/app/shared/media/media-viewer/media-viewer.page.ts +++ b/src/app/shared/media/media-viewer/media-viewer.page.ts @@ -19,6 +19,8 @@ export class MediaViewerPage { distinctUntilChanged(), map((src): SafeUrl | null => { if (!ALLOWED_URL_SCHEMES.some(scheme => src.startsWith(scheme))) { + // eslint-disable-next-line no-console + console.error(`Blocked media URL with disallowed scheme: ${src}`); this.navController.back(); return null; }