◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Getting Started
◈ Getting Started/2026-10-20Intermediate

A 40-Line Script to Check Whether Rork Wrote an API Key Into Your Code

How to check code Rork generated for hard-coded API keys, using the files you export to GitHub. A short Python script with three signals (key shape, name, EXPO_PUBLIC_ prefix), plus the order to follow when a real key turns up.

Rork594API KeysSecurity10GitHub SyncSecretsCode Review3

Open More → Code in a Rork project and a file tree appears, with src/ and components/ stretching down the page. I've scrolled through that tree and closed it again more than once, with no idea where to start reading.

Rork's documentation says keys belong in Secrets, not in your code and not in a chat message. What we rarely do is check that the promise held, that no key actually ended up in the code.

Here's the short version: you don't have to read every file. Keys have habits, in how they look and in what they're called, and you can have a machine look for exactly those. Below is a small script, and what to do when it finds something.

Get the code onto your own machine first

The Code tab is readable as it is. On the free plan the editor shows "Read-only" and won't let you edit, but the docs say you can still read every file. If you only want to look with your own eyes, that's enough.

To run a script, connect GitHub and clone the repository. According to the docs, a connected project becomes an ordinary repository with one commit per agent turn. Point the script at the cloned folder and you're set.

One reassuring detail: values you put in Secrets live with the project, not in the code, and are not committed (the docs' FAQ says so). The reverse follows. If a key shows up in the GitHub copy, it got there without going through Secrets.

Keys show up in three recognizable ways

I look for three things.

SignalHow to find itTypical hit
The shape of the keyProvider prefixes such as sk-, sk_live_, AIza, ghp_OpenAI-style keys, Stripe secret keys, GitHub tokens
A secret-looking name given a literal valueNames containing SECRET, TOKEN or API_KEY, assigned a string of 16+ charactersA weather API key written into a constant
A secret under EXPO_PUBLIC_EXPO_PUBLIC_ followed by SECRET, SERVICE_ROLE and similarA Supabase service role key read under a public name

The third one ties straight to a warning in the docs. In Rork, a variable starting with EXPO_PUBLIC_ is sent to the app itself, and everything else stays on the server. Give a private key that prefix and anyone who installs the app can extract it.

The script

It runs on the Python 3 standard library alone and looks at .ts, .tsx, .js, .swift, .kt, .plist and a few other file types.

#!/usr/bin/env python3
"""Scan code exported from Rork (via GitHub) for hard-coded keys."""
import re, sys, pathlib
 
SKIP_DIRS = {"node_modules", ".git", "Pods", "build", "dist", ".expo"}
EXTS = {".ts", ".tsx", ".js", ".jsx", ".json", ".swift", ".kt", ".env", ".plist", ".md"}
 
# 1) The shape of the key itself
SHAPES = {
    "OpenAI-style sk-":  re.compile(r"\bsk-[A-Za-z0-9_-]{20,}"),
    "Stripe secret key":    re.compile(r"\b[sr]k_(live|test)_[A-Za-z0-9]{16,}"),
    "Google API key":  re.compile(r"\bAIza[0-9A-Za-z_-]{35}\b"),
    "GitHub token":  re.compile(r"\bgh[pousr]_[A-Za-z0-9]{36,}"),
    "Resend":           re.compile(r"\bre_[A-Za-z0-9]{20,}"),
}
# 2) A secret-looking name assigned a long literal string
ASSIGN = re.compile(
    r"""(?i)\b([A-Z0-9_]*(SECRET|TOKEN|PRIVATE|SERVICE_ROLE|API_?KEY)[A-Z0-9_]*)\b\s*[:=]\s*["']([^"'\s]{16,})["']"""
)
# 3) A secret-looking name under the public prefix
PUBLIC_SECRET = re.compile(
    r"(?i)\bEXPO_PUBLIC_[A-Z0-9_]*(SECRET|PRIVATE|SERVICE_ROLE|TOKEN)[A-Z0-9_]*"
)
 
def walk(root):
    for p in root.rglob("*"):
        if p.is_file() and p.suffix in EXTS and not any(s in p.parts for s in SKIP_DIRS):
            yield p
 
hits = 0
for path in walk(pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")):
    for n, line in enumerate(path.read_text(errors="ignore").splitlines(), 1):
        found = [name for name, rx in SHAPES.items() if rx.search(line)]
        if ASSIGN.search(line) and "process.env" not in line:
            found.append("secret-looking name with literal value")
        if PUBLIC_SECRET.search(line):
            found.append("secret-looking name under EXPO_PUBLIC_")
        for f in found:
            hits += 1
            print(f"{path}:{n}: [{f}]")   # never print the line itself
print(f"-- {hits} hits")
sys.exit(1 if hits else 0)

It prints only the file, line number and kind of match. I left out the line's contents on purpose, so that screen-sharing or pasting the output into Rork's chat can't leak the key a second time.

Run it from the cloned folder:

python3 scan_keys.py ./my-rork-project

What it printed on a sample project

I can't use real keys here, so I built a small project with fake values that only imitate the shapes: a file with a weather API key in a constant, one with an sk- style string in apiKey, one reading EXPO_PUBLIC_SERVICE_ROLE_KEY, a Firebase config JSON, and one clean file.

The output:

demo/src/weather.ts:1: [secret-looking name with literal value]
demo/src/ai.ts:1: [OpenAI-style sk-]
demo/src/ai.ts:1: [secret-looking name with literal value]
demo/src/ai.ts:3: [secret-looking name under EXPO_PUBLIC_]
demo/config/firebase.json:1: [Google API key]
-- 5 hits

Three things stand out. Lines like process.env.RESEND_API_KEY, which only read an environment variable, and a setting like TOKEN_REFRESH_SECONDS = 3600 were not flagged. Line 1 of ai.ts matched two signals at once, so one line can be counted twice by design. And the last point matters most.

A Firebase warning is usually not a leak

The final hit, in firebase.json, is not a real leak. The API key inside a Firebase config is described in Google's documentation as different in nature from an ordinary secret key. It ships inside the app by design, and the protection comes from Firebase's security rules and restrictions, not from hiding the key.

The GoogleService-Info.plist in my own iOS apps has an API key field too. I included .plist files in the scan for coverage, and I read hits there as false positives.

So treat the output as a list of what to doubt first, not as a breach notice. Sort each hit with three questions:

  • Would it matter if anyone who installs the app could see this value?
  • If yes, who could use it, and who would receive the bill or the damage?
  • Is the key still live, or already revoked?

When it's real, the order is everything

The tempting mistake is to delete the key from the code in Rork and call it done. The docs give four steps for a key that has ended up somewhere public:

  1. Revoke or rotate the key in the provider's own dashboard. That is the only step that actually stops it working.
  2. Create a new key and copy its value.
  3. In Rork's Secrets, put the new value under the same variable name and press Save.
  4. If the key was in your code, ask Rork to remove it and read the value from the variable instead.

Fix only the code first and the old key is still in your GitHub history, where it still works. Revoking at the provider always comes first.

A request that names the file and the destination tends to land better:

src/ai.ts has an API key written directly on line 1.
Remove it and read OPENAI_API_KEY from a server function instead.
Do not use an EXPO_PUBLIC_ name.

Where a key should live in the first place, environment variables or an Edge Function, is covered separately in Is That API Key Inside Your App?.

One thing to do today

A place for your keys isn't decided until you've checked that they're there. Run this script once before you publish, and again at the end of any day you added a big feature. That alone is a good first habit.

Zero hits means the signals didn't match, not that you're safe. Still, it costs far less than reading dozens of files by eye, and it catches the most common mistake first.

Once keys live on the server, the next question is how much to trust what the app tells that server. A premium article covers why a rewarded-ad payout shouldn't depend on a signal from the device alone: Don't Pay Out Rewarded-Ad Credit on the Device's Word Alone.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ Getting Started2026-10-19
My plugins Array Was Empty, and Info.plist Still Had Nine Permission Strings
Even with an empty plugins array, every Expo package you install can add permission strings to Info.plist. I ran prebuild on SDK 58, counted them, and wrote the settings and a small audit script that remove them.
◈ Getting Started2026-10-17
Before Moving to Expo SDK 58, List What Disappears in Your Own Code
A small audit script that scans your project for the APIs Expo SDK 58 removes, changes, or deprecates, with the output from a test project and the pitfalls I only noticed once I ran it.
◈ Getting Started2026-10-16
The night the Play Console wouldn't let me submit — release notes cap at 500 characters per language
Google Play release notes are limited to 500 characters per language. Here is how I got unstuck after pasting a long update memo, plus a short check script and what to do when an old draft release blocks a new one.