Security

    React Native source maps in production: keep them private

    A minified JavaScript stack trace beside the same trace resolved through a source map into readable file and function names.

    Source maps are the files that translate a minified JavaScript bundle back into the original source, with file names, function names and often the full original code. Every React Native build can produce them, and crash reporting depends on them to turn an unreadable stack trace into a useful one. The security question is where they end up. A source map shipped inside the app, published at a public URL or left in a public repository hands anyone the readable version of code that minification was supposed to make harder to study.

    Short answer

    Generate source maps for every release build, upload them privately to your crash reporting service, and keep them out of the app binary and out of anything publicly reachable. A React Native app's JavaScript bundle can be extracted from an IPA or APK either way, so minification and missing maps are not a secret-keeping mechanism. The real value of keeping maps private is that they make reverse engineering slower and expose fewer comments, internal names and file structures. Anything that must stay secret, such as API keys or business logic that enforces payments, should not be in the client bundle at all.

    What a source map contains

    A source map is a JSON file that links positions in the generated bundle to positions in the original files. The format can also embed the original source text directly, in a field called sourcesContent, and many build tools do so by default. The MDN reference for the SourceMap header describes how browsers find maps for web code; mobile bundles do not use that header, but the file format is the same.

    So a full source map for a React Native app is close to a copy of the app's JavaScript source tree: file paths such as src/payments/validateCoupon.ts, original variable and function names, comments, and sometimes configuration that a developer assumed would never leave the repository.

    Why React Native teams generate them

    Release builds are minified, so a crash in production arrives as a stack trace full of single-letter names and line one, column forty thousand. Without the matching source map that trace is close to useless. With it, the crash reporter shows the original file, function and line.

    That is why crash reporting tools ask for maps at build time. Sentry's React Native documentation describes uploading them as part of the release process, and the React Native guide to debugging release builds explains generating and using them. Generating maps is good practice. Distributing them is where it goes wrong.

    Where source maps leak

    Leak pathHow it happensWhat is exposedHow to check
    Bundled in the appBuild copies the map into the IPA or APK assetsFull source to anyone who unzips the appList files in the built artifact
    Public web hostingOTA or web build uploads maps next to bundlesSource to anyone who guesses the URLRequest the bundle URL with .map appended
    Public repositoryBuild output committed to a public repoSource plus historySearch the repository for .map files
    CI artifactsMaps stored as public build artifactsSource to anyone with the linkReview artifact visibility settings
    Crash reporter project left publicShared or misconfigured projectSource and stack tracesReview project access

    The first two are the common ones. A build script that copies the whole output directory into the app's assets will include the map if it is there. An over-the-air or web deployment that uploads the build folder will publish the map alongside the bundle, often at a predictable URL.

    What an attacker gains, honestly

    It is worth being precise about the impact, because overstating it leads to the wrong fixes. The JavaScript bundle of a React Native app can be pulled from the IPA or APK by anyone who downloads the app, and tools exist to decompile even the Hermes bytecode format into readable code. Without a source map, an attacker still gets the logic, with minified names.

    What the map adds is speed and context: real names that explain intent, file structure that maps the app's features, comments that sometimes describe known weaknesses or temporary workarounds, and occasionally values a developer believed were stripped. It turns hours of reading into minutes. That matters for targeted attacks on a specific app, for cloning, and for finding the client-side checks that guard paid features.

    It does not create a vulnerability on its own. If the app's security depends on attackers not reading the bundle, the problem is already there, and the map only makes it obvious. This is the weakness described in CWE-540, inclusion of sensitive information in source code: the sensitive information was in the code before the map exposed it.

    What should never be in the bundle

    Because the bundle is readable regardless, a short list of things should never be in it:

    • Secret API keys. Server keys, admin tokens and third-party secret keys belong on a server. Publishable or restricted client keys are fine when the provider designs them for that.
    • Authorisation decisions. Whether a user can access a premium feature, see another user's data or perform an admin action must be decided on the server.
    • Pricing and payment validation. Purchase receipts are validated server-side.
    • Internal endpoints and debug switches that bypass normal checks.

    The posts on hard-coded secrets in minified bundles and mobile app error messages cover two of the most common versions of this problem.

    Configuring builds so maps stay private

    The goal is simple: maps are produced, uploaded to the crash reporter, and then deleted from anything that ships or deploys.

    Native app builds

    For iOS and Android release builds, generate the map into a build output location that is not part of the app's bundled assets. Upload it to the crash reporter in a build step, then confirm the final IPA and APK do not contain any .map files. That last check is the one teams skip, and it is a one-line listing of the archive contents.

    Over-the-air and web builds

    Over-the-air update services and web deployments upload build output to hosting. Configure the publish step to exclude .map files, or delete them after uploading to the crash reporter and before publishing. Then request a published bundle URL with .map appended and confirm the response is a 404 rather than a JSON file. The post on over-the-air update security covers the update channel itself.

    Repositories and CI

    Add build output directories and map files to the ignore list, search existing history for committed maps, and set CI artifacts to private. If maps were ever public, assume they were copied and treat any secret that appeared in them as exposed.

    Checking a built app

    The quickest check is direct: unzip the IPA or APK and search for .map files and for the readable bundle. An automated pass over the built artifact, such as PTKD.com, flags bundled source maps, hard-coded keys and debug configuration in one run, which is useful on every release because a build script change can reintroduce a map that was excluded last month. The published update and web URLs need a separate request, since they are not part of the binary.

    The OWASP MASVS resilience requirements treat making static analysis harder as a defence-in-depth measure, which is the right frame here: keeping maps private raises the cost of reverse engineering, and it should never be the only thing protecting a secret or a business rule.

    A release checklist for source maps

    1. Generate maps for every release build.
    2. Upload them to the crash reporter with the release version.
    3. Delete them from the build output before packaging and publishing.
    4. List the IPA and APK contents and confirm no .map files.
    5. Request published bundle URLs with .map appended and confirm a 404.
    6. Keep secrets and authorisation logic out of the bundle entirely.

    Hermes and the bytecode question

    Many React Native apps now ship Hermes bytecode rather than plain JavaScript, which some teams treat as protection in itself. It is a different format, not an encrypted one. Decompilers for Hermes bytecode produce readable output, and while names are still minified, the structure and logic come back. Source maps for Hermes builds map that bytecode back to the original source in the same way as for plain bundles, so the same rule applies: generate them, upload them privately, never ship them.

    Comments and names are part of the exposure

    The least appreciated content of a source map is the commentary. Developers write comments for colleagues: notes about temporary workarounds, references to internal tickets, explanations of why a check is client-side for now, names of internal services. Variable and function names do the same, with names such as skipPaywallForTesting or legacyAdminBypass describing exactly what an attacker would look for. Code review is the right place to catch these, and a private source map is the backstop.

    When a map has already leaked

    If a map was bundled in a released app or published at a public URL, removing it from the next release does not undo the exposure, because old versions remain installed and copies may exist. Treat the incident as a disclosure of the code: rotate any secret that appeared in it, review the client-side checks it revealed and move any that matter to the server, and note the affected versions. Then fix the build so the next release is clean, and add the artifact listing to the release checklist so it stays that way.

    Who should be able to see maps internally

    Private does not mean everyone in the company. Crash reporting projects often have broad access because many people need to read stack traces, and source maps uploaded there are visible to anyone with project access. Limit project membership to people who triage crashes, use single sign-on so access ends when someone leaves, and avoid exporting resolved traces with full source context into public issue trackers or support tools, which is a quieter version of the same leak.

    Web builds of the same app

    Teams that ship a web version of a React Native or Expo app face the same issue in its most familiar form. Web bundlers often emit maps by default and hosting platforms serve whatever is in the output folder. Check the web deployment with the same request, the bundle URL with .map appended, because a carefully cleaned mobile release does nothing about a web build that publishes the full source next to its scripts.

    Automating the check

    The simplest durable fix is a step in the release pipeline that fails the build if any .map file is found in the final IPA, APK or publish folder. It takes a few lines of shell, runs in seconds, and prevents the most common regression: a well-meaning change to the build configuration months later that quietly starts bundling maps again. Pair it with a scheduled request against the published bundle URLs, so a hosting change cannot reintroduce public maps unnoticed.

    Most teams find the check catches something within the first few releases, usually a map left behind by a dependency's own build step rather than by the app's configuration.

    What to take away

    Source maps are essential for debugging production crashes and should be generated for every release, then uploaded privately and removed from anything users or the public can reach. The JavaScript bundle can be extracted with or without them, so treat keeping maps private as a way to slow reverse engineering, not as a way to hide secrets. Put API keys, authorisation decisions and payment validation on the server, check every built artifact and published URL for stray map files, and treat any map that was ever public as a disclosure of the code it described.

    • #react native
    • #source maps
    • #reverse engineering
    • #crash reporting
    • #hermes
    • #secrets

    Frequently asked questions

    Should React Native source maps be included in the app?
    No. Generate them for each release build and upload them privately to your crash reporting service, then make sure the IPA, APK and any published update or web bundle do not contain them. Shipping a map gives anyone who downloads the app a readable copy of your JavaScript source, including file names, function names and comments.
    Can someone read my React Native code without a source map?
    Yes. The JavaScript bundle, or Hermes bytecode, can be extracted from the app and decompiled. Without a map the names are minified, which slows reading but does not prevent it. That is why secrets and authorisation logic must never live in the bundle, whether or not the map is private.
    How do I check whether my source maps are public?
    Unzip the IPA and APK and search for .map files, then request your published over-the-air or web bundle URLs with .map appended and confirm they return a 404. Also check repository history and CI artifact visibility. If a map was ever public, assume it was copied and treat any secret it contained as exposed.
    Do I still need source maps if they are a security risk?
    Yes. Without them, production crash reports are unreadable stack traces, and debugging slows dramatically. The risk comes from distributing maps, not from generating them. Uploading them only to your crash reporter, with restricted project access, gives you readable crashes without publishing your source.
    Is hiding source maps enough to protect my business logic?
    No. It only raises the effort needed to understand the bundle. Any rule that protects revenue or data, such as premium access, coupon validation or permission checks, must be enforced on the server, because a determined attacker can read and modify the client code with or without the map.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free