API Keys Don't Belong in URLs: The nebula-mesh Operator Token Leak That Exposes Your Cluster
A critical vulnerability in nebula-mesh exposes freshly-minted operator API keys via redirect URL query parameters, leaking them to browser history, Referer headers, and proxy logs. Here's how the attack works, how to detect it, and how to build API key management that doesn't leak secrets.
Nebula-mesh is a service mesh for edge and IoT networks — a lightweight control plane that manages how containers and devices talk to each other. Operators use it to configure routes, apply security policies, and manage node identities. An API key for an operator is essentially a root credential: it can authenticate to the mesh's management API and reconfigure anything. Leaking it isn't just a data breach; it's a network takeover. And yesterday, the maintainers disclosed that the key to that kingdom was being handed out via a simple redirect URL.
Here's the thing: if you're building an API or a management dashboard, you never, ever put secrets in a URL. Query parameters, path segments, fragments — doesn't matter. Once a secret touches the browser's address bar, you've lost control of it. The advisory GHSA-9pg3-25fq-p6cc (published June 10, 2026) is a textbook example of why this rule matters, and the fallout is nastier than a quick code fix.
The Vulnerability at Hand
The issue lives in internal/web/operators.go at line 251. The function handleOperatorCreateAPIKey generates a fresh 32-byte bearer token, stores a hash of it in the database, and then — critically — constructs a redirect URL that shoves the raw token straight into a query parameter:
HTTP/2 302 Found
Location: /ui/operators/<id>?new_key=<raw-token>&key_The operator's browser follows that redirect, and at that moment the token is scattered across a dozen different places. The advisory rates this as High severity — and that feels right, given the sensitivity of operator keys.
I've seen this pattern before in internal dashboards and, terrifyingly, in DeFi frontends where private keys get loaded into URLs, leading to wallet draining attacks. The mechanism is the same, and the damage is always worse than you think.
Where the Token Goes: Attack Surface
A URL with a secret in it doesn't just sit in the address bar. It travels. Let's map every place it ends up.
1. Browser History
Every browser stores full URLs. An attacker with even temporary access to the operator's machine (physical, via malware, or a shared account) can pull ~/.bash_history, chrome://history, or the SQLite history databases and grep for new_key=. No exploit needed. A malicious browser extension with history permission can scrape the entire history silently. I've tested this: a 30-line Chrome extension can exfiltrate the last 10,000 URLs in seconds.
2. Referer Headers
When the page /ui/operators/<id>?new_key=... loads, any subresource request (images, scripts, analytics beacons) or any subsequent navigation from that page sends a Referer header containing the full URL. Consider:
- A link to documentation on
docs.nebula-mesh.io - An analytics pixel from
metrics.example.com - A font hosted on a CDN
All those third parties receive the token. Even same-origin requests can leak the token to internal services that log Referer. The Referrer-Policy header can mitigate this (e.g., no-referrer strips the entire header), but it's often not set, and when it is, the default strict-origin-when-cross-origin still sends the full path and query for same-origin requests. In this case, the redirect originates from the app itself, so the token is already in the URL before any policy can kick in.
# A band-aid, not a fix
add_header Referrer-Policy "no-referrer" always;That nginx directive would stop Referer leakage, but the token would still live in history and logs. It's a superficial protection that might lull you into a false sense of security.
3. Proxy, Load Balancer, and WAF Logs
HTTP intermediaries — nginx, Envoy, HAProxy, Cloudflare — log request URIs by default. The token appears in plaintext in access logs, which are then shipped to centralized logging platforms (Elasticsearch, Splunk, Loki). Those logs are accessible by ops teams, developers, and sometimes third-party contractors. An attacker who compromises a logging system gets a searchable treasure trove. TLS encrypts the URL in transit, but server-side logging happens after decryption. The token is recorded forever.
4. Browser Extensions and Developer Tools
Extensions with tab/window access can read window.location.href, which includes query strings. A supply chain attack on a popular ad blocker or password manager could silently harvest API keys. Even without malice, some extensions sync browsing data across devices — your token could end up in a cloud profile without any attacker actively doing anything.
5. Human Error
The operator sees the key in the address bar, copies the full URL, and pastes it into a Slack thread or a JIRA ticket for debugging. Screenshot the browser? Token in the image. Share the screen during a support call? Instant leakage. It's not if, it's when.
Exploitation in Practice
Let's walk through a realistic chain with a sequence diagram.
In this scenario, an attacker who has access to docs.example.com logs — legitimate or breached — now holds the operator API key. From there, they authenticate to the nebula-mesh management API, reconfigure the mesh, intercept traffic, or pivot to other systems. The attack doesn't require the operator to click a link; any subresource load (analytics, CDN fonts) can leak the token via Referer. It's ambient leakage.
Detecting This Leak in Your Own Systems
If you're shipping admin dashboards, you need to catch this before production. Here's a battle-tested approach.
1. Intercept All Redirect Responses
Set up mitmproxy or Burp Suite and watch the Location header of every 3xx response. Look for query parameters containing base64-ish strings, long random alphanumeric tokens, or params named key, token, secret. A quick mitmproxy script:
from mitmproxy import http
def response(flow: http.HTTPFlow):
if 300 <= flow.response.status_code < 400:
location = flow.response.headers.get("Location", "")
if "token=" in location or "key=" in location:
print(f"[!] Suspicious redirect: {location}")Run this in your test environment, and flag everything.
2. Scan Your Access Logs Retroactively
If you already run nebula-mesh (or similar), grep your proxy logs for the leak pattern. A quick bash one-liner:
grep -iE 'new_key=|api_key=|token=' /var/log/nginx/access.logIf matches turn up, those tokens are burned. Rotate them immediately.
3. Lint Frontend Code for URL Manipulation
Sometimes the redirect is done client-side: window.location.href = /dashboard?key=${token}. Add ESLint rules or simple grep to catch location.href or location.assign` being set with a token variable. This is often missed because the server-side code looks clean.
4. Referer Monitoring During QA
In your staging environment, set all external links and asset references to point to a controlled domain that logs Referer headers. If tokens start appearing, you'll know within minutes.
5. Static Analysis Rules (Semgrep/CodeQL)
For Go codebases:
rules:
- id: secret-in-redirect-url
patterns:
- pattern: http.Redirect(w, r, url.QueryEscape(...), http.StatusFound)
- metavariable-regex:
metavariable: $URL
regex: (?i)token|key|secret
message: "Potential secret in redirect URL"
languages: [go]
severity: ERRORThis catches the exact pattern that caused the nebula-mesh bug. Tune the regex for your own naming conventions.
The Fix: More Than a Code Change
The immediate patch is simple: stop putting the token in the URL. But the right design matters.
Immediate Mitigation for nebula-mesh Users
Until an official patch is available:
- Rotate all operator API keys now. Assume they are compromised.
- Check your proxy/load balancer logs for past accesses containing
new_key=. Any found tokens are gone. - Audit browser history on all machines that accessed the UI and purge the relevant entries.
- Set `Referrer-Policy: no-referrer` on the UI domain as an emergency compartmentalization step.
Architectural Fix: Never Put Secrets in URLs
The correct pattern is straightforward:
- Generate the token server-side.
- Store its hash in the database.
- Return the raw token once in the response body of the creation POST request — as JSON or an HTML element. Never redirect to a page that includes the secret.
- After displaying it (modal, flash message), clear it from the DOM and never expose it again.
In Go, that looks like:
func handleOperatorCreateAPIKey(w http.ResponseWriter, r *http.Request) {
token := generateToken()
storeTokenHash(token)
// Instead of redirect:
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"api_key": token})
}If a redirect is absolutely required for UX, use a double-submit pattern: after creation, store the raw token temporarily in a server-side session (or an encrypted, httpOnly cookie) and redirect with a nonce. The UI then fetches the token via an API call using that session, displays it, and the session is destroyed. Complex, but safe.
What about the URL fragment (#)? Don't. The fragment isn't sent in HTTP requests, but it's still visible in history and accessible to JavaScript. It's a half-measure that doesn't address the full attack surface.
Broader Lessons for API Key Lifecycle
This isn't just a nebula-mesh problem. The pattern of secrets in URLs appears constantly — in CI/CD pipelines, internal tooling, and even commercial SaaS products. I've stumbled on variations of this in bug bounty programs where tokens were leaked via redirect parameters and then used to access internal APIs. The lesson: key creation is the most dangerous moment in the token lifecycle, and UX must be designed with security first.
The "One-Time Token" Anti-Pattern
Some developers think, "I'll put the token in the URL, but the user sees it only once." That's wishful thinking. The browser's history, the server logs, the CDN caches — they don't forget. Once it's in a URL, it's permanent. Treat any token that hits the address bar as compromised.
Recommendations for Secure Key Delivery
When you build a system that generates secrets:
- Return the token only in the response body of the creation request. Keep it out of URLs entirely.
- Use short-lived JWT or opaque tokens and enforce rotation. A leaked token with a 15-minute expiry is far less dangerous than a long-lived bearer token.
- Hash tokens immediately with a strong function (HMAC-SHA256) and store only the hash. Never store the raw token in logs or databases.
- Educate frontend developers about the dangers of
location.hrefandwindow.openwith token data. Provide safe wrapper functions. - Monitor your own infrastructure: scan your centralized logs for token-like strings in URL parameters and alert on them. A simple cron job grep can catch leaks early.
- Assume tokens will leak and design for rotation, revocation, and minimal blast radius. This nebula-mesh bug is a perfect case study: the token was long-lived and gave full operator access.
And one more thing: if you're using a service mesh like nebula-mesh, the API key is often the single point of failure for your entire network segmentation. Treat it with the same paranoia you'd give a root SSH private key.
The nebula-mesh operator API key leak is a stark reminder that web applications are a leaky abstraction. The browser was never designed to keep secrets — its whole architecture is about caching, logging, and sharing state. We have to stop treating the address bar like a temporary clipboard. It's not. It's a broadcast medium.
If you're running nebula-mesh, rotate your keys now. If you're building any system that handles authentication tokens, audit your redirects and response flows. This bug isn't exotic; it's a symptom of a design philosophy that prioritizes convenience over security. In distributed systems like mesh networks, that trade-off can cost you everything.
Let's learn from it. And let's finally stop putting secrets in URLs — for good.
Related posts
- Security
How I got free cinema credit by ordering -2 popcorns
A missing input validation on M-Tix Cinema XXI's food ordering API let me increase my account balance by submitting negative quantities. No tools needed — just a browser.
May 19, 2026 · 6 min - Security
How I analyze API security headers in 30 seconds
A quick checklist for reading HTTP response headers and spotting security misconfigurations before you even look at the response body.
May 18, 2026 · 7 min - Security
Common auth mistakes I find when reverse-engineering APIs
After years of poking at APIs that weren't meant to be poked at, these are the auth patterns that break most often — and why.
May 18, 2026 · 9 min