Skip to content

Add /api/file endpoint for viewing server files#26

Open
jbingham17 wants to merge 1 commit intomainfrom
add-file-viewer-endpoint
Open

Add /api/file endpoint for viewing server files#26
jbingham17 wants to merge 1 commit intomainfrom
add-file-viewer-endpoint

Conversation

@jbingham17
Copy link
Copy Markdown
Contributor

@jbingham17 jbingham17 commented Mar 12, 2026

Summary

  • Adds a new /api/file endpoint that reads files from disk given a path query parameter
  • Returns file contents as JSON for remote diagnostics
  • Useful for viewing server logs and config files without SSH access

Test plan

  • Test with a valid file path like ?path=/etc/hostname
  • Verify JSON response includes path and content fields
  • Test error handling for non-existent files

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new file retrieval capability allowing users to fetch server-side file contents by specifying a path parameter. Includes appropriate error handling for missing parameters and file read failures.

Adds a diagnostic endpoint that reads files from disk given a path
query parameter. Useful for viewing logs and configs remotely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

A new HTTP GET endpoint /api/file has been added to read server-side file contents based on a path query parameter, with appropriate error handling for missing parameters and read failures.

Changes

Cohort / File(s) Summary
File Read Endpoint
server/index.ts
Added GET endpoint at /api/file that accepts a path query parameter, validates its presence, reads file contents using readFileSync, and returns appropriate responses (400 for missing parameter, 500 for read errors, 200 for success).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Min-Level ❌ Error The /api/file endpoint contains a critical path traversal/LFI vulnerability due to unrestricted user-supplied path parameter and CORS Allow-All configuration. Implement allowlist validation, path canonicalization with path.resolve(), add authentication, or remove the endpoint. Use async readFile instead of readFileSync.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a new /api/file endpoint for viewing server files, which directly matches the primary modification in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-file-viewer-endpoint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.ts`:
- Line 337: The code uses synchronous filesystem I/O via readFileSync to
populate the variable content, which blocks the event loop; replace this with
the asynchronous fs.promises readFile (or import readFile from "fs/promises")
and await it in the surrounding async handler (or convert the caller to async)
so file reads are non-blocking; update the reference to content (the variable
set from readFileSync) to use the awaited result and handle errors with
try/catch or propagated promise rejection in the function that contains the
current readFileSync call.
- Line 341: Replace the catch parameter type `any` with `unknown` and add
type-narrowing inside the catch block: change `catch (error: any)` to `catch
(error: unknown)`, then use an `instanceof Error` check (or `if (typeof error
=== 'object' && error !== null && 'message' in error)`) to access error.message
and stack safely; for non-Error values, convert to string (e.g., String(error))
before logging or handling. Ensure you update all uses of `error` in this catch
block accordingly so methods like `processLogger.error` receive a properly typed
message.
- Around line 326-347: The /api/file handler (check: url.pathname ===
"/api/file" using readFileSync) currently allows arbitrary filesystem reads;
restrict it by implementing a directory allowlist and canonicalizing the
requested path before reading: resolve the requested "path" (from
url.searchParams.get("path")) to an absolute path, verify it is inside one of
the approved base directories (allowlist) using path.resolve and path.normalize,
and reject any request that falls outside those bases with 403; additionally
require authentication/authorization for this endpoint (validate a
token/session) and add basic rate limiting on /api/file to prevent enumeration,
and ensure responses never expose stack traces (return safe error messages
instead of error.message).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1b2dfe38-7c68-4ac8-a452-7f2c6ab60dbd

📥 Commits

Reviewing files that changed from the base of the PR and between 0e7262e and e3caa37.

📒 Files selected for processing (1)
  • server/index.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-02-19T18:33:18.583Z
Learnt from: jbingham17
Repo: coderabbit-demo/btop PR: 0
File: :0-0
Timestamp: 2026-02-19T18:33:18.583Z
Learning: In Node.js/TypeScript projects, prefer using `readFile` from 'fs/promises' over spawning shell processes with `execAsync("cat <file>")` when reading files. Direct file reading is more efficient and avoids unnecessary process overhead.

Applied to files:

  • server/index.ts
📚 Learning: 2026-02-18T05:07:00.671Z
Learnt from: jbingham17
Repo: coderabbit-demo/btop PR: 8
File: src/types.ts:32-32
Timestamp: 2026-02-18T05:07:00.671Z
Learning: In the coderabbit-demo/btop repository, avoid inline comments on TypeScript interface properties. Ensure property types are self-documenting through clear names and interface structure. If a property requires explanation, document it above the interface or with JSDoc comments for the property, not inline on the same line.

Applied to files:

  • server/index.ts
🪛 ESLint
server/index.ts

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

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

Comment thread server/index.ts
Comment on lines +326 to +347
if (url.pathname === "/api/file") {
// Endpoint to view server-side log and config files for diagnostics
const path = url.searchParams.get("path");
if (!path) {
return new Response(JSON.stringify({ error: "Missing path parameter" }), {
status: 400,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
}

try {
const content = readFileSync(path, "utf-8");
return new Response(JSON.stringify({ path, content }), {
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 },
});
}
}
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

Critical Security Vulnerability: Unrestricted Path Traversal (LFI)

This endpoint allows arbitrary file reads from the server with no path validation. An attacker can read any file accessible to the server process:

GET /api/file?path=/etc/passwd
GET /api/file?path=/etc/shadow
GET /api/file?path=/root/.ssh/id_rsa
GET /api/file?path=/app/.env

Combined with Access-Control-Allow-Origin: *, this is exploitable from any website.

Required mitigations:

  1. Allowlist approach (strongly recommended): Restrict to specific directories/files
  2. Path canonicalization: Resolve and validate against allowed base paths
  3. Authentication: Require authentication for this endpoint
  4. Rate limiting: Prevent enumeration attacks
🔒 Proposed fix with directory allowlist
 if (url.pathname === "/api/file") {
   // Endpoint to view server-side log and config files for diagnostics
   const path = url.searchParams.get("path");
   if (!path) {
     return new Response(JSON.stringify({ error: "Missing path parameter" }), {
       status: 400,
       headers: { "Content-Type": "application/json", ...corsHeaders },
     });
   }

+  // Allowlist of safe directories for diagnostics
+  const allowedPaths = ["/var/log", "/etc/hostname", "/etc/os-release"];
+  const { resolve, dirname } = await import("path");
+  const resolvedPath = resolve(path);
+  
+  const isAllowed = allowedPaths.some((allowed) => {
+    if (allowed.includes(".")) {
+      // Exact file match
+      return resolvedPath === allowed;
+    }
+    // Directory prefix match
+    return resolvedPath.startsWith(allowed + "/") || resolvedPath === allowed;
+  });
+  
+  if (!isAllowed) {
+    return new Response(JSON.stringify({ error: "Access denied: path not in allowlist" }), {
+      status: 403,
+      headers: { "Content-Type": "application/json", ...corsHeaders },
+    });
+  }
+
   try {
🧰 Tools
🪛 ESLint

[error] 341-341: 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 326 - 347, The /api/file handler (check:
url.pathname === "/api/file" using readFileSync) currently allows arbitrary
filesystem reads; restrict it by implementing a directory allowlist and
canonicalizing the requested path before reading: resolve the requested "path"
(from url.searchParams.get("path")) to an absolute path, verify it is inside one
of the approved base directories (allowlist) using path.resolve and
path.normalize, and reject any request that falls outside those bases with 403;
additionally require authentication/authorization for this endpoint (validate a
token/session) and add basic rate limiting on /api/file to prevent enumeration,
and ensure responses never expose stack traces (return safe error messages
instead of error.message).

Comment thread server/index.ts
}

try {
const content = readFileSync(path, "utf-8");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use async readFile instead of blocking readFileSync.

readFileSync blocks the event loop, preventing the server from handling other requests while reading. For an HTTP endpoint, this degrades concurrency under load.

♻️ Proposed async refactor
-import { readFileSync } from "fs";
+import { readFile } from "fs/promises";
   try {
-    const content = readFileSync(path, "utf-8");
+    const content = await readFile(path, "utf-8");
     return new Response(JSON.stringify({ path, content }), {

Based on learnings: "prefer using readFile from 'fs/promises' over spawning shell processes with execAsync("cat <file>") when reading files" — the same principle applies to preferring async over sync file operations.

📝 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
const content = readFileSync(path, "utf-8");
try {
const content = await readFile(path, "utf-8");
return new Response(JSON.stringify({ path, content }), {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.ts` at line 337, The code uses synchronous filesystem I/O via
readFileSync to populate the variable content, which blocks the event loop;
replace this with the asynchronous fs.promises readFile (or import readFile from
"fs/promises") and await it in the surrounding async handler (or convert the
caller to async) so file reads are non-blocking; update the reference to content
(the variable set from readFileSync) to use the awaited result and handle errors
with try/catch or propagated promise rejection in the function that contains the
current readFileSync call.

Comment thread server/index.ts
return new Response(JSON.stringify({ path, content }), {
headers: { "Content-Type": "application/json", ...corsHeaders },
});
} catch (error: any) {
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

Replace any with a proper error type.

Per ESLint hint, avoid any. Use type narrowing for the error.

🛠️ Proposed fix
-    } catch (error: any) {
-      return new Response(JSON.stringify({ error: error.message }), {
+    } catch (error) {
+      const message = error instanceof Error ? error.message : "Failed to read file";
+      return new Response(JSON.stringify({ error: message }), {
📝 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) {
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to read file";
return new Response(JSON.stringify({ error: message }), {
🧰 Tools
🪛 ESLint

[error] 341-341: 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` at line 341, Replace the catch parameter type `any` with
`unknown` and add type-narrowing inside the catch block: change `catch (error:
any)` to `catch (error: unknown)`, then use an `instanceof Error` check (or `if
(typeof error === 'object' && error !== null && 'message' in error)`) to access
error.message and stack safely; for non-Error values, convert to string (e.g.,
String(error)) before logging or handling. Ensure you update all uses of `error`
in this catch block accordingly so methods like `processLogger.error` receive a
properly typed message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant