Introduction
The everclaw-key-api/server.js file is the backbone of the Everclaw bootstrap service — it handles wallet funding, proof-of-work challenges, claim code generation, cross-post verification, and GDPR-compliant data deletion. But a critical inconsistency lurked in the code: while the /api/stats endpoint at line 154 properly validated the x-admin-secret header, four other endpoints that performed equally sensitive (arguably more sensitive) operations had zero authentication.
Specifically, the route handlers for /bootstrap/challenge (line 182), /bootstrap (line 209), /verify-xpost (line 283), and /forget (line 311) all accepted requests from anyone who could reach the server. An attacker didn't need a token, a session, or any credential — just a well-formed HTTP request.
This is the kind of vulnerability that doesn't come from a single bad line of code. It comes from an inconsistent security boundary: some endpoints were protected, others were not, and nothing enforced uniformity.
The Vulnerability Explained
The Inconsistent Auth Boundary
In the Everclaw Key API, the /api/stats route already had a proper authentication guard:
// Existing protection on /api/stats (line 154-157)
if (!SECRET || req.headers["x-admin-secret"] !== SECRET) {
return res.status(401).json({ error: "unauthorized" });
}
This pattern checks two things: (1) that the SECRET environment variable is actually configured, and (2) that the incoming request's x-admin-secret header matches it. If either condition fails, the request is rejected with a 401.
But the bootstrap endpoints — which handle wallet operations and fund distribution — had no such check. Here's what the /bootstrap/challenge handler looked like before the fix:
// POST /bootstrap/challenge (line 182) — BEFORE fix
app.post("/bootstrap/challenge", async (req, res) => {
if (!redis) return res.status(503).json({ error: "Redis not configured" });
const { fingerprint, timestamp } = req.body;
// ... generates PoW challenge and returns it
});
The same pattern repeated for /bootstrap, /verify-xpost, and /forget — each jumped straight into business logic without verifying the caller's identity.
The Attack Scenario
This is a 2-step exploitation chain specific to the Everclaw bootstrap flow:
-
Step 1 — Obtain a challenge: The attacker sends a
POSTrequest to/bootstrap/challengewith a fabricatedfingerprintandtimestamp. Because there's no auth check, the server generates a proof-of-work challenge and nonce, returning them in the response. -
Step 2 — Claim bootstrap funds: The attacker solves the PoW challenge locally (the difficulty is designed to be solvable by legitimate clients, so an attacker can solve it too), then sends a
POSTto/bootstrapwith thewallet,fingerprint,challengeNonce,solution, andtimestamp. The server validates the PoW solution, then distributes bootstrap funds and claim codes to the attacker's wallet.
The attacker can repeat this with different fingerprints to drain the bootstrap fund. They can also call /verify-xpost to validate fraudulent claim codes, or hit /forget to trigger GDPR deletion of other users' data by supplying their wallet address and fingerprint hash.
Why This Is High Severity
- Financial impact: The
/bootstrapendpoint distributes real funds (likely tokens or cryptocurrency) to wallets. Unauthenticated access means free money for attackers. - Data integrity: The
/forgetendpoint deletes user records from Redis. An attacker can trigger data loss for arbitrary users. - Claim code abuse:
/verify-xpostvalidates claim codes, which could be used to game referral or verification systems. - No rate-limiting substitute: While some endpoints check Redis for prior usage by fingerprint, fingerprints are client-supplied and trivially spoofed.
The Fix
The fix is surgically precise: it adds the exact same x-admin-secret validation pattern already used on /api/stats to all four unprotected endpoints. Here's the before-and-after for each:
/bootstrap/challenge (line 182)
Before:
app.post("/bootstrap/challenge", async (req, res) => {
if (!redis) return res.status(503).json({ error: "Redis not configured" });
// ... business logic
});
After:
app.post("/bootstrap/challenge", async (req, res) => {
if (!SECRET || req.headers["x-admin-secret"] !== SECRET) {
return res.status(401).json({ error: "unauthorized" });
}
if (!redis) return res.status(503).json({ error: "Redis not configured" });
// ... business logic
});
/bootstrap (line 209)
The same guard is added before the Redis check and before the destructuring of { wallet, fingerprint, challengeNonce, solution, timestamp } from req.body.
/verify-xpost (line 283)
The guard is inserted before the { wallet, claimCode } extraction, ensuring no claim code validation occurs without authentication.
/forget (line 311)
The GDPR deletion endpoint now requires authentication before it will process { wallet, fingerprintHash } from the request body.
Why This Specific Pattern Works
The check !SECRET || req.headers["x-admin-secret"] !== SECRET is a fail-closed design:
- If
SECRETis not configured (undefined, null, or empty string), the check fails and returns 401. This prevents accidental exposure in misconfigured deployments. - If the header is missing or doesn't match, the check fails and returns 401.
- Only when
SECRETis truthy and the header matches does the request proceed.
The early return ensures no business logic executes for unauthorized requests — the auth check is a gate, not a side-effect.
Prevention & Best Practices
1. Use Authentication Middleware, Not Inline Checks
The root cause here was copy-paste inconsistency. A better pattern is to extract the auth logic into Express middleware:
function requireAuth(req, res, next) {
if (!SECRET || req.headers["x-admin-secret"] !== SECRET) {
return res.status(401).json({ error: "unauthorized" });
}
next();
}
app.post("/bootstrap/challenge", requireAuth, async (req, res) => { /* ... */ });
app.post("/bootstrap", requireAuth, async (req, res) => { /* ... */ });
app.post("/verify-xpost", requireAuth, async (req, res) => { /* ... */ });
app.delete("/forget", requireAuth, async (req, res) => { /* ... */ });
This makes it impossible to "forget" authentication on a new endpoint — you either add the middleware or you don't, and code review catches the absence immediately.
2. Default-Deny Architecture
Instead of protecting individual routes, consider applying authentication globally and explicitly exempting public routes:
app.use(requireAuth); // All routes require auth by default
app.get("/health", skipAuth, healthHandler); // Explicitly public
3. Write Regression Tests for Auth Boundaries
The PR includes an excellent regression test pattern that tests all protected endpoints with missing, invalid, and empty auth headers:
const endpoints = [
{ method: "post", path: "/bootstrap/challenge" },
{ method: "post", path: "/bootstrap" },
{ method: "post", path: "/verify-xpost" },
{ method: "post", path: "/forget" },
];
test.each(endpoints)("rejects unauthenticated request to $method $path", async (endpoint) => {
// Test with missing, invalid, and empty auth headers
});
This test will catch any future regression where auth is accidentally removed.
4. Static Analysis Rules
Tools like Semgrep can detect Express route handlers that lack authentication patterns. The detect-child-process family of rules can be extended with custom rules that flag route handlers missing your organization's auth middleware.
Key Takeaways
- The
/bootstrap/challenge,/bootstrap,/verify-xpost, and/forgetendpoints ineverclaw-key-api/server.jswere completely unprotected while the nearby/api/statsendpoint had properx-admin-secretvalidation — a classic inconsistent security boundary. - Inline auth checks are fragile: when the same
if (!SECRET || ...)pattern must be manually copied to every handler, omissions are inevitable. Extract it into middleware. - Fail-closed is essential: the
!SECRETcheck ensures that if the environment variable is misconfigured, the endpoint locks down rather than opening up. - Financial endpoints demand defense-in-depth: the PoW challenge was not sufficient as a standalone access control — it's a rate-limiting mechanism, not an authentication mechanism.
- Regression tests for auth boundaries should be mandatory: the test in this PR would have caught this vulnerability before deployment if it had existed from the start.
How Orbis AppSec Detected This
- Source: Unauthenticated HTTP POST/DELETE requests reaching route handlers in
everclaw-key-api/server.js - Sink: Business logic in
/bootstrap/challenge(line 182),/bootstrap(line 209),/verify-xpost(line 283), and/forget(line 311) that processes wallet operations, fund distribution, claim code validation, and data deletion - Missing control: No
x-admin-secretheader validation on these four endpoints, despite the same validation being present on/api/statsat line 154 - CWE: CWE-306 — Missing Authentication for Critical Function
- Fix: Added
if (!SECRET || req.headers["x-admin-secret"] !== SECRET) return res.status(401)guard at the top of all four unprotected route handlers
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
This vulnerability is a textbook example of how inconsistent security boundaries create exploitable gaps. The Everclaw Key API already had the right authentication pattern — it just wasn't applied everywhere it needed to be. Four endpoints that handle wallet funding, claim codes, cross-post verification, and GDPR data deletion were left wide open while a less sensitive stats endpoint was properly protected.
The fix is straightforward: the same x-admin-secret header check now gates all four endpoints. But the deeper lesson is architectural — authentication should be a default, not an opt-in. Use middleware, write boundary tests, and let static analysis tools catch the gaps your eyes miss.