Outray cli is vulnerable to race conditions in tunnels creation
A TOCTOU race condition vulnerability allows a user to exceed the set number of active tunnels in their subscription plan.
Affected conponent: apps/web/src/routes/api/tunnel/register.ts
/tunnel/register endpoint code-:```ts
// Check if tunnel already exists in database
const [existingTunnel] = await db
.select()
.from(tunnels)
.where(eq(tunnels.url, tunnelUrl));
const isReconnection = !!existingTunnel;
console.log(
[TUNNEL LIMIT CHECK] Org: ${organizationId}, Tunnel: ${tunnelId},
);
console.log(
[TUNNEL LIMIT CHECK] Is Reconnection: ${isReconnection},
);
console.log(
[TUNNEL LIMIT CHECK] Plan: ${currentPlan}, Limit: ${tunnelLimit},
);
// Check limits only for NEW tunnels (not reconnections)
if (!isReconnection) {
// Count active tunnels from Redis SET
const activeCount = await redis.scard(setKey);
console.log(
[TUNNEL LIMIT CHECK] Active count in Redis: ${activeCount},
);
// The current tunnel is NOT yet in the online_tunnels set (added after successful registration)
// So we check if activeCount >= limit (not >)
if (activeCount >= tunnelLimit) {
console.log(
[TUNNEL LIMIT CHECK] REJECTED - ${activeCount} >= ${tunnelLimit},
);
return json(
{
error: Tunnel limit reached. The ${currentPlan} plan allows ${tunnelLimit} active tunnel${tunnelLimit > 1 ? "s" : ""}.,
},
{ status: 403 },
);
}
console.log(
[TUNNEL LIMIT CHECK] ALLOWED - ${activeCount} < ${tunnelLimit},
);
} else {
console.log([TUNNEL LIMIT CHECK] SKIPPED - Reconnection detected);
}
if (existingTunnel) {
// Tunnel with this URL already exists, update lastSeenAt
await db
.update(tunnels)
.set({ lastSeenAt: new Date() })
.where(eq(tunnels.id, existingTunnel.id));
return json({
success: true,
tunnelId: existingTunnel.id,
});
}
// Create new tunnel record
const tunnelRecord = {
id: randomUUID(),
url: tunnelUrl,
userId,
organizationId,
name: name || null,
protocol,
remotePort: remotePort || null,
lastSeenAt: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
};
await db.insert(tunnels).values(tunnelRecord);
return json({ success: true, tunnelId: tunnelRecord.id });
} catch (error) {
console.error("Tunnel registration error:", error);
return json({ error: "Internal server error" }, { status: 500 });
}
```
```ts
// Check if tunnel already exists in database
const [existingTunnel] = await db
.select()
.from(tunnels)
.where(eq(tunnels.url, tunnelUrl));
const isReconnection = !!existingTunnel;
```
```ts
// Check limits only for NEW tunnels (not reconnections)
if (!isReconnection) {
// Count active tunnels from Redis SET
const activeCount = await redis.scard(setKey);
console.log(
[TUNNEL LIMIT CHECK] Active count in Redis: ${activeCount},
);
```
```ts
// Check limits only for NEW tunnels (not reconnections)
if (!isReconnection) {
// Count active tunnels from Redis SET
const activeCount = await redis.scard(setKey);
console.log(
[TUNNEL LIMIT CHECK] Active count in Redis: ${activeCount},
);
```
```ts
if (activeCount >= tunnelLimit) {
console.log(
[TUNNEL LIMIT CHECK] REJECTED - ${activeCount} >= ${tunnelLimit},
);
return json(
{
error: Tunnel limit reached. The ${currentPlan} plan allows ${tunnelLimit} active tunnel${tunnelLimit > 1 ? "s" : ""}.,
},
{ status: 403 },
);
```
Insert Statement without locking transactions from other request```ts
await db.insert(tunnels).values(tunnelRecord);
```
wshandler in /outray/outray-main/apps/tunnel/src/core/WSHandler.ts from the command line app. A request can work on a non updated row because thThis issue can be reached over the network, attack complexity is high, an attacker needs no privileges on the target. No user interaction is required. Rated impact: confidentiality none, integrity low, availability none.
The score comes from this vector: CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
CVE-2026-22820 is classified as CWE-367: Time-of-check Time-of-use Race Condition. The state checked and the state acted upon can differ, because it may change between the two steps.
CVE-2026-22820 is recorded against 1 package.
Published on 13 January 2026 and last revised on 17 June 2026. A public exploit is known to exist, which raises the urgency of patching considerably. A vendor advisory or fix has been published. Record sourced from OSV.
github.com (Web)
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Package)
outray has other advisories on record. If you are patching this one, these are worth checking on the same host:
These advisories are the same class of weakness (CWE-367: Time-of-check Time-of-use Race Condition) in other software:
Details
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| outray | — | — |
References
Similar Threats
Exploit Protection
CVE-2026-22820 carries CVSS 3.7 Low rating and a public exploit already exists. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.
Check My Site For CVE-2026-22820 →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.