Learn Hermes JS Engine - Secure Runtime Practices
Episode 12 of 23

Learn Hermes JS Engine - Secure Runtime Practices

This episode enters Phase 4 on runtime security: how to load Hermes JavaScript bundles securely, avoid remote code execution and unsafe eval, and practical mitigations for mobile apps and edge runtimes.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 11 we closed Phase 3 by organizing Hermes configuration so builds are reproducible, validated in CI, and integrated with the release pipeline. Now we enter Phase 4: Networking & Security. If performance is about speed, security is about what happens once your app is in the hands of someone with bad intentions.

Episode 12 focuses on secure runtime practices: how your Hermes JavaScript bundle is loaded securely, how to avoid remote code execution and unsafe eval, then real mitigation examples for mobile apps and edge runtimes. The roadmap: the threat model, secure loading, avoiding RCE, mitigations on mobile, then mitigations on the edge.

Why JavaScript Bundles Need to Be Secured

Hermes executes a bundle compiled into .hbc bytecode. It's important to understand from the start: Hermes bytecode is not encryption. A .hbc file can be unpacked back into disassembly using the hbcdump tool, and its internal structure is documented. What protects the app isn't bytecode "secrecy" but integrity and control over what gets executed.

The typical threat model for a mobile app:

  • Tampering: someone unpacks the APK/IPA, modifies the bundle, then redistributes it as a "modified version".
  • MITM: a bundle downloaded from the network at runtime can be intercepted and swapped mid-flight.
  • Injection: external data (API, deep links, storage) is inserted into a path that ultimately gets executed as code.

Info

The golden rule: anything you control (bundle, configuration, native code) must always be assumed readable and analyzable by an attacker. Security comes from verification, not from hiding code.

Securely Loading JavaScript Bundles

The safest way to load a Hermes bundle is embedding it inside the app assets at build time, not downloading it from a server at runtime. In React Native, the index.android.bundle file (which in a release build is .hbc bytecode) is automatically packed into the APK. As long as you don't replace this mechanism, the bundle is covered by the app's signature in the app store.

If your app is forced to fetch the bundle from the network — for example, for over-the-air updates — never trust the downloaded result blindly. Verify it first against a known hash:

JSVerify bundle integrity before execution
import { checkBundleSignature } from "./crypto-utils";
 
const EXPECTED_SHA256 = "ab3f...0c9d";
 
async function loadRemoteBundle(url) {
  const bundle = await fetch(url).then((res) => res.arrayBuffer());
  const digest = await checkBundleSignature(bundle);
  if (digest !== EXPECTED_SHA256) {
    throw new Error("Integritas bundel gagal diverifikasi");
  }
  return bundle;
}

A static hash like the one above only makes sense for scheduled releases. For more frequent updates, replace the mechanism with cryptographic signatures: a public key embedded in the app, the bundle signed with a private key on the server, and the app verifying that signature. Always combine this with HTTPS and, where possible, certificate pinning.

Avoiding Remote Code Execution and Unsafe eval

The good news: Hermes has never supported local eval from the start. Calling eval in Hermes won't give access to local variables or define new variables outside — the behavior deviates from the spec and in some cases throws an error outright. This isn't a bug, but a design decision that also shrinks the attack surface: there's no free dynamic code execution path.

Even so, you can still shoot yourself in the foot. The dangerous patterns to avoid: new Function, template strings that execute code, and the most classic — fetch then eval. Here's the bad example:

JSDangerous pattern: executing data from the network
const config = await fetch("https://api.example.com/feature").then((r) => r.text());
const feature = new Function(config);
feature();

And here's the safe replacement — data stays data, never becomes code:

JSSafe pattern: parse data, don't execute
const payload = await fetch("https://api.example.com/feature").then((r) => r.json());
 
const feature = {
  enabled: payload.enabled === true,
  label: String(payload.label ?? "fitur baru"),
};
const code = `return ${userInput};`;
const result = new Function(code)();

Note that JSON.parse isn't a one-hundred-percent replacement for every eval case — it only parses data structures. That's exactly the point: if all you need is data, use a data format. If you need logic, write that logic as a real function that takes input, not as a string compiled on the spot.

Warning

Also be careful with helpers that wrap new Function to "build queries" or "template engines". In Hermes, such helpers often break on device due to local eval limitations — besides being a security hazard, they're not portable either.

Mitigations in React Native Mobile Apps

Here's the minimum set that must always be active for mobile release builds:

  • Make sure Hermes is active and dev mode is turned off in release — dev mode enables the inspector and debug paths that aren't needed in production.
  • Don't include source maps inside the production APK. Use source maps for deobfuscation in the backend crash reporting, not for shipping to devices.
  • Enable R8/Proguard so native Java/Kotlin code is minified, preventing leakage of internal class and method names.
  • Never pass untrusted data into native modules that execute strings (for example, evalString provided by some debugging modules).

The compiler flags in android/app/build.gradle can also be locked down for consistency across developers:

Lock Hermes flags for build reproducibility
react {
  hermesFlags = ["-O", "-output-source-map"]
}

With -output-source-map, production stack traces can be mapped back to the original source — but make sure the resulting .map file is stored in CI for observability tools, not bundled.

Mitigations in Edge Runtimes

In edge runtimes (serverless, Cloudflare Workers, or embedded Hermes in Node.js), the principle is the same, but the threat shifts: now what's untrusted is the request input, not the bundle. The first thought that should come to mind: the code being executed is your own code, while all external input is data. Never build a path where input can become instructions:

Validate input at the edge, don't execute it
import { z } from "zod";
 
const schema = z.object({
  command: z.enum(["list", "detail", "refresh"]),
  id: z.string().max(36),
});
 
export async function handler(req: Request) {
  const parsed = schema.safeParse(await req.json());
  if (!parsed.success) return new Response("invalid", { status: 400 });
  return runKnownCommand(parsed.data);
}

With z.enum, the command value is only allowed from a list we know. There's no path where foreign input becomes a dynamically called function name — the pattern still maps strings to already-registered functions, not executing strings.

Conclusion

Hermes runtime security isn't about hiding bytecode, but about controlling what can be executed and ensuring what's executed is genuinely yours. With embedded and verified bundles, a ban on dynamic eval patterns, and strict build keys, the attack surface shrinks drastically.

The essentials to take home:

  • Hermes bytecode can be read back — security comes from integrity and execution control, not secrecy.
  • Bundles should be embedded in app assets; if they must be downloaded, verify the hash or cryptographic signature first.
  • Avoid eval, new Function, and fetch-then-execute patterns in all app code.
  • Turn off dev mode and don't include source maps in mobile release builds.
  • In edge runtimes, treat all input as data and validate it against a whitelist.

In episode 13, we continue with Hermes and JIT / AOT Security Considerations: how the AOT choice affects the security profile, what signed bundles and content integrity are, and how to integrate hardening and privacy into the runtime. See you there!

Learn Hermes JS Engine - Secure Runtime Practices | Learn Hermes JS Engine