untangle
Summary
ExpandThe untangle codebase is a React Native and Expo mobile application, targeting both iOS and Android, that allows users to save Instagram posts into user-defined categories and synthesize consolidated "cheat sheets" from that saved content using Anthropic's Claude API. The architecture pairs a native iOS share extension — which writes shared post URLs into an App-Group `UserDefaults` store for the JavaScript shell to ingest — with Firebase Authentication for identity and two parallel server-side AI proxies: a Vercel serverless function at `api/generate.js` and a Firebase Callable Cloud Function in `functions/index.js`. Native Vision-based OCR modules are bridged into React Native to extract text from images and video frames before that text is forwarded to the AI tier, which keeps the visual-content pipeline on-device and reduces what is shipped to third-party services. The result is a relatively focused application with clearly demarcated layers: native capture and OCR, a JavaScript app shell with a single ingestion screen, an authentication context, and a dedicated AI proxy tier that holds the Anthropic credential.
Several deliberate security patterns are visible throughout the project. The `ANTHROPIC_API_KEY` is read from environment variables on both server tiers, and a comment in `src/config.js` confirms that the key was intentionally migrated out of the client bundle, indicating the developer recognized the risk of embedding third-party secrets in a distributable mobile app. Firebase Authentication is integrated through `AuthContext`, which exposes a token-based identity model, and the client in `src/services/ai.js` correctly attaches the user's Firebase ID token as a Bearer credential on outbound calls to the AI proxy. The Firebase Callable variant of the proxy enforces `context.auth` and explicitly raises an `unauthenticated` error when the caller has no Firebase identity, demonstrating that the developer understands and applies the canonical Cloud Functions authentication pattern.
The overall posture reflects a small project written with reasonable security awareness: secrets are kept server-side, identity flows through Firebase rather than ad-hoc tokens, and the surface area exposed to untrusted input is intentionally narrow. There is no use of `eval`, dynamic `Function` construction, template-literal SQL, or `child_process` sinks, and storage utilities are consistent across the codebase. The dominant weakness is concentrated in the Vercel proxy, where the Bearer token check is purely structural rather than cryptographic, and where outbound `fetch` calls accept caller-supplied URLs without restriction. Because the Firebase Callable mirror of the same handler implements proper authentication, the Vercel oversight reads as a localized regression rather than a systemic failure of the security model.
Findings
2 issues identified
Bearer token never validated on Vercel AI proxy
api/generate.js:6
View Details
Description
The /api/generate Vercel handler (the endpoint the mobile client actually calls per src/services/ai.js) only checks that the Authorization header begins with ’Bearer ’. The token is never decoded or signature-verified. The client sends a Firebase ID token but the server never calls firebase-admin verifyIdToken or any equivalent, so any non-empty bearer string is accepted.
Impact
Unauthenticated callers can drain the developer’s ANTHROPIC_API_KEY by issuing arbitrary Claude requests at scale; combined with H-1 the same endpoint becomes an unauthenticated SSRF primitive. There is no rate limiting, so abuse is bounded only by Anthropic quotas/budget caps.
Recommendation
Verify the Firebase ID token with firebase-admin’s verifyIdToken before any downstream work; reject on failure. Add per-uid rate limiting.
Fix
Initializes firebase-admin once per cold start using service-account env vars and verifies the caller's Firebase ID token. Any forged/empty/expired token is rejected with 401, closing the auth bypass.
api/generate.js
Authenticated SSRF in Firebase callable function generateCheatSheet
functions/index.js:11
View Details
Description
downloadImageAsBase64 in the Firebase callable function fetches any URL passed by an authenticated caller. Sign-up is open, so any user can supply arbitrary post.imageUrl / post.imageUrls entries and force the Cloud Function worker to issue outbound HTTP(S) requests. The only filter is url.startsWith(‘http’).
Impact
Authenticated users can use the function as an HTTP request proxy attributed to the developer’s Firebase project, enabling probing of reachable internal services, attacker-server pings (timing/exfil), and indirect content reflection through the Anthropic response.
Recommendation
Apply the same HTTPS Instagram-CDN allowlist used elsewhere before any fetch call.
Fix
Defines an HTTPS Instagram-CDN allowlist and gates both the helper and its caller behind it, preventing authenticated users from coercing the Cloud Function into fetching arbitrary URLs.
functions/index.js
Conclusion
ExpandThe untangle codebase is a small but coherently structured React Native and Expo application with thoughtful separation of concerns across its identity, AI proxy, and native capture layers. Code quality is generally good for a project of this scope: services are modular, storage helpers are consistent, the OCR bridge is encapsulated in a single native module surface, and the AI client centralizes outbound traffic in src/services/ai.js rather than scattering API calls across screens. The developer’s intent to keep the Anthropic key server-side is clearly visible in both the inline comments and the existence of two parallel proxy implementations, and the Firebase Callable variant demonstrates the correct authentication idiom for that platform. These foundations make the application meaningfully easier to harden than a codebase without comparable structure.
That said, the residual risk is concentrated in a single hot spot. The api/generate.js Vercel proxy — which is the route actually wired up in the client — performs only a structural check on the Authorization header, accepting any value that begins with "Bearer " without verifying the token against Firebase. The same handler additionally performs unrestricted outbound fetch calls against caller-supplied image URLs, which converts the endpoint into both an open billing channel for the developer’s Anthropic key and an unauthenticated server-side request forgery primitive. The Firebase Cloud Function variant carries a smaller-blast-radius version of the SSRF issue, since it is gated by context.auth and reachable only by authenticated callers, but the underlying outbound fetch pattern still warrants tightening across both proxies.
In its current state the project should not be considered deployment-ready for broader release. The recommended remediation path, in priority order, is to verify Firebase ID tokens in api/generate.js so that the Vercel and Callable proxies converge on a single authentication contract; to apply an Instagram-CDN URL allowlist on both fetch paths to neutralize the SSRF primitive; to introduce per-user rate limiting and an Anthropic budget ceiling so that any future regression has a hard upper bound on cost; and to replace the placeholder debug-signing configuration in android/app/build.gradle with a production keystore before any Play Store distribution. With those changes in place, the residual risk of the audited surface should drop to a level appropriate for a consumer mobile application of this scope.
Legal Disclaimer: This report covers the code submitted for analysis. It does not account for infrastructure, deployment configuration, third-party dependencies, or changes made after the audit date. Automated analysis may produce false positives or miss context-dependent vulnerabilities. audited.xyz provides this report “as is” without warranty of any kind.