Back to Blog
critical SEVERITY8 min read

How Plaintext Secret Storage Happens in Cloudflare Workers (wrangler.toml) and How to Fix It

A critical misconfiguration in `platforms/m365/wrangler.toml` left developers one copy-paste away from committing live API keys directly into git history. The fix adds an explicit warning comment blocking the `[vars]` anti-pattern and adds `.dev.vars` to `.gitignore`, ensuring secrets flow through Cloudflare's encrypted `wrangler secret` mechanism instead of plaintext config. This matters because git history is permanent — a key committed even once can be extracted long after it's "deleted."

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

This vulnerability is a hardcoded-secrets risk (CWE-312: Cleartext Storage of Sensitive Information) in a Cloudflare Workers configuration file (`platforms/m365/wrangler.toml`). The `[vars]` section of `wrangler.toml` is committed to git in plaintext, meaning any API key placed there — such as `ANTHROPIC_API_KEY` or `API_TOKEN` — becomes permanently readable in version history. The fix adds a prominent `WARNING` comment to block the `[vars]` anti-pattern, redirects developers to `wrangler secret put`, and adds `.dev.vars` to `.gitignore` to prevent local dev secrets from being committed.

Vulnerability at a Glance

cweCWE-312
fixAdded explicit WARNING comment blocking [vars] usage for secrets and added .dev.vars to .gitignore
riskLive API keys committed to git history, readable by anyone with repo access
languageTOML / Cloudflare Workers (JavaScript/TypeScript)
root causewrangler.toml [vars] section is version-controlled plaintext with no enforcement against storing secrets there
vulnerabilityCleartext Storage of Sensitive Information (API Key in Config File)

The Problem With "Just Don't Put Keys Here"

Documentation comments are not security controls.

In the platforms/m365/wrangler.toml file for this Cloudflare Workers deployment, a comment instructed developers to set ANTHROPIC_API_KEY and API_TOKEN via wrangler secret put. That's correct advice. But the comment sat directly adjacent to the [vars] section — the exact place where a developer under deadline pressure might paste a key "just to test something" and accidentally commit it.

That's the vulnerability: not a missing encryption library, not a broken authentication flow, but a configuration file that was one distracted commit away from leaking production credentials into version control forever.


The Vulnerability Explained

What Makes [vars] Dangerous for Secrets

Cloudflare Workers has two ways to inject values into a Worker's environment:

  1. [vars] in wrangler.toml — plaintext key-value pairs, committed to git alongside your code
  2. wrangler secret put — encrypted secrets stored in Cloudflare's secret store, never written to disk or version control

The distinction is critical. Anything in [vars] is:
- Committed to git in plaintext
- Visible in every clone, fork, and CI/CD log that checks out the repo
- Permanent in git history, even if later deleted

Here's what the vulnerable section looked like before the fix:

# platforms/m365/wrangler.toml (before fix, line 7-11)

ALLOWED_ORIGINS = ""

# Set secrets via:
#   wrangler secret put ANTHROPIC_API_KEY
#   wrangler secret put API_TOKEN

The problem isn't what's written — it's what's missing. There's no barrier between the [vars] block above and the comment below. A developer who sees ALLOWED_ORIGINS = "" and needs to add ANTHROPIC_API_KEY for local testing might naturally write:

# DANGEROUS — do not do this
[vars]
ALLOWED_ORIGINS = ""
ANTHROPIC_API_KEY = "sk-ant-api03-..."   # ← now in git forever
API_TOKEN = "Bearer eyJ..."              # ← same

Once that commit is pushed — even to a private repository — the key is in the git object database. Deleting the line in a follow-up commit does not remove it from history.

The Attack Scenario

The PR's exploitation scenario is concrete: an attacker gains read access to the repository via leaked Git credentials, an exposed CI/CD log, or a compromised developer account. With that access, they run:

git log --all --full-history -- platforms/m365/wrangler.toml
git show <commit-hash>:platforms/m365/wrangler.toml

If an ANTHROPIC_API_KEY was ever committed to [vars], it appears in plain text. The attacker now has:
- Full access to the Anthropic API under the victim's billing account
- Potentially unbounded LLM API spend
- Access to any conversation history or fine-tuning data tied to that key

The API_TOKEN exposure is similarly severe — depending on what it authenticates, it could grant access to M365 data, admin endpoints, or downstream services.

Why the 503 Fallback Doesn't Help

The codebase includes a defensive control: when secrets are missing at runtime, the Worker returns a 503 error rather than proceeding without credentials. This is good practice — but it protects against absent secrets, not exposed ones. If a key is committed to git, it isn't missing. The Worker runs fine. The attacker also runs fine, using the same key from outside the Worker entirely.


The Fix

The fix is two targeted changes that together close the gap between documentation intent and developer behavior.

Change 1: Explicit Warning in wrangler.toml

# Before
# Set secrets via:
#   wrangler secret put ANTHROPIC_API_KEY
#   wrangler secret put API_TOKEN

# After
# WARNING: NEVER add ANTHROPIC_API_KEY or API_TOKEN to [vars] above.
# [vars] is committed to git and is NOT encrypted. Use wrangler secrets:
#   wrangler secret put ANTHROPIC_API_KEY
#   wrangler secret put API_TOKEN

The change is minimal in lines but significant in intent. The original comment read like a suggestion. The new comment reads like a guardrail. By naming the specific variables (ANTHROPIC_API_KEY, API_TOKEN) and explaining why [vars] is unsafe ("committed to git and is NOT encrypted"), the warning gives a developer the information they need at the exact moment they might make the mistake.

This is the principle of contextual security guidance — security instructions are most effective when they appear at the point of decision, not in a README three directories away.

Change 2: .dev.vars Added to .gitignore

# .gitignore

+# Cloudflare Workers local dev secrets — must never be committed
+.dev.vars
+
 # Internal lead documents — not for public consumption

Cloudflare Workers supports a .dev.vars file for local development — it's the local equivalent of wrangler secret put, letting developers set secret values for wrangler dev without touching wrangler.toml. But .dev.vars is only safe if it's gitignored. Without this entry, a developer creating .dev.vars for local testing could accidentally commit it.

The comment in the .gitignore entry matters too: "must never be committed" explains the why, making it less likely a future developer will remove the entry thinking it's unnecessary.

What This Fix Does NOT Do (And Why That's Okay)

This fix doesn't add runtime enforcement — it doesn't fail the build if [vars] contains a secret-shaped value. That would be a stronger control, but it's also a more invasive change. The current fix correctly scopes the change to the minimum necessary: clear guidance at the point of risk, and a gitignore entry that closes the .dev.vars vector. The PR notes that "the project's existing tests still pass, so intended behavior is unchanged."


Prevention & Best Practices

1. Use Wrangler Secrets for All Sensitive Values

# Correct approach for all secrets
wrangler secret put ANTHROPIC_API_KEY
wrangler secret put API_TOKEN

# For local dev, use .dev.vars (gitignored)
echo 'ANTHROPIC_API_KEY=sk-ant-...' >> .dev.vars

Never use [vars] for anything you wouldn't want in a public git repository.

2. Audit Git History for Committed Secrets

If you suspect a secret was ever committed, don't just delete it — rotate it immediately and scan history:

# Scan git history for secret patterns
trufflehog git file://. --since-commit HEAD~100
git-secrets --scan-history

Tools like truffleHog, gitleaks, and git-secrets can find secrets in historical commits.

3. Pre-commit Hooks

Add a pre-commit hook that blocks commits containing secret patterns:

# Install gitleaks as a pre-commit hook
gitleaks protect --staged

Or use the pre-commit framework with the detect-secrets hook.

4. CI/CD Secret Scanning

Enable GitHub's built-in secret scanning (Settings → Security → Secret scanning) and push protection. This blocks pushes containing known secret formats before they reach the remote.

5. Principle of Least Privilege for Config Files

Review every configuration file in your repository and ask: "If this file were public, what would be exposed?" Config files that need secrets should reference environment variables or secret store paths — never inline values.

OWASP and CWE Alignment

This vulnerability maps to:
- CWE-312: Cleartext Storage of Sensitive Information
- CWE-798: Use of Hard-coded Credentials
- OWASP A02:2021 — Cryptographic Failures (storing sensitive data without encryption)
- OWASP A05:2021 — Security Misconfiguration (insecure default configuration patterns)


Key Takeaways

  • wrangler.toml [vars] is git-committed plaintext — treat it like a public file and never store ANTHROPIC_API_KEY, API_TOKEN, or any credential there, regardless of how temporary the intent is.
  • A comment saying "don't do X" is not a control — the original comment correctly pointed to wrangler secret put, but without a warning explaining why, it provided no friction against the dangerous alternative.
  • .dev.vars needs to be gitignored explicitly — Cloudflare's local dev secrets file is only safe if it never reaches version control; the absence of this gitignore entry was a second vector for the same class of exposure.
  • Git history is permanent — rotating a key after committing it is mandatory, not optional. Deleting the line in a follow-up commit does not remove it from git log.
  • Contextual warnings outperform distant documentation — the fix's WARNING: NEVER add ANTHROPIC_API_KEY or API_TOKEN to [vars] comment is more effective than any README section because it appears at the exact point of risk.

How Orbis AppSec Detected This

  • Source: The [vars] section of platforms/m365/wrangler.toml at line 11, where environment variables are defined in plaintext and committed to version control
  • Sink: Any git clone, CI/CD checkout, or repository browser that reads wrangler.toml — the "dangerous call site" is the git commit itself, which permanently stores the value
  • Missing control: No explicit prohibition against placing ANTHROPIC_API_KEY or API_TOKEN in [vars]; no .dev.vars gitignore entry to block the local dev secret file from being committed
  • CWE: CWE-312 — Cleartext Storage of Sensitive Information
  • Fix: Added a WARNING comment naming the specific forbidden variables and explaining that [vars] is git-committed plaintext, plus added .dev.vars to .gitignore

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

The vulnerability in platforms/m365/wrangler.toml is a reminder that security misconfiguration doesn't require a sophisticated exploit — sometimes it just requires a developer who doesn't know which config section is safe for secrets. The fix is small: two files, a warning comment, and a gitignore entry. But the impact is significant: it closes the gap between "we told developers to use wrangler secrets" and "we made it hard for developers to accidentally do the wrong thing."

Cloudflare Workers makes secret management easy with wrangler secret put. The failure mode here wasn't a missing feature — it was a missing guardrail. The lesson for any project using configuration files: document the safe path, warn against the unsafe path, and use tooling to enforce the difference.


References

Frequently Asked Questions

What is cleartext storage of sensitive information in wrangler.toml?

It occurs when API keys or tokens are placed in the [vars] section of wrangler.toml, which is committed to git as plaintext and readable by anyone with repository access — including in historical commits.

How do you prevent API key exposure in Cloudflare Workers?

Use `wrangler secret put SECRET_NAME` to store secrets encrypted in Cloudflare's secret store. Never place sensitive values in the [vars] section of wrangler.toml, which is version-controlled.

What CWE is hardcoded API key exposure?

CWE-312 (Cleartext Storage of Sensitive Information) and CWE-798 (Use of Hard-coded Credentials) both apply. The root issue is that secrets are stored in a form that can be read without decryption.

Is adding .gitignore enough to prevent secret exposure?

No. .gitignore prevents future commits of a file, but if a secret was already committed, it remains in git history and must be rotated immediately. Defense-in-depth requires both .gitignore and enforced use of wrangler secrets.

Can static analysis detect API keys committed in wrangler.toml?

Yes. Tools like Semgrep, truffleHog, and git-secrets can scan for secret patterns in config files. Orbis AppSec's multi-agent AI scanner flagged this exact pattern in wrangler.toml line 11.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #11

Related Articles

critical

How Hardcoded API Keys Happen in JavaScript and How to Fix Them

A critical security vulnerability was discovered in `src/js/init.js` where a Bugsnag API key was hardcoded directly into client-side JavaScript, making it visible to anyone who inspects the page source or JavaScript bundle. The fix replaces the hardcoded string with an environment variable reference (`import.meta.env.VITE_BUGSNAG_API_KEY`), ensuring the key is injected at build time rather than baked into the shipped code. This pattern is one of the most common — and most avoidable — secrets exp

high

How Hardcoded API Keys happen in JavaScript and how to fix it

A critical security vulnerability was discovered in `javascripts/common.js` where Firebase API keys, auth domains, and sender IDs were hardcoded directly in client-side JavaScript. Any user who opened browser DevTools or viewed page source could extract these credentials and make unauthorized calls to the Firebase Realtime Database and Yandex Translation services. The fix moves all sensitive configuration values to environment variables, ensuring secrets never reach the client bundle.

critical

How Plaintext Credential Storage Happens in Node.js Config Files and How to Fix It

A critical vulnerability in `config.js` allowed OAuth tokens and user IDs to silently fall back to empty strings when environment variables were unset, enabling credential bypass and potential hardcoded secret exposure. The fix removes the `|| ""` fallback pattern, ensuring credentials are either properly set or explicitly `undefined`, and updates downstream checks to use truthy evaluation instead of empty-string comparison. This change closes a subtle but dangerous gap that could have allowed A

critical

How Hardcoded API Keys happen in JavaScript plugins and how to fix them

A critical hardcoded API key was discovered in `plugins/ocr.js` at line 21, where the OCR integration used a plaintext fallback credential `'K81241004488957'` whenever the `OCR_API_KEY` environment variable was absent. This exposed a live API key to anyone with repository access, enabling unauthorized use of the OCR service. The fix removes the hardcoded fallback entirely and fails fast with a clear error message when the required environment variable is not configured.

high

How Information Disclosure happens in Go dependency management and how to fix it

CVE-2026-42151 is a high-severity information disclosure vulnerability in the Prometheus monitoring library (github.com/prometheus/prometheus) that exposed Azure OAuth client secrets through the Prometheus configuration API endpoint. Applications depending on versions prior to v0.311.3 were at risk of leaking sensitive Azure credentials to anyone with access to the config API. The fix involves upgrading the dependency in go.mod from v0.310.0 to v0.311.3.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript