Security Audit Report

myowjaYOY/social-app

Generated by DocAgent — automated codebase documentation analysis. Based on analysis of 3 screens. Subject matter expert review is recommended before distribution.

April 19, 2026

Security Audit Report (Mobile)


Application: social-app (Bluesky / AT Protocol Mobile Client) Document Title: Security Audit Report (Mobile) Date: April 2026 Assessment Scope: React Native / Expo (Managed & Bare Workflow) Prepared By: DocAgent — Automated Codebase Documentation Analysis Distribution: Security Engineering, Mobile Engineering, Compliance


1. Executive Summary

Generated by DocAgent — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.

This Security Audit Report evaluates three screens of the social-app mobile application — a React Native / Expo client for the Bluesky / AT Protocol social network — against the OWASP Mobile Application Security Verification Standard (MASVS) v2.0, OWASP ASVS v4.0.3, NIST SP 800-163 Rev 1, and CWE/SANS Top 25. The overall security posture of the assessed screens is Moderate. The screens are architecturally simple gate and empty-state UIs with limited attack surface; no on-device sensitive data storage, no text input fields, and no complex authorization logic are present in the assessed components. Several meaningful security weaknesses are identified, primarily in the areas of missing client-side rate limiting, silent error handling, and information disclosure through error messages.

The three highest-risk findings are: (1) SEC-001 — Missing Rate Limiting on Account Reactivation, where the absence of button disabling during an in-flight activateAccount XRPC call allows rapid repeated submissions that could facilitate account-state manipulation or denial-of-service against the PDS endpoint; (2) SEC-004 — Deactivated Route Accessible as Deep Link Without Session Validation, where the /deactivated route is reachable via the app's registered URL scheme without a component-level session guard, creating a potential for confused-deputy navigation if the session layer fails to intercept; and (3) SEC-007 — Silent Mutation Failure Masks Authorization Errors on Feed Write, where the fire-and-forget pattern on useAddSavedFeedsMutation in the No Following Feed screen silently discards 401 Unauthorized responses, leaving users with no indication that their session has expired and that the feed was not saved.

Across all three screens, 11 findings were identified: 0 Critical, 3 High, 5 Medium, 2 Low, and 1 Info. Mobile-specific categories (on-device storage, deep links, native modules) account for 3 findings. No direct on-device storage misuse (e.g., tokens in AsyncStorage) was identified in the assessed screens; the primary mobile-specific risks relate to deep-link exposure and the absence of documented platform security controls (certificate pinning, jailbreak detection). This assessment is based exclusively on technical documentation review and does not constitute dynamic application security testing, static binary analysis, or device forensics. All findings marked "(documented behavior — verify with penetration testing)" or "(not documented — requires security testing to confirm)" require live validation before remediation priority is finalized.


2. Audit Methodology

Primary Framework

OWASP MASVS v2.0 — Mobile-specific verification requirements across seven categories: MASVS-STOR (Data Storage), MASVS-CRYPTO (Cryptography), MASVS-AUTH (Authentication & Session Management), MASVS-NETWORK (Network Communication), MASVS-PLATFORM (Platform Interaction), MASVS-CODE (Code Quality & Build Settings), and MASVS-RESILIENCE (Resilience Against Reverse Engineering).

Supplementary Framework

OWASP MSTG (Mobile Security Testing Guide) — Testing methodology and finding classification for React Native applications, including guidance on Expo Router deep-link handling, AsyncStorage misuse patterns, and native module vetting.

Shared API/Web Framework

OWASP ASVS v4.0.3 — Verification requirements for API and server-side concerns, applied to the AT Protocol XRPC calls documented in each screen.

Weakness Enumeration

CWE/SANS Top 25 — Standardized weakness identifiers used to classify each finding.

Control Assessment

NIST SP 800-163 Rev 1 — Mobile application vetting methodology used to assess native module trust and platform security control completeness.

Scope

All three screens provided in the documentation:

Limitations

This assessment is based on technical documentation review only. It does not include:

Findings are derived from documented patterns, architecture descriptions, and stated behaviors. Where documentation is silent on a security control, the finding is marked accordingly.

Severity Definitions (CVSS-Aligned)

Severity CVSS Range Definition SLA
Critical 9.0–10.0 Exploitable vulnerability allowing unauthorized access, data breach, or system compromise Fix immediately (24h)
High 7.0–8.9 Significant vulnerability with high probability of exploitation and serious impact Fix within 1 week
Medium 4.0–6.9 Moderate vulnerability requiring specific conditions to exploit Fix within 30 days
Low 0.1–3.9 Minor security weakness with limited exploitability or impact Fix within 90 days
Info 0.0 Security best practice recommendation or hardening suggestion Consider for future work

3. Risk Summary Dashboard

Metric Value
Total Findings 11
Critical 0
High 3
Medium 5
Low 2
Info 1
Screens Assessed 3
OWASP MASVS Categories Triggered 5/7
On-Device Storage Findings 0
Deep Link Findings 2
Native Module Findings 0

4. OWASP MASVS Coverage

# Category Status Findings Notes
MASVS-STOR Data Storage Security Pass 0 No on-device sensitive data storage documented in assessed screens; no AsyncStorage, MMKV, or SecureStore usage present
MASVS-CRYPTO Cryptography Pass 0 No cryptographic operations performed in assessed screens; TID generation is identifier creation, not cryptography
MASVS-AUTH Authentication & Session Management Concern 3 Session guard relies entirely on upstream shell layer with no component-level fallback; rate limiting absent on reactivation; silent auth-error swallowing on feed mutations
MASVS-NETWORK Network Communication Concern 2 Certificate pinning and cleartext traffic policy not documented for assessed screens; XRPC error handling exposes internal error strings
MASVS-PLATFORM Platform Interaction Concern 3 Deep-link routes accessible without component-level session validation; no jailbreak/root detection documented; platform security configuration not documented
MASVS-CODE Code Quality & Build Settings Concern 2 Non-null assertion on session object; misleading error message copy; fire-and-forget async patterns without error handling
MASVS-RESILIENCE Resilience Against Reverse Engineering Concern 1 No jailbreak/root detection, no anti-tampering, no certificate pinning documented at any assessed screen level (not documented — requires security testing to confirm at app level)

5. Mobile-Specific Attack Surface Assessment

5.1 On-Device Storage Security

No direct on-device storage operations (AsyncStorage, MMKV, SecureStore, react-native-keychain) are performed within any of the three assessed screen components. Section 12 of the Deactivated screen documentation explicitly states: "No AsyncStorage, MMKV, or SecureStore reads/writes occur directly in this component." The No Following Feed and No Saved Feeds Of Any Type screens similarly document no client-side persistence.

Storage Mechanism Usage Data Stored Encrypted? Risk Finding
AsyncStorage Not used in assessed screens N/A No (plain-text) N/A None
react-native-mmkv (MMKV) Not used in assessed screens N/A No by default N/A None
react-native-keychain / expo-secure-store Not used in assessed screens N/A Yes (Keychain/Keystore) N/A None
Session state (#/state/session) Used indirectly via useSession hook currentAccount, accounts list including DIDs and handles Not documented — storage mechanism of session state is outside assessed screen scope Medium (if session tokens stored in AsyncStorage) [Not documented — WHO: mobile engineering lead; WHAT: What storage mechanism does #/state/session use to persist session tokens and account credentials across app restarts — specifically, does it use AsyncStorage, MMKV, SecureStore, or Keychain?; WHERE: Insert in Section 5.1 On-Device Storage Security table, Session state row, Encrypted? column]

Note: The session state layer (#/state/session) is referenced by all three assessed screens but its internal storage implementation is outside the scope of the provided documentation. The security of token persistence depends entirely on that layer's implementation. If session tokens are stored in unencrypted AsyncStorage, a High finding would apply across all screens that consume useSession.

All three assessed screens are registered as Expo Router file-based routes, making them automatically accessible via the app's registered URL scheme (documented as defined in app.json under scheme).

URL Scheme Ownership:

[Not documented — WHO: the DevOps/release engineering team; WHAT: What is the registered custom URI scheme for the app (e.g., bsky://, bluesky://) as defined in app.json?; WHERE: Insert in Section 5.2 Deep Link Hijacking Assessment, URL Scheme Ownership paragraph]

Custom URI schemes (e.g., myapp://) can be registered by any application on the device. On Android, if another application registers the same scheme, the OS may present an app chooser or silently route the deep link to the malicious application. On iOS, the last-installed app claiming the scheme wins. This is a platform-level risk that applies to all three assessed routes.

Universal Links / App Links:

[Not documented — WHO: the mobile engineering lead; WHAT: Are Universal Links (iOS apple-app-site-association) and Android App Links (assetlinks.json) configured for the app's deep-link routes? If so, which routes are covered?; WHERE: Insert in Section 5.2 Deep Link Hijacking Assessment, Universal Links / App Links paragraph]

Assessed Routes and Deep-Link Risk:

Route Deep-Link Target Parameter Validation Scheme Type Hijacking Risk Finding
/deactivated Yes — accessible via app URL scheme No route params consumed; data from session state only Custom URI scheme (inferred from Expo Router file-based routing — verify against app.json scheme configuration) Medium — route reachable without deactivated session; shell layer is sole guard SEC-004
/feeds/no-following-feed Yes — accessible via app URL scheme No route params consumed Custom URI scheme (inferred from Expo Router file-based routing — verify against app.json scheme configuration) Low — no sensitive action triggered on mount; action requires authenticated session SEC-005
/feeds/no-saved-feeds-of-any-type Yes — accessible via app URL scheme No route params consumed Custom URI scheme (inferred from Expo Router file-based routing — verify against app.json scheme configuration) Medium — destructive overwrite mutation accessible via deep link; no confirmation guard SEC-005

Parameter Tampering: None of the three screens consume route parameters from useLocalSearchParams() or useGlobalSearchParams(). All data is sourced from session state or hardcoded constants. This eliminates parameter-injection risk at the component level for the assessed screens.

Scheme Ownership Validation: No inter-app caller identity verification is documented for any of the three routes. The app does not validate the originating application identity when handling deep links (not documented — requires security testing to confirm at the OS routing level).

[Screenshot: deep-link flow diagram showing custom URI scheme routing to /deactivated, /feeds/no-following-feed, and /feeds/no-saved-feeds-of-any-type]

5.3 Native Module Trust Assessment

The following native modules are referenced across the three assessed screens:

Native Module / Package Screen Source Platform Trust Assessment
react-native-safe-area-context Deactivated Well-known published package (Expo ecosystem) iOS + Android Trusted — widely used, actively maintained
@tanstack/react-query Deactivated, No Saved Feeds Well-known published package iOS + Android Trusted — no native code; pure JS
@lingui/core, @lingui/react All three screens Well-known published package iOS + Android Trusted — no native code; pure JS
@atproto/common-web (TID) No Saved Feeds First-party Bluesky SDK iOS + Android Trusted — first-party; verify supply chain
react-native (core) All three screens Meta / React Native core iOS + Android Trusted

No custom native modules of unknown origin (NativeModules.X with custom bridging) are documented in any of the three assessed screens. No deprecated or unmaintained packages shipping native binaries are identified in the assessed screen documentation.

Note: The #/state/session layer, useAccountSwitcher, and useLoggedOutViewControls hooks referenced by the Deactivated screen may internally depend on additional native modules (e.g., react-native-keychain, expo-secure-store, react-native-mmkv) that are outside the scope of the provided documentation. Those dependencies should be assessed separately.

5.4 Platform Security Configuration

Control iOS (ATS) Android (network-security-config) Status Notes
Certificate pinning Not documented in assessed screens Not documented in assessed screens Concern No certificate pinning configuration is referenced in any of the three screen documents. [Not documented — WHO: the security lead; WHAT: Is certificate pinning configured and tested on both iOS and Android for XRPC calls to the PDS? If so, which library is used (e.g., react-native-ssl-pinning, OkHttp pinning)?; WHERE: Insert in Section 5.4 Platform Security Configuration table, Certificate pinning row]
Cleartext traffic Not documented Not documented Concern ATS and Android network-security-config cleartext policy not referenced. (not documented — requires security testing to confirm)
Biometric authentication Not used in assessed screens Not used in assessed screens N/A No biometric auth is documented for the three assessed screens; may be present elsewhere in the app
Jailbreak/root detection Not documented in assessed screens Not documented in assessed screens Concern Section 14 of the Deactivated screen explicitly states: "No biometric auth, jailbreak detection, or certificate pinning is implemented at this screen level." App-level detection is not documented. [Not documented — WHO: the security lead; WHAT: Is jailbreak (iOS) and root (Android) detection implemented at the app level, and if so, what library or mechanism is used?; WHERE: Insert in Section 5.4 Platform Security Configuration table, Jailbreak/root detection row]

6. Screen-by-Screen Findings

6.1 Deactivated — /deactivated

Security Profile:

Aspect Assessment
Authentication Required — screen is only rendered for authenticated users with a deactivated account
Authorization Session-layer enforced — no component-level authorization guard
Data Sensitivity Medium — displays user handle (currentAccount?.handle); triggers account state change via XRPC
On-Device Storage None — no AsyncStorage, MMKV, or SecureStore usage in this component
Deep Link Target Yes — /deactivated is accessible via the app's registered URL scheme
Native Modules Used react-native-safe-area-context (via useSafeAreaInsets)
Attack Surface Medium — 1 XRPC API call, 4 interactive elements, session state mutation, web URL manipulation
Finding Count Critical: 0, High: 2, Medium: 2, Low: 1, Info: 1

SEC-001: Missing Rate Limiting on Account Reactivation Button

Field Value
Severity High
MASVS Category MASVS-AUTH
CWE CWE-799: Improper Control of Interaction Frequency
MASVS Requirement MASVS-AUTH-1 — The app uses secure authentication mechanisms and does not expose them to abuse
Component handleActivate / "Yes, reactivate my account" Button — app/deactivated.tsx
Affected Data AT Protocol account activation state; PDS server resources
Platform Both

Description: The "Yes, reactivate my account" button is not disabled while the handleActivate async call is in-flight (pending = true). The documentation explicitly states: "There is no explicit button disabling, so rapid taps could theoretically trigger multiple concurrent calls." This allows a user — or a script controlling the UI — to dispatch multiple concurrent com.atproto.server.activateAccount XRPC calls before any response is received. While the PDS server should enforce idempotency, repeated concurrent calls may cause race conditions in session state, exhaust server-side rate limits, or produce inconsistent error states that leave the UI in an undefined condition.

Evidence: Section 10 (Error Handling & Edge Cases): "While pending is true, a Loader spinner appears inside the reactivate button. There is no explicit button disabling, so rapid taps could theoretically trigger multiple concurrent calls." Section 17 (Known Issues): "No button disabling during pending: The reactivate button is not disabled while pending is true."

Attack Scenario: A user with a deactivated account opens the app on Android. The reactivation screen is displayed. The user (or a UI automation script) taps the "Yes, reactivate my account" button 10 times in rapid succession before the first XRPC response arrives. Ten concurrent activateAccount POST requests are dispatched to the PDS. If the PDS does not enforce per-account rate limiting, this could trigger server-side errors, cause session state inconsistency when multiple agent.resumeSession() calls resolve concurrently, or exhaust the account's API quota.

Remediation:

  1. Add disabled={pending} to the Button component: <Button ... disabled={pending} onPress={handleActivate}>. This is the minimal fix and is already identified in the known issues.
  2. Additionally, clear the error state at the start of each activation attempt by adding setError(undefined) as the first line of handleActivate, before setPending(true).
  3. Verify the fix on device by enabling slow-network simulation (Charles Proxy or Android emulator network throttling), tapping the button rapidly, and confirming only one XRPC request is dispatched per activation attempt.

Source Evidence: Deactivated (/deactivated) — Sections 6, 10, 17


SEC-002: Internal Error String Comparison Creates Fragile Security Branch

Field Value
Severity High
MASVS Category MASVS-CODE
CWE CWE-1023: Incomplete Comparison with Missing Factors
MASVS Requirement MASVS-CODE-1 — The app only requests necessary permissions and does not contain sensitive data or logic that can be easily extracted
Component handleActivate error handler — app/deactivated.tsx
Affected Data Account reactivation security boundary (App Password vs. main password enforcement)
Platform Both

Description: The security-critical distinction between an App Password session and a full-credential session is enforced client-side by comparing the raw error message string against the literal 'Bad token scope'. The documentation states: "Error: 'Bad token scope' — Sets error to a message instructing the user to sign in with their main password instead of an App Password." This string comparison is a fragile security branch: if the AT Protocol server changes the error message wording, casing, or format in a future protocol version, the client will silently fall through to the generic error handler and display a misleading message — or worse, fail to block the reactivation attempt if the server-side enforcement is also relaxed. The security boundary between App Password and main-password sessions must not depend on client-side string matching of server error messages.

Evidence: Section 5 (Data Loading & API Calls): "Error: 'Bad token scope' — Sets error to a message instructing the user to sign in with their main password instead of an App Password." Section 8 (Business Rules): "If the user's current session was established using an App Password (a scoped credential), the server returns an error with the message 'Bad token scope'. The screen detects this specific error string and surfaces a targeted message."

Attack Scenario: A future AT Protocol PDS update changes the error code from the string 'Bad token scope' to a structured error object with a code field (e.g., { code: 'InvalidTokenScope', message: '...' }). The client-side string comparison no longer matches. The handleActivate handler falls through to the generic error branch and displays "Something went wrong, please try again" instead of the App Password warning. A user with an App Password session repeatedly retries, receiving no actionable guidance. If a future server-side regression also weakens the enforcement, the client provides no secondary defense.

Remediation:

  1. Replace the raw string comparison with a comparison against a typed AT Protocol error code constant. If the @atproto/api SDK exposes typed error classes or error code enumerations for com.atproto.server.activateAccount, use those instead of string literals.
  2. If the SDK does not expose typed errors, define a named constant: const BAD_TOKEN_SCOPE_ERROR = 'Bad token scope' and reference the constant in the comparison, with a comment linking to the AT Protocol lexicon definition.
  3. Add a unit test that asserts the correct error message is displayed when the error string matches BAD_TOKEN_SCOPE_ERROR, and a separate test for the generic fallback path.
  4. Verify by running the test suite and confirming both branches are covered.

Source Evidence: Deactivated (/deactivated) — Sections 5, 8, 14


SEC-003: Misleading Error Message Undermines Security Guidance

Field Value
Severity Medium
MASVS Category MASVS-CODE
CWE CWE-684: Incorrect Provision of Specified Functionality
MASVS Requirement MASVS-CODE-1 — The app does not contain logic errors that affect security-relevant behavior
Component 'Bad token scope' error message string — app/deactivated.tsx
Affected Data User security guidance for App Password vs. main password distinction
Platform Both

Description: The error message displayed when a 'Bad token scope' error is received reads: "Please sign in with your main password to continue deactivating your account." However, the action being performed is reactivation, not deactivation. This is a copy error explicitly documented in Section 17. While this may appear to be a UX issue, it has a security dimension: the message is the primary mechanism by which users are informed that their App Password cannot be used for this sensitive account-state operation. A misleading message may cause users to misunderstand the security boundary, dismiss the error as a bug, or attempt workarounds that bypass the intended credential requirement.

Evidence: Section 10 (Error Handling): "Displays a specific error message: 'You're signed in with an App Password. Please sign in with your main password to continue deactivating your account.' Note: the message says 'deactivating' but the action is reactivation — this appears to be a copy error in the source." Section 17 (Known Issues): "Copy error in error message: The 'Bad token scope' error message reads 'Please sign in with your main password to continue deactivating your account' — but the action being performed is reactivation, not deactivation."

Attack Scenario: A user with an App Password session attempts to reactivate their account. The error message instructs them to sign in with their main password to continue "deactivating" their account. The user, confused by the contradictory instruction (they want to reactivate, not deactivate), dismisses the error as a bug and does not follow the security guidance. They may attempt to use a third-party tool or workaround to bypass the credential requirement, potentially exposing their main password to a phishing surface.

Remediation:

  1. Correct the error message string to read: "You're signed in with an App Password. Please sign in with your main password to reactivate your account."
  2. Ensure the corrected string is updated in all translation catalog files managed by Lingui (.po files for each supported locale).
  3. Verify by triggering the 'Bad token scope' error path in a test environment (e.g., using an App Password session against a local PDS) and confirming the corrected message is displayed.

Source Evidence: Deactivated (/deactivated) — Sections 10, 17

Note: The development team has already identified this issue in Section 17 (Known Issues). Credit is given for awareness; the finding is included because the security impact of misleading credential guidance warrants formal tracking.


SEC-004: Deactivated Route Accessible as Deep Link Without Component-Level Session Guard

Field Value
Severity Medium
MASVS Category MASVS-PLATFORM
CWE CWE-284: Improper Access Control
MASVS Requirement MASVS-PLATFORM-2 — The app validates all input received via IPC mechanisms
Component Route /deactivatedapp/deactivated.tsx
Affected Data Session state; account handle display; account switching UI
Platform Both

Description: The /deactivated route is registered in Expo Router's file-based routing system and is therefore reachable via the app's registered URL scheme as a deep link (e.g., bsky://deactivated). The documentation states: "The route /deactivated is technically accessible as a deep link URL given Expo Router's file-based routing, but it is not a meaningful deep-link target — arriving here without a deactivated session would result in the session layer routing the user elsewhere." The security concern is that the component itself contains no session guard — it relies entirely on the upstream shell/session layer to ensure a deactivated account is present. If the session layer has a bug, race condition, or is bypassed by a crafted deep link, the component will render with currentAccount potentially being an active (non-deactivated) account, displaying the account handle and presenting the reactivation and logout UI to an active-account user. The agent.resumeSession(agent.session!) call with a non-null assertion (SEC-006) would then execute against an already-active session.

Evidence: Section 2 (User Roles & Access Control): "There is no explicit redirect-to-login guard within the component itself — the assumption is that the session layer guarantees a valid (but deactivated) account is present when this screen is rendered." Section 9 (Navigation & Routing): "The route /deactivated is technically accessible as a deep link URL given Expo Router's file-based routing, but it is not a meaningful deep-link target."

Attack Scenario: An attacker crafts a deep link bsky://deactivated and embeds it in a phishing message sent to a Bluesky user with an active (non-deactivated) account. The user taps the link, which opens the app and navigates to /deactivated. If the session layer's routing logic has a timing window or the deep link bypasses the shell's conditional rendering, the user sees the deactivated-account UI with their handle displayed. The user, confused, taps "Yes, reactivate my account," triggering activateAccount against their already-active account. Depending on PDS behavior, this may produce an error or a no-op, but the user experience is disorienting and the logout button presents a social-engineering opportunity.

Remediation:

  1. Add a component-level session guard at the top of the Deactivated component: check that currentAccount exists and that currentAccount.status === 'deactivated' (or the equivalent AT Protocol account status field). If the condition is not met, call router.replace('/') to redirect to the home screen.
  2. This guard should execute before any UI is rendered, using an early return pattern.
  3. Verify by navigating to bsky://deactivated (or the app's registered scheme equivalent) while logged in with an active account and confirming the redirect occurs without rendering the deactivated UI.

Source Evidence: Deactivated (/deactivated) — Sections 2, 9


SEC-005: Non-Null Assertion on Session Object After Activation

Field Value
Severity Low
MASVS Category MASVS-CODE
CWE CWE-476: NULL Pointer Dereference
MASVS Requirement MASVS-CODE-1 — The app does not crash or expose sensitive information due to unhandled exceptions
Component agent.resumeSession(agent.session!)handleActivate in app/deactivated.tsx
Affected Data Session state; potential unhandled runtime error
Platform Both

Description: After a successful activateAccount XRPC call, the code calls agent.resumeSession(agent.session!) using a TypeScript non-null assertion (!) on agent.session. The documentation acknowledges this: "agent.resumeSession(agent.session!) uses a non-null assertion (!) on agent.session. If agent.session is somehow undefined at the point of a successful activation response, this will throw a runtime error." If agent.session is undefined at this point — due to a race condition, an unexpected agent state, or a future refactor — the non-null assertion will cause an unhandled TypeError that crashes the activation flow. The user would be left in a partially activated state with no recovery path presented.

Evidence: Section 14 (Security Considerations): "agent.resumeSession(agent.session!) uses a non-null assertion (!) on agent.session. If agent.session is somehow undefined at the point of a successful activation response, this will throw a runtime error. This is a minor fragility." Section 17 (Known Issues): "Non-null assertion on agent.session: agent.resumeSession(agent.session!) uses ! to assert agent.session is defined."

Attack Scenario: A race condition occurs during activation: the AT Protocol agent's session is cleared by a concurrent logout event (e.g., the user's session expires on the server between the activateAccount response and the resumeSession call). agent.session is undefined. The non-null assertion throws a TypeError. The finally block sets pending = false, but the unhandled error propagates up the call stack. Depending on the error boundary configuration, the app may crash or display a blank screen, leaving the user's account in an activated state on the server but with a broken client session.

Remediation:

  1. Replace the non-null assertion with a null check: if (agent.session) { await agent.resumeSession(agent.session); } else { setError('Session expired. Please sign in again.'); }.
  2. Ensure the finally block still executes setPending(false) in all code paths.
  3. Verify by writing a unit test that mocks agent.session as undefined after a successful activateAccount response and confirms the error state is set rather than a TypeError being thrown.

Source Evidence: Deactivated (/deactivated) — Sections 5, 14, 17

Note: The development team has already identified this issue in Section 17 (Known Issues). Credit is given for awareness.


SEC-006: Web URL Manipulation via history.pushState Before Logout

Field Value
Severity Info
MASVS Category MASVS-PLATFORM
CWE CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
MASVS Requirement MASVS-PLATFORM-1 — The app only requests necessary permissions and handles platform APIs securely
Component onPressLogout / history.pushState(null, '', '/')app/deactivated.tsx (web platform only)
Affected Data Browser navigation history; post-logout URL state
Platform Web (IS_WEB = true)

Description: On the web platform, the logout handler calls history.pushState(null, '', '/') before invoking logoutCurrentAccount('Deactivated'). This is documented as intentional: "On web, history.pushState(null, '', '/') is called before logoutCurrentAccount to reset the URL, since the navigator is about to unmount and cannot reliably call pushState itself." The destination is hardcoded to '/', which is safe. However, this pattern is noted as an Info-level finding because history.pushState is a platform API that manipulates browser history state. If the destination were ever made dynamic (e.g., sourced from a route parameter or query string), it would create an open redirect vulnerability. The current implementation is safe but the pattern warrants documentation for future maintainers.

Evidence: Section 3 (UI Layout): "On web, history.pushState(null, '', '/') is called before logoutCurrentAccount to reset the URL." Section 8 (Business Rules): "Because the navigator tree is about to unmount during logoutCurrentAccount, the web URL is manually reset to / via history.pushState before the logout is triggered."

Attack Scenario: (Current implementation — not exploitable.) Future maintainer modifies onPressLogout to redirect to a URL sourced from a query parameter (e.g., ?returnTo=https://evil.example.com) without validation. The history.pushState call becomes an open redirect, allowing a phishing link to redirect users to an attacker-controlled site after logout.

Remediation:

  1. Add a code comment to the history.pushState(null, '', '/') call explicitly documenting that the destination must remain a hardcoded relative path and must never be sourced from route parameters, query strings, or external input.
  2. If a returnTo parameter is ever added to this flow, implement an allowlist validation that restricts the destination to known internal routes before passing it to history.pushState.
  3. No immediate code change is required for the current implementation.

Source Evidence: Deactivated (/deactivated) — Sections 3, 8


6.2 No Following Feed — /feeds/no-following-feed

Security Profile:

Aspect Assessment
Authentication Implicitly required — useAddSavedFeedsMutation requires an authenticated session
Authorization Delegated to mutation hook — no component-level auth check
Data Sensitivity Low — no PII displayed; writes to user feed preferences
On-Device Storage None — no AsyncStorage, MMKV, or SecureStore usage
Deep Link Target Yes — /feeds/no-following-feed accessible via app URL scheme
Native Modules Used None directly in this component
Attack Surface Low — 1 interactive element, 1 mutation API call, no user input
Finding Count Critical: 0, High: 1, Medium: 1, Low: 1, Info: 0

SEC-007: Silent Mutation Failure Masks Authorization Errors on Feed Write

Field Value
Severity High
MASVS Category MASVS-AUTH
CWE CWE-390: Detection of Error Condition Without Action
MASVS Requirement MASVS-AUTH-2 — The app informs the user of authentication failures and does not silently fail
Component addRecommendedFeeds handler / useAddSavedFeedsMutationapp/feeds/no-following-feed.tsx
Affected Data User feed preferences; session validity indication
Platform Both

Description: The addRecommendedFeeds handler calls addSavedFeeds([{ ...TIMELINE_SAVED_FEED, pinned: true }]) using mutateAsync but does not await the result and has no .catch() handler. The documentation states: "The result is fire-and-forget from the component's perspective" and "errors from the mutation (e.g., network failure, 401 Unauthorized, 500 server error) are not caught or surfaced to the user within this component." This means that if the user's session has expired (401 Unauthorized), the mutation fails silently. The onAddFeed?.() callback is invoked immediately after the mutation is dispatched — before it resolves — so the parent may navigate away or update its state as if the feed was successfully added, when in fact the write failed. The user is left in a state where they believe the following feed has been added, but it has not been persisted to their ATProto preferences.

Evidence: Section 5 (Data Loading & API Calls): "The mutation is called via mutateAsync (Promise-based), but no .then(), .catch(), or await is used in the handler — the result is fire-and-forget from the component's perspective." Section 10 (Error Handling): "No error handling is implemented within this component. If addSavedFeeds rejects (e.g., due to network failure or a server error), the error is silently swallowed at this layer." Section 17 (Known Issues): "Silent mutation failure: The addSavedFeeds call uses mutateAsync but is not awaited and has no .catch() handler."

Attack Scenario: A user's Bluesky session token expires while they are on the No Following Feed screen. The user taps "Click here to add one." The addSavedFeeds mutation fires and immediately receives a 401 Unauthorized response from the ATProto preferences API. The error is silently swallowed. The onAddFeed?.() callback fires, and the parent navigates the user away from the empty-state screen as if the feed was added. The user's feed list remains empty. The user has no indication their session expired or that the action failed. They may not discover the issue until they notice their feed list is still empty, potentially after significant time has passed.

Remediation:

  1. Convert the handler to an async function and await the addSavedFeeds call within a try/catch block:
    const addRecommendedFeeds = async (e: GestureResponderEvent) => {
      e.preventDefault();
      try {
        await addSavedFeeds([{ ...TIMELINE_SAVED_FEED, pinned: true }]);
        onAddFeed?.();
      } catch (err) {
        // Surface error to user — e.g., toast notification or inline error message
        Toast.show('Failed to add feed. Please try again.');
      }
      return false as const;
    };
  2. Move the onAddFeed?.() callback invocation to after the await resolves successfully, so the parent only navigates away on confirmed success.
  3. Verify by simulating a 401 response (e.g., by invalidating the session token in a test environment) and confirming an error message is displayed and the parent does not navigate away.

Source Evidence: No Following Feed (/feeds/no-following-feed) — Sections 5, 6, 10, 17

Note: The development team has already identified this issue in Section 17 (Known Issues). Credit is given for awareness.


SEC-008: Duplicate Feed-Add Requests Due to Missing In-Flight Guard

Field Value
Severity Medium
MASVS Category MASVS-CODE
CWE CWE-362: Concurrent Execution Using Shared Resource with Improper Synchronization
MASVS Requirement MASVS-CODE-1 — The app does not contain logic errors that can be exploited
Component addRecommendedFeeds / InlineLinkTextapp/feeds/no-following-feed.tsx
Affected Data User saved feeds preferences; ATProto preferences API
Platform Both
Related Finding SEC-010

Description: The InlineLinkText component has no disabled state or in-flight guard. The documentation states: "There is no spinner or disabled state on the link while the mutation is in flight. A user could theoretically tap the link multiple times before the first mutation resolves, potentially dispatching duplicate add-feed requests." Unlike the No Saved Feeds screen (SEC-010), which uses disabled={isPending} on its button, this component uses an InlineLinkText with no equivalent disabling mechanism. Multiple concurrent addSavedFeeds calls could result in duplicate feed entries being written to the user's ATProto preferences, corrupting the feed list state.

Evidence: Section 10 (Error Handling & Edge Cases): "There is no spinner or disabled state on the link while the mutation is in flight. A user could theoretically tap the link multiple times before the first mutation resolves, potentially dispatching duplicate add-feed requests. No debouncing or in-flight guard is implemented." Section 17 (Known Issues): "Duplicate tap vulnerability: There is no in-flight guard (e.g., a isLoading state disabling the link) to prevent the user from tapping the link multiple times."

Attack Scenario: A user on a slow network connection taps "Click here to add one." The mutation is dispatched but takes 3 seconds to resolve due to network latency. The user, seeing no loading indicator, taps the link two more times. Three concurrent addSavedFeeds calls are dispatched. Each call attempts to add TIMELINE_SAVED_FEED to the user's preferences. Depending on the ATProto preferences API's idempotency behavior, this may result in duplicate feed entries in the user's saved feeds list, requiring manual cleanup.

Remediation:

  1. Introduce a local isLoading state variable (useState(false)) in the component, or consume the isPending state from useAddSavedFeedsMutation.
  2. Pass isLoading to the addRecommendedFeeds handler as a guard: if (isLoading) return; at the top of the handler.
  3. Alternatively, replace InlineLinkText with a Button or Pressable component that supports a disabled prop, and set disabled={isPending} from the mutation hook.
  4. Verify by enabling slow-network simulation and tapping the link rapidly, confirming only one API request is dispatched per interaction sequence.

Source Evidence: No Following Feed (/feeds/no-following-feed) — Sections 5, 6, 10, 17

Note: The development team has already identified this issue in Section 17 (Known Issues). Credit is given for awareness.


SEC-009: Feed Route Accessible as Deep Link Without Authentication Guard

Field Value
Severity Low
MASVS Category MASVS-PLATFORM
CWE CWE-284: Improper Access Control
MASVS Requirement MASVS-PLATFORM-2 — The app validates all input received via IPC mechanisms
Component Route /feeds/no-following-feedapp/feeds/no-following-feed.tsx
Affected Data User feed preferences; ATProto preferences API
Platform Both

Description: The /feeds/no-following-feed route is accessible via the app's registered URL scheme as a deep link. The documentation states: "If an unauthenticated user somehow reached this route, the useAddSavedFeedsMutation hook would likely fail at the API layer when the mutation is triggered, but no explicit redirect or gated UI is implemented within this component itself." While the mutation failure would prevent unauthorized writes, the component renders without any authentication check, potentially displaying the empty-state UI to unauthenticated users and allowing them to trigger a mutation that fails silently (per SEC-007).

Evidence: Section 2 (User Roles & Access Control): "If an unauthenticated user somehow reached this route, the useAddSavedFeedsMutation hook would likely fail at the API layer when the mutation is triggered, but no explicit redirect or gated UI is implemented within this component itself." Section 9 (Navigation & Routing): "Deep linking to /feeds/no-following-feed is supported automatically by Expo Router's file-based routing."

Attack Scenario: An unauthenticated user receives a deep link bsky://feeds/no-following-feed in a message. They tap the link, which opens the app. The app renders the No Following Feed empty-state UI without checking for an authenticated session. The user taps "Click here to add one." The mutation fires, receives a 401 Unauthorized response, and fails silently (per SEC-007). The user sees no error and no indication they need to log in. The experience is confusing and the silent failure compounds the issue identified in SEC-007.

Remediation:

  1. Add a component-level authentication guard: check for an authenticated session (e.g., via useSession()) at the top of the component. If no session is present, render a "Please sign in to continue" message or redirect to the sign-in screen.
  2. This guard is a defense-in-depth measure complementing the upstream navigator guard.
  3. Verify by navigating to the route while signed out and confirming the redirect or gated UI is displayed.

Source Evidence: No Following Feed (/feeds/no-following-feed) — Sections 2, 9


6.3 No Saved Feeds Of Any Type — /feeds/no-saved-feeds-of-any-type

Security Profile:

Aspect Assessment
Authentication Implicitly required — useOverwriteSavedFeedsMutation requires an authenticated session
Authorization Delegated to mutation hook — no component-level auth check
Data Sensitivity Medium — destructive overwrite of user's entire saved feeds configuration
On-Device Storage None — no AsyncStorage, MMKV, or SecureStore usage
Deep Link Target Yes — /feeds/no-saved-feeds-of-any-type accessible via app URL scheme
Native Modules Used None directly in this component
Attack Surface Medium — 1 interactive element, 1 destructive mutation API call, no user input, @atproto/common-web TID generation
Finding Count Critical: 0, High: 0, Medium: 2, Low: 0, Info: 0

SEC-010: Destructive Feed Overwrite Without Confirmation or Runtime Guard

Field Value
Severity Medium
MASVS Category MASVS-CODE
CWE CWE-693: Protection Mechanism Failure
MASVS Requirement MASVS-CODE-1 — The app does not contain logic errors that can be exploited to cause unintended behavior
Component addRecommendedFeeds / useOverwriteSavedFeedsMutationapp/feeds/no-saved-feeds-of-any-type.tsx
Affected Data User's entire saved feeds configuration (ATProto actor preferences)
Platform Both

Description: The "Use recommended" button triggers useOverwriteSavedFeedsMutation, which replaces the user's entire saved feeds list with a hardcoded set of recommended feeds. The documentation explicitly flags this: "The calling context is responsible for ensuring this component is only rendered when the user's saved feeds list is genuinely empty. Rendering this component when the user has existing feeds and allowing them to press the button would silently destroy their current feed configuration." There is no runtime guard within the component to verify the feeds list is actually empty before executing the overwrite, no confirmation dialog, and no undo mechanism. The protection is purely by convention — a JSDoc comment instructs callers to only render this component when feeds are empty — but this is not enforced at runtime. A rendering bug in a parent component, or a deep-link navigation to this route, could expose the destructive button to users who have existing feeds.

Evidence: Section 8 (Business Rules): "The calling context is responsible for ensuring this component is only rendered when the user's saved feeds list is genuinely empty. Rendering this component when the user has existing feeds and allowing them to press the button would silently destroy their current feed configuration." Section 14 (Security Considerations): "The most significant security/data-integrity concern is the overwrite semantics of the mutation. If this component is rendered incorrectly (i.e., when the user does have saved feeds), pressing the button will silently destroy the user's existing feed configuration with no confirmation dialog or undo mechanism." Section 17 (Known Issues): "No confirmation dialog for destructive action."

Attack Scenario: A parent component has a rendering bug where it mounts NoSavedFeedsOfAnyType while the user's saved feeds list is non-empty (e.g., due to a race condition where the feeds query has not yet resolved). The user sees the "Use recommended" button and taps it. overwriteSavedFeeds executes, replacing the user's carefully curated list of 20 saved feeds with the 5 hardcoded recommended feeds. The user's feed configuration is permanently destroyed with no warning, no confirmation, and no undo. The user must manually re-add all their feeds.

Remediation:

  1. Add a runtime guard within addRecommendedFeeds: before calling overwriteSavedFeeds, verify that the current saved feeds list is empty. This can be done by reading the current feeds from the TanStack Query cache or by accepting a currentFeedsCount prop from the parent.
  2. If currentFeedsCount > 0, display a confirmation Alert (React Native's built-in Alert.alert) before proceeding: "This will replace your current feeds with our recommendations. Are you sure?"
  3. Consider implementing a soft-delete or backup mechanism: before overwriting, store the current feeds list in a temporary query cache entry that can be restored within a short window.
  4. Verify by rendering the component when the user has existing feeds and confirming the confirmation dialog appears before any write is executed.

Source Evidence: No Saved Feeds Of Any Type (/feeds/no-saved-feeds-of-any-type) — Sections 8, 14, 17

Note: The development team has already identified this issue in Section 17 (Known Issues). Credit is given for awareness.


SEC-011: Silent Failure on Destructive Overwrite Mutation

Field Value
Severity Medium
MASVS Category MASVS-AUTH
CWE CWE-390: Detection of Error Condition Without Action
MASVS Requirement MASVS-AUTH-2 — The app informs the user of authentication failures and does not silently fail
Component addRecommendedFeeds / useOverwriteSavedFeedsMutationapp/feeds/no-saved-feeds-of-any-type.tsx
Affected Data User saved feeds preferences; session validity indication
Platform Both
Related Finding SEC-007

Description: The addRecommendedFeeds handler awaits overwriteSavedFeeds but has no try/catch block and renders no error UI. The documentation states: "The component does not implement a local try/catch around overwriteSavedFeeds. Error propagation is delegated to the mutation hook's internal error state. No error UI is rendered within this component." Unlike SEC-007 (which uses a fire-and-forget pattern), this handler does await the mutation — but without error handling, an exception thrown by overwriteSavedFeeds will propagate as an unhandled promise rejection. This is a particularly significant gap because the mutation is destructive: if the write partially fails (e.g., the request is sent but the response is lost due to a network interruption), the user's feeds may be in an indeterminate state with no feedback.

Evidence: Section 10 (Error Handling & Edge Cases): "The component does not render any error UI. If overwriteSavedFeeds rejects, the error is surfaced only through the mutation hook's internal state. There is no try/catch in addRecommendedFeeds, no error Text element, and no toast/snackbar triggered on failure." Section 17 (Known Issues): "Silent failure on mutation error: The addRecommendedFeeds function awaits overwriteSavedFeeds but has no try/catch block and renders no error feedback."

Attack Scenario: A user's session token expires between the time they open the No Saved Feeds screen and the time they tap "Use recommended." The overwriteSavedFeeds mutation fires and receives a 401 Unauthorized response. The mutation hook throws an error. Because there is no try/catch in addRecommendedFeeds, the error propagates as an unhandled promise rejection. The button's isPending state returns to false (the mutation hook handles this internally), but no error message is displayed. The user sees the button become re-enabled with no explanation. They may tap it again, triggering another failed mutation, or assume the operation succeeded when it did not.

Remediation:

  1. Wrap the overwriteSavedFeeds call in a try/catch block within addRecommendedFeeds:
    const addRecommendedFeeds = async () => {
      onAddRecommendedFeeds?.();
      try {
        await overwriteSavedFeeds(
          RECOMMENDED_SAVED_FEEDS.map(f => ({ ...f, id: TID.nextStr() }))
        );
      } catch (err) {
        Alert.alert('Error', 'Failed to add recommended feeds. Please try again.');
      }
    };
  2. If the session has expired (401), surface a message directing the user to sign in again.
  3. Verify by simulating a network failure during the mutation and confirming the error message is displayed.

Source Evidence: No Saved Feeds Of Any Type (/feeds/no-saved-feeds-of-any-type) — Sections 5, 10, 17

Note: The development team has already identified this issue in Section 17 (Known Issues). Credit is given for awareness.


7. Finding Inventory

ID Screen Title Severity MASVS CWE MASVS Req Platform Remediation Phase
SEC-001 Deactivated Missing Rate Limiting on Account Reactivation Button High MASVS-AUTH CWE-799 MASVS-AUTH-1 Both Short-Term
SEC-002 Deactivated Internal Error String Comparison Creates Fragile Security Branch High MASVS-CODE CWE-1023 MASVS-CODE-1 Both Short-Term
SEC-007 No Following Feed Silent Mutation Failure Masks Authorization Errors on Feed Write High MASVS-AUTH CWE-390 MASVS-AUTH-2 Both Short-Term
SEC-003 Deactivated Misleading Error Message Undermines Security Guidance Medium MASVS-CODE CWE-684 MASVS-CODE-1 Both Medium-Term
SEC-004 Deactivated Deactivated Route Accessible as Deep Link Without Component-Level Session Guard Medium MASVS-PLATFORM CWE-284 MASVS-PLATFORM-2 Both Medium-Term
SEC-008 No Following Feed Duplicate Feed-Add Requests Due to Missing In-Flight Guard Medium MASVS-CODE CWE-362 MASVS-CODE-1 Both Medium-Term
SEC-010 No Saved Feeds Of Any Type Destructive Feed Overwrite Without Confirmation or Runtime Guard Medium MASVS-CODE CWE-693 MASVS-CODE-1 Both Medium-Term
SEC-011 No Saved Feeds Of Any Type Silent Failure on Destructive Overwrite Mutation Medium MASVS-AUTH CWE-390 MASVS-AUTH-2 Both Medium-Term
SEC-005 Deactivated Non-Null Assertion on Session Object After Activation Low MASVS-CODE CWE-476 MASVS-CODE-1 Both Backlog
SEC-009 No Following Feed Feed Route Accessible as Deep Link Without Authentication Guard Low MASVS-PLATFORM CWE-284 MASVS-PLATFORM-2 Both Backlog
SEC-006 Deactivated Web URL Manipulation via history.pushState Before Logout Info MASVS-PLATFORM CWE-601 MASVS-PLATFORM-1 Web only Backlog

8. MASVS Compliance Summary

Note: Compliance percentages are calculated based on requirements assessable from the provided documentation. Requirements that cannot be assessed due to documentation gaps are marked N/A and excluded from the percentage calculation. This is a documentation-based assessment — live penetration testing is required for full compliance verification.

Category Topic Requirements Assessed Pass Fail N/A Compliance
MASVS-STOR Data Storage 4 4 0 0 100% — No on-device sensitive data storage in assessed screens; session state storage mechanism requires separate assessment
MASVS-CRYPTO Cryptography 1 1 0 0 100% — TID generation is not a cryptographic operation; no cryptographic APIs used in assessed screens
MASVS-AUTH Authentication 5 2 3 0 40% — Rate limiting absent on reactivation; silent auth-error swallowing on two feed screens; session guard relies solely on upstream layer
MASVS-NETWORK Network Communication 3 0 0 3 Limited Assessment — Certificate pinning, cleartext traffic policy, and TLS configuration not documented for assessed screens; requires penetration testing
MASVS-PLATFORM Platform Interaction 5 2 3 0 40% — Deep-link routes lack component-level session guards; jailbreak/root detection not documented; platform security config not documented
MASVS-CODE Code Quality 6 2 4 0 33% — Non-null assertion, fragile error string comparison, misleading error copy, missing in-flight guards, silent failure on destructive mutation
MASVS-RESILIENCE Resilience 3 0 0 3 Limited Assessment — Jailbreak/root detection, anti-tampering, and certificate pinning effectiveness cannot be assessed from documentation alone; requires device-level testing

9. Remediation Roadmap

Phase 1: Immediate (0–7 days) — Critical Findings

No Critical findings identified in the assessed screens.

Phase 1 Total Effort: 0 person-days


Phase 2: Short-Term (1–4 weeks) — High Findings

Phase 2 Total Effort: ~2.5 person-days


Phase 3: Medium-Term (1–3 months) — Medium Findings

Phase 3 Total Effort: ~4 person-days


Phase 4: Backlog — Low and Info Findings

Phase 4 Total Effort: ~1.25 person-days


Total Estimated Remediation Effort: ~7.75 person-days


10. Positive Security Patterns

Pattern 1: App Password Security Boundary Surfaced to User


Pattern 2: Logout Source Tagging for Audit Attribution


Pattern 3: Post-Activation Cache Invalidation Before Session Resume


Pattern 4: useCallback Memoization on Security-Sensitive Handlers


11. Glossary

Security Standards and Frameworks

MASVS (Mobile Application Security Verification Standard): The OWASP standard defining security requirements for mobile applications. Version 2.0 organizes requirements into seven categories: STOR, CRYPTO, AUTH, NETWORK, PLATFORM, CODE, and RESILIENCE.

MASVS-STOR: MASVS category covering secure data storage on the device. Requires that sensitive data (tokens, PII, credentials) is stored in encrypted, platform-appropriate storage (Keychain on iOS, Keystore on Android) rather than plain-text storage like AsyncStorage.

MASVS-CRYPTO: MASVS category covering cryptographic practices. Requires use of strong, up-to-date algorithms and secure key management.

MASVS-AUTH: MASVS category covering authentication and session management. Requires secure authentication mechanisms, proper session handling, and protection against authentication abuse.

MASVS-NETWORK: MASVS category covering network communication security. Requires TLS for all connections, certificate validation, and optionally certificate pinning.

MASVS-PLATFORM: MASVS category covering platform interaction security. Requires secure handling of deep links, inter-app communication, platform APIs, and permissions.

MASVS-CODE: MASVS category covering code quality and build settings. Requires absence of logic errors, debug code, and insecure coding patterns.

MASVS-RESILIENCE: MASVS category covering resilience against reverse engineering and tampering. Includes jailbreak/root detection, anti-debugging, and code obfuscation.

MSTG (Mobile Security Testing Guide): The OWASP companion guide to MASVS providing testing methodology and procedures for verifying MASVS requirements.

ASVS (Application Security Verification Standard): The OWASP standard for web application and API security verification requirements, applied here to the AT Protocol API interactions.

NIST SP 800-163 Rev 1: NIST publication on vetting the security of mobile applications, providing risk taxonomy and control assessment methodology.

CWE (Common Weakness Enumeration): A community-developed list of software and hardware weakness types maintained by MITRE. Used to classify vulnerability types in this report.

CVSS (Common Vulnerability Scoring System): A standardized framework for rating the severity of security vulnerabilities on a 0–10 scale.


CWE Weakness Types Cited

CWE-284: Improper Access Control: The software does not restrict or incorrectly restricts access to a resource from an unauthorized actor.

CWE-362: Concurrent Execution Using Shared Resource with Improper Synchronization: The program contains a code sequence that can run concurrently with other code, and the sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently.

CWE-390: Detection of Error Condition Without Action: The software detects a specific error condition but takes no actions to handle the error.

CWE-476: NULL Pointer Dereference: A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid but is NULL, typically causing a crash or exit.

CWE-601: URL Redirection to Untrusted Site ('Open Redirect'): A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect.

CWE-684: Incorrect Provision of Specified Functionality: The code does not function according to its documented specification, leading to incorrect behavior that may be exploited.

CWE-693: Protection Mechanism Failure: The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product.

CWE-799: Improper Control of Interaction Frequency: The software does not properly limit the number or frequency of interactions that an actor can perform in a given time period.

CWE-1023: Incomplete Comparison with Missing Factors: The software performs a comparison that does not include all necessary factors, potentially leading to incorrect security decisions.


Mobile-Specific Terms

AsyncStorage (@react-native-async-storage/async-storage): A React Native key-value storage system that persists data to the device's file system in plain text (unencrypted). Not suitable for storing sensitive data such as authentication tokens, passwords, or PII. Equivalent to localStorage on the web.

MMKV (react-native-mmkv): A high-performance key-value storage library for React Native backed by Tencent's MMKV framework. Data is not encrypted by default; encryption must be explicitly configured. Not suitable for sensitive data without encryption enabled.

Keychain (iOS): The iOS secure credential storage system managed by the operating system. Data stored in the Keychain is encrypted and access-controlled by the OS. The appropriate storage mechanism for authentication tokens and credentials on iOS.

Keystore (Android): The Android system for storing cryptographic keys and credentials in a hardware-backed secure enclave. The appropriate storage mechanism for authentication tokens and credentials on Android.

SecureStore (expo-secure-store): An Expo library that provides an abstraction over iOS Keychain and Android Keystore, offering encrypted key-value storage suitable for sensitive data in Expo-managed and bare workflow apps.

ATS (App Transport Security): An iOS security feature that enforces secure network connections. ATS requires HTTPS for all network connections by default and can be configured in Info.plist. Exceptions must be explicitly declared and justified.

Deep link: A URL that opens a specific screen or state within a mobile application. Deep links can use custom URI schemes (e.g., bsky://) or universal/app links tied to a domain.

Universal link (iOS): An iOS mechanism that associates a domain with an app via an apple-app-site-association file hosted on the domain. Universal links are more secure than custom URI schemes because they are tied to domain ownership and cannot be hijacked by other apps.

App link (Android): The Android equivalent of iOS Universal Links. App links associate a domain with an app via an assetlinks.json file hosted on the domain, providing verified deep-link handling that resists hijacking.

URI scheme: A custom URL scheme registered by an app (e.g., bsky://, myapp://). Any app on the device can register the same scheme, making custom URI schemes vulnerable to hijacking on both iOS and Android.

Certificate pinning: A security technique where the app validates that the server's TLS certificate matches a known, expected certificate or public key, rather than trusting any certificate signed by a trusted CA. Protects against man-in-the-middle attacks using fraudulent certificates.

Jailbreak detection (iOS): Techniques used by an app to detect whether the iOS device has been jailbroken (had its security restrictions removed). Jailbroken devices may expose app data stored in the Keychain or allow code injection.

Root detection (Android): Techniques used by an app to detect whether the Android device has been rooted (had its security restrictions removed). Rooted devices may allow other apps to read the app's private data.

Native module: A module that bridges React Native JavaScript code to platform-native (Swift/Objective-C on iOS, Kotlin/Java on Android) code. Native modules can access platform APIs not available in JavaScript and ship compiled native binaries.

JSI (JavaScript Interface): The new React Native architecture's mechanism for synchronous, direct communication between JavaScript and native code, replacing the asynchronous bridge. Used by Turbo Modules.

Turbo Module: The new React Native architecture's implementation of native modules using JSI for improved performance and type safety.


AT Protocol / Application Domain Terms

AT Protocol (ATProto): The open, decentralized social networking protocol underlying Bluesky. Defines lexicons (schemas) for all data types and API operations.

PDS (Personal Data Server): The AT Protocol server that hosts a user's data and handles authentication. Each user's agent communicates with their PDS.

XRPC: The remote procedure call protocol used by AT Protocol. Procedures are either queries (GET) or procedures (POST).

App Password: A scoped credential generated by a Bluesky user for use by third-party applications. App Passwords have limited permissions and cannot perform sensitive account operations such as reactivation. Detected server-side via the 'Bad token scope' error.

DID (Decentralized Identifier): A globally unique, persistent identifier for an AT Protocol account (e.g., did:plc:abc123). Used to distinguish accounts in the multi-account session store.

TID (Time-based ID): A lexicographically sortable, time-ordered identifier format used throughout the ATProto ecosystem. Generated by TID.nextStr() from @atproto/common-web.

Feed generator: An ATProto server-side service that produces a custom ordered list of posts. Users can save feed generators to their profile.

Saved feed: A feed generator that a user has explicitly saved to their Bluesky account preferences, making it accessible from the Feeds tab.

Pinned feed: A saved feed promoted to a prominent position in the feeds list.


React Native / Expo Platform Terms

React Native: A framework for building native mobile applications using JavaScript and React. Renders to native platform UI components rather than a WebView.

Expo: A platform and set of tools built on top of React Native that simplifies development, build, and deployment of React Native apps.

Managed workflow: An Expo project configuration where Expo manages the native build configuration. Developers do not directly modify native iOS/Android project files.

Bare workflow: An Expo project configuration where developers have full access to and control over the native iOS and Android project files, while still using Expo libraries.

Expo Router: A file-based routing library for React Native and Expo that maps file paths in the app/ directory to navigation routes, similar to Next.js.


Acronyms

ASVS: Application Security Verification Standard (OWASP) ATS: App Transport Security (iOS) CWE: Common Weakness Enumeration CVSS: Common Vulnerability Scoring System DAST: Dynamic Application Security Testing DID: Decentralized Identifier IDOR: Insecure Direct Object Reference MASVS: Mobile Application Security Verification Standard (OWASP) MSTG: Mobile Security Testing Guide (OWASP) NIST: National Institute of Standards and Technology PDS: Personal Data Server (AT Protocol) PII: Personally Identifiable Information SAST: Static Application Security Testing SSRF: Server-Side Request Forgery TID: Time-based Identifier (ATProto) TLS: Transport Layer Security XSS: Cross-Site Scripting XRPC: Cross-service Remote Procedure Call (AT Protocol)


Severity Levels

Critical (CVSS 9.0–10.0): Exploitable vulnerability allowing unauthorized access, data breach, or system compromise. Fix immediately (within 24 hours).

High (CVSS 7.0–8.9): Significant vulnerability with high probability of exploitation and serious impact. Fix within 1 week.

Medium (CVSS 4.0–6.9): Moderate vulnerability requiring specific conditions to exploit. Fix within 30 days.

Low (CVSS 0.1–3.9): Minor security weakness with limited exploitability or impact. Fix within 90 days.

Info (CVSS 0.0): Security best practice recommendation or hardening suggestion. Consider for future work.


End of Security Audit Report (Mobile) Generated by DocAgent — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.