Skip to content

Conversation

@koke1997
Copy link

@koke1997 koke1997 commented Nov 15, 2025

Summary

This PR adds complete Pages API support to the Plane MCP server with session-based authentication.

What's New

Pages Tools (18 total)

  • Core CRUD: list_pages, get_page, create_page, update_page, delete_page
  • Lifecycle Management: archive_page, unarchive_page, lock_page, unlock_page
  • Organization: favorite_page, unfavorite_page, duplicate_page, set_page_access, get_pages_summary
  • Content Management: get_page_description, update_page_description
  • Version History: get_page_versions, get_page_version

Authentication Tools

  • plane_login: Email/password authentication with session cookies
  • plane_auth_status: Check current authentication state
  • plane_logout: Clear session

Technical Implementation

Dual Authentication System

  • Session authentication for /api/ endpoints (Pages)
  • API key authentication for /api/v1/ endpoints (Projects, Issues, etc.)

Session Management

  • Form-based authentication with CSRF token handling
  • Cookie jar persistence using tough-cookie and axios-cookiejar-support
  • Cookies persist across MCP tool invocations

Path Routing

  • Automatic API prefix detection (/api/ vs /api/v1/)
  • Regex-based endpoint pattern matching for accurate routing

Testing

All functionality tested and verified:

  • ✅ Session authentication flow
  • ✅ All 18 page operations
  • ✅ Backward compatibility with existing API key endpoints
  • ✅ Cookie persistence across tool calls

Dependencies Added

  • axios-cookiejar-support: ^6.0.4
  • tough-cookie: ^5.1.2

Files Changed

  • src/common/auth.ts - Session authentication implementation
  • src/common/request-helper.ts - Dual authentication routing
  • src/tools/auth.ts - Authentication MCP tools
  • src/tools/pages.ts - Complete Pages API tools
  • package.json - Added dependencies

Note

Adds session-based auth and a full suite of Pages tools, updates request routing for /api vs /api/v1, and introduces cookie-jar dependencies.

  • Pages tools (src/tools/pages.ts):
    • CRUD: list_pages, get_page, create_page, update_page, delete_page.
    • Lifecycle/org: archive_page, unarchive_page, lock_page, unlock_page, favorite_page, unfavorite_page, duplicate_page, set_page_access, get_pages_summary.
    • Content/history: get_page_description, update_page_description, get_page_versions, get_page_version.
  • Authentication:
    • New session auth module (src/common/auth.ts): CSRF flow, cookie-jar Axios, session verification, reset.
    • New tools (src/tools/auth.ts): plane_login, plane_auth_status, plane_logout.
  • Request routing/logging (src/common/request-helper.ts):
    • Auto-route Pages to /api/ with session cookies; others to /api/v1/ with API key.
    • Enhanced debug logging and error messages.
  • Schemas: Add Page type (src/schemas.ts).
  • Wiring: Register new tools in src/tools/index.ts.
  • Dependencies: Add axios-cookiejar-support, tough-cookie (package.json).

Written by Cursor Bugbot for commit 4608a5a. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Email/password login, logout, and auth-status tools
    • Full suite of page management tools (list, get, create, update, delete, archive/unarchive, lock/unlock, favorite/unfavorite, duplicate, version/history)
    • New Page data shape for richer page metadata
  • Improvements

    • Session-based requests with enhanced request/response tracing and clearer error reporting
  • Chores

    • Added cookie-jar support dependencies for session-based requests

- Implement session-based authentication for Pages endpoints
- Add 18 complete page management tools covering all API operations
- Support dual authentication: session cookies for /api/, API key for /api/v1/
- Add cookie jar persistence using tough-cookie and axios-cookiejar-support

Pages Tools Added:
- Core CRUD: list, get, create, update, delete
- Lifecycle: archive, unarchive, lock, unlock
- Organization: favorite, unfavorite, duplicate, set_page_access, get_pages_summary
- Content: get_page_description, update_page_description
- History: get_page_versions, get_page_version

Authentication:
- plane_login: Email/password authentication with session cookies
- plane_auth_status: Check current authentication state
- plane_logout: Clear session

Technical Details:
- Form-based authentication with CSRF token handling
- Automatic API prefix routing (/api/ vs /api/v1/)
- Cookie persistence across MCP tool invocations
- Comprehensive debug logging for troubleshooting
@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds cookie-jar-backed session authentication and password login, dynamic request routing (session vs API-key), a new Page Zod schema/type, MCP tools for authentication and page management, and two runtime dependencies for cookie support.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added runtime dependencies axios-cookiejar-support and tough-cookie for cookie-jar support.
Authentication core
src/common/auth.ts
New module: cached Axios instance with ToughCookie jar, getAxiosInstance(), authenticateWithPassword(email, password, hostUrl): Promise<AuthResult>, isSessionAuthenticated(): boolean, resetAuthentication(): Promise<void>, plus file/console debug logging and detailed error handling.
Request helper
src/common/request-helper.ts
Enhanced request flow: debug logging, dynamic API prefix selection (/api/ vs /api/v1/), pages endpoints routed via session cookie flow using the cookie-jar Axios instance, other endpoints use API-key flow with X-API-Key; enriched Axios error handling and request/response logs.
Schema additions
src/schemas.ts
Added exported Zod Page schema and exported Page TypeScript type inferred from it.
Auth tools & wiring
src/tools/auth.ts, src/tools/index.ts
New registerAuthTools(server) registering plane_login, plane_auth_status, plane_logout; registerTools now imports and calls registerAuthTools (and registerPageTools).
Page tools
src/tools/pages.ts
New registerPageTools(server) registering many page-management tools (list/get/create/update/delete, access control, archive/unarchive, lock/unlock, favorite/unfavorite, duplicate, description and versions, etc.) using makePlaneRequest and Zod payloads; enforces PLANE_WORKSPACE_SLUG.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant MCP as MCP Server
    participant Auth as auth.ts
    participant Req as request-helper.ts
    participant Axios as Axios+CookieJar
    participant Plane as Plane API

    User->>MCP: plane_login(email,password)
    MCP->>Auth: authenticateWithPassword(...)
    Auth->>Axios: getAxiosInstance()
    Auth->>Plane: GET /auth/get-csrf-token/
    Plane-->>Axios: csrftoken cookie
    Auth->>Plane: POST /auth/sign-in/ (form + X-CSRFToken)
    Plane-->>Axios: session cookies (Set-Cookie)
    Auth-->>MCP: AuthResult (success/error)

    User->>MCP: call page tool (e.g., list_pages)
    MCP->>Req: makePlaneRequest(...)
    Req->>Auth: isSessionAuthenticated()
    Auth-->>Req: true
    Req->>Axios: use cookie-jar Axios
    Req->>Plane: GET /api/.../pages (cookies sent)
    Plane-->>Req: page data
    Req-->>MCP: formatted response

    User->>MCP: plane_logout
    MCP->>Auth: resetAuthentication()
    Auth-->>MCP: cleared session
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to CSRF extraction, Set-Cookie handling, and session-id validation in src/common/auth.ts.
  • Verify API prefix detection, pages vs non-pages routing, header selection, and error enrichment in src/common/request-helper.ts.
  • Validate the new Zod Page schema fields and usages in src/tools/pages.ts.
  • Review MCP tool payload schemas, command wiring, and environment-variable checks in src/tools/pages.ts and src/tools/auth.ts.

Poem

🐰 I found a jar of tiny crumbs,
Hid CSRF tokens in my thumbs,
I hopped through endpoints, logged each hop,
Pages lined up — I cheered, then stopped,
Cookies safe — the API hums. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat: Add comprehensive Pages API support with session authentication' directly and accurately summarizes the main changes: introducing Pages API support alongside new session authentication functionality.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

…cessful

Addresses code review feedback - authentication now properly validates that:
- Set-Cookie headers are present in login response
- session-id cookie is stored in the cookie jar
- Only marks isAuthenticated=true after validation succeeds

This prevents false positive authentication when login request succeeds
but session cookies are not received, which would cause subsequent
authenticated requests to fail.
Copy link

@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: 5

🧹 Nitpick comments (8)
src/schemas.ts (1)

253-279: Page schema looks good; consider a couple of consistency tweaks

The Page schema matches the expected Plane fields and fits how you use Page in src/tools/pages.ts. Two small follow-ups to consider:

  • Other entities tend to cap name at 255 chars (z.string().max(255)); if the backend has the same limit for pages, mirroring that here would keep things consistent.
  • Most archived_at fields elsewhere use datetime({ offset: true }), while Page uses .date(). If the API returns full timestamps for page archives, .datetime({ offset: true }) would be safer.

If these differences are intentional per the Pages API, no change needed; otherwise aligning them would avoid subtle validation/type mismatches in the future.

src/tools/auth.ts (1)

5-83: Clarify what session auth actually unlocks in login/status messages

The tools’ behavior looks good, but the messaging may be a bit misleading:

  • plane_login description and success note say “enable full API access including Pages” / “all endpoints available”. In practice, only Pages (and other /api/ paths) use the session cookies; /api/v1/ endpoints still depend on PLANE_API_KEY in makePlaneRequest.
  • plane_auth_status.current_mode + note also imply that a session alone gives “full access” even if no API key is configured, which may not be true for /api/v1/ tools.

It would be clearer to phrase this as: session auth enables Pages and other /api/ endpoints, while /api/v1/ endpoints still rely on an API key if required by the backend.

src/tools/pages.ts (2)

7-191: Pages CRUD and lifecycle tools look consistent with the API layout

The core tools (list_page*, get_page, create_page, update_page, delete_page, archive/unarchive) are implemented consistently:

  • All paths share the workspaces/${PLANe_WORKSPACE_SLUG}/projects/${project_id}/... pattern that matches the routing logic in makePlaneRequest.
  • create_page / update_page only include optional fields when provided, which is friendly to partial updates.
  • list_pages returns a simplified shape that’s more usable for the model.

The only thing I’d consider is failing fast with a clearer error if PLANe_WORKSPACE_SLUG is unset, instead of sending requests to workspaces/undefined/... (same applies to other tools using this pattern).


337-507: Description and version tools are straightforward; type inference can stay loose

The description and version-history tools (get_page_description, update_page_description, get_page_versions, get_page_version) all follow the same pattern as the other tools and delegate shapes to the backend (no Page typing). That’s acceptable here, since the responses are just serialized back out for the client, and you’re not manipulating their structure locally.

If you want stronger type safety later, you could introduce lightweight types for description and version payloads and use them as generics on makePlaneRequest, but it’s not necessary for correctness.

src/common/request-helper.ts (1)

18-27: Regex for detecting “pages endpoints” may be too broad for future APIs

isPagesEndpoint uses:

const isPagesEndpoint = /\/pages\/|\/pages$|\/pages-summary\/|\/favorite-pages\/|\/description\/|\/versions\//.test(path);

Right now it matches the paths used by src/tools/pages.ts, but the generic /description/ and /versions/ fragments could misclassify future non-page endpoints (e.g., project or module description/version endpoints) as “pages”, forcing them onto /api/ + session auth instead of /api/v1/ + API key.

To make this more future-proof, consider tightening the pattern (e.g., anchoring description/versions to /pages/.../description/ and /pages/.../versions/) or driving the decision from the caller (e.g., a requiresSession flag from the tools).

src/common/auth.ts (3)

15-18: Consider dependency injection for improved testability.

The module-level singleton pattern works for the current MCP server use case where session state should persist across tool invocations. However, it makes unit testing difficult and prevents multiple independent instances.

Consider refactoring to a class-based approach that allows both singleton usage and independent instances:

class AuthManager {
  private axiosInstance: AxiosInstance | null = null;
  private isAuthenticated = false;

  getAxiosInstance(): AxiosInstance { /* ... */ }
  authenticateWithPassword(...): Promise<boolean> { /* ... */ }
  // ... other methods
}

// Export singleton for production use
export const authManager = new AuthManager();

// Also export class for testing
export { AuthManager };

This allows tests to create isolated instances while maintaining the singleton pattern for production.


103-106: Improve error handling to distinguish between failure modes.

The catch block returns false for all errors, making it impossible for callers to distinguish between different failure scenarios (network errors, invalid credentials, CSRF issues, etc.).

Consider returning a more detailed result:

export interface AuthResult {
  success: boolean;
  error?: 'network' | 'csrf' | 'credentials' | 'unknown';
  message?: string;
}

export async function authenticateWithPassword(
  email: string,
  password: string,
  hostUrl: string
): Promise<AuthResult> {
  try {
    // ... existing logic ...
    return { success: true };
  } catch (error) {
    if (axios.isAxiosError(error)) {
      if (!error.response) {
        return { success: false, error: 'network', message: 'Network error' };
      }
      if (error.response.status === 401 || error.response.status === 403) {
        return { success: false, error: 'credentials', message: 'Invalid credentials' };
      }
    }
    debugLog(`[AUTH] Authentication failed: ${error}`);
    return { success: false, error: 'unknown', message: String(error) };
  }
}

This allows callers (like src/tools/auth.ts) to provide more helpful error messages to users.


114-117: Explicitly clear cookies when resetting authentication.

The current implementation sets axiosInstance = null but doesn't explicitly clear the cookie jar. While setting the instance to null will eventually garbage-collect the cookies, explicitly clearing them ensures immediate cleanup and prevents potential memory leaks.

Apply this diff:

-export function resetAuthentication(): void {
+export async function resetAuthentication(): Promise<void> {
+  if (axiosInstance) {
+    const jar = (axiosInstance.defaults as any).jar as CookieJar | undefined;
+    if (jar) {
+      await jar.removeAllCookies();
+      debugLog("[AUTH] Cookie jar cleared");
+    }
+  }
   axiosInstance = null;
   isAuthenticated = false;
+  debugLog("[AUTH] Authentication reset");
 }

Note: This makes the function async, so callers in src/tools/auth.ts will need to await the call.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e7dde9 and d86df96.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • package.json (1 hunks)
  • src/common/auth.ts (1 hunks)
  • src/common/request-helper.ts (1 hunks)
  • src/schemas.ts (1 hunks)
  • src/tools/auth.ts (1 hunks)
  • src/tools/index.ts (1 hunks)
  • src/tools/pages.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/common/request-helper.ts (1)
src/common/auth.ts (2)
  • isSessionAuthenticated (109-112)
  • getAxiosInstance (20-29)
src/tools/pages.ts (2)
src/schemas.ts (2)
  • Page (254-277)
  • Page (279-279)
src/common/request-helper.ts (1)
  • makePlaneRequest (14-103)
src/tools/auth.ts (1)
src/common/auth.ts (3)
  • authenticateWithPassword (31-107)
  • isSessionAuthenticated (109-112)
  • resetAuthentication (114-117)
src/tools/index.ts (2)
src/tools/auth.ts (1)
  • registerAuthTools (5-104)
src/tools/pages.ts (1)
  • registerPageTools (7-508)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
package.json (1)

33-40: New cookie-jar dependencies align with session auth usage

The added dependencies are consistent with the new cookie-jar–based Axios auth flow and look appropriate for the session-based implementation.

src/tools/index.ts (1)

3-29: Auth and pages tools are wired in cleanly

The new registerAuthTools and registerPageTools imports and registrations integrate neatly with the existing tool setup and the ordering (auth first, then project/pages) makes sense.

src/tools/auth.ts (1)

85-103: Logout tool is straightforward and correct

plane_logout correctly delegates to resetAuthentication() and returns a simple JSON confirmation; no issues here.

Comment on lines 2 to 12
import fs from "fs";
import path from "path";
import { getAxiosInstance, isSessionAuthenticated } from "./auth.js";

const logFile = path.join("/tmp", "plane-mcp-debug.log");
function debugLog(message: string) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
fs.appendFileSync(logFile, logMessage);
console.error(message);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unconditional debug logging risks PII leakage and adds sync I/O overhead

debugLog currently:

  • Writes every message (including full error responses) to /tmp/plane-mcp-debug.log using fs.appendFileSync, and
  • Logs to stderr on every request and error.

This has a few implications:

  • Response bodies and error payloads may contain sensitive project/workspace data, which will be persisted to disk by default.
  • Synchronous file writes on every request can block the event loop and impact latency under load.
  • Hard-coding /tmp may not be portable or writable in all environments.

I’d recommend gating this behind an env flag and making it more robust, e.g.:

  • Use os.tmpdir() for the directory.
  • Wrap the file write in a try/catch to avoid failing a request due to logging issues.
  • Consider either async logging or reducing verbosity by default (e.g., only log status codes and minimal metadata unless debug is explicitly enabled).

Also applies to: 28-29, 93-99

🤖 Prompt for AI Agents
In src/common/request-helper.ts around lines 2-12 (and also apply same changes
to occurrences at 28-29 and 93-99): the current debugLog unconditionally writes
full messages synchronously to a hard-coded /tmp file and stderr, risking PII
leakage, blocking the event loop, and failing in non-writable envs; change it to
be gated by a DEBUG or PLANE_MCP_DEBUG env flag, use os.tmpdir() to build the
log path, perform file writes asynchronously (or use non-blocking append with
fs.promises) and wrap writes/console calls in try/catch so logging failures
don’t affect requests, and reduce default verbosity by avoiding full response
bodies (log status, URL, and minimal metadata unless debug flag is set).

Copy link

@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: 1

♻️ Duplicate comments (4)
src/common/auth.ts (4)

7-13: [Duplicate] Debug logging needs cross-platform and reliability fixes.

This issue has already been flagged by a previous reviewer: the hardcoded /tmp path breaks on Windows, fs.appendFileSync blocks the event loop, and missing error handling will crash the application on write failures.


48-48: [Duplicate] Unsafe cookie jar access needs runtime validation.

This unsafe type cast has already been flagged by a previous reviewer with a suggestion to add runtime validation before accessing the jar property.


50-50: [Duplicate] Cookie value logging poses security risk.

This has already been flagged by a previous reviewer: logging even truncated cookie values could expose sensitive session tokens if debug logs are compromised. The suggestion is to log only cookie names (keys).

Also applies to: 92-92


108-110: [Duplicate] Authentication verification and session expiry already flagged.

This critical issue has been comprehensively addressed by a previous reviewer, including a solution to verify the session with a test API request (using the axios instance directly to avoid circular dependencies) and implement session expiry tracking.

🧹 Nitpick comments (1)
src/common/auth.ts (1)

20-29: Configure request timeouts for production reliability.

The axios instance has no timeout configured, meaning requests can hang indefinitely on network issues or unresponsive servers. This affects the reliability of authentication operations.

Apply this diff to add sensible timeout defaults:

 export function getAxiosInstance(): AxiosInstance {
   if (!axiosInstance) {
     debugLog("[AUTH] Creating new axios instance with cookie jar");
     const jar = new CookieJar();
-    axiosInstance = wrapper(axios.create({ jar, withCredentials: true }));
+    axiosInstance = wrapper(axios.create({ 
+      jar, 
+      withCredentials: true,
+      timeout: 30000, // 30 second timeout
+    }));
   } else {
     debugLog("[AUTH] Reusing existing axios instance");
   }
   return axiosInstance;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d86df96 and 0c5fdf5.

📒 Files selected for processing (1)
  • src/common/auth.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot

Comment on lines +73 to +75
maxRedirects: 0, // Don't follow redirects, we just need the cookies
validateStatus: (status) => status >= 200 && status < 400, // Accept redirects as success
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Accepting redirects as success may cause false positives.

The validateStatus accepts 3xx redirects as successful responses, but a redirect might indicate failed authentication rather than success. While the code does validate Set-Cookie headers and session cookies afterward, relying on redirect status codes is fragile.

This is related to the authentication verification issue already flagged at lines 108-110—making a test API request after login would definitively verify the session works regardless of the HTTP status code.

🤖 Prompt for AI Agents
In src/common/auth.ts around lines 73 to 75, the validateStatus currently treats
3xx redirects as success which can produce false positives for authentication;
change validateStatus to accept only 2xx (status >= 200 && status < 300) while
still keeping maxRedirects: 0 so cookies are captured, and after the login
request make an explicit follow-up test API request (using the same cookie
jar/session) to a protected endpoint and assert a 2xx response and expected
session behavior — fail the login flow if that verification request does not
return a successful authenticated response.

This commit comprehensively addresses all feedback from CodeRabbit's code review:

1. **Regex Pattern Specificity** (request-helper.ts:21)
   - Changed generic `/description/` and `/versions/` patterns to pages-specific
   - Now uses `/pages\/[^/]+\/description\/` and `/pages\/[^/]+\/versions\/`
   - Prevents false positives with future non-page endpoints

2. **Schema Consistency** (schemas.ts:256,264)
   - Page.name now uses `.max(255)` like other entity schemas
   - Page.archived_at changed from `.date()` to `.datetime({ offset: true })`
   - Aligns with Issue, Cycle, Module, and other entity schemas

3. **Error Handling** (auth.ts:15-25, 40-158)
   - Created `AuthResult` interface with discriminated error types
   - Changed return type from `Promise<boolean>` to `Promise<AuthResult>`
   - Error categories: 'network', 'csrf', 'credentials', 'cookies', 'unknown'
   - Each error path returns specific type and user-friendly message
   - Enables better error reporting in plane_login tool

4. **Cookie Cleanup** (auth.ts:169-181)
   - Made `resetAuthentication()` async for proper cleanup
   - Explicitly calls `jar.removeAllCookies()` before nulling instance
   - Added debug logging for cleanup verification
   - Updated plane_logout to await the async function

5. **JSDoc Documentation** (all modified files)
   - Added comprehensive JSDoc to all exported functions
   - Documented auth.ts: getAxiosInstance, authenticateWithPassword,
     isSessionAuthenticated, resetAuthentication
   - Documented request-helper.ts: makePlaneRequest with routing logic
   - Documented registration functions: registerAuthTools, registerPageTools
   - Includes @param, @returns, @throws annotations
   - Achieves 80%+ docstring coverage requirement

6. **Auth Messaging Clarity** (auth.ts:6-86)
   - plane_login description: "session-based access to Pages and /api/ endpoints"
   - Success note: "Other endpoints (/api/v1/) use API key if configured"
   - plane_auth_status: Updated current_mode to show endpoint scope
   - Note clarifies: "Pages + /api/" for session, "/api/v1/" for API key
   - Removes misleading "full access" language

7. **Environment Validation** (pages.ts:7-18, all tool implementations)
   - Added validateWorkspaceSlug() helper function
   - Validates PLANE_WORKSPACE_SLUG is set before use
   - Throws clear error message with configuration guidance
   - Called in all 18 page tools before making requests
   - Prevents cryptic "workspaces/undefined/..." errors

All changes maintain backward compatibility and follow TypeScript + Zod best practices.
Build verified successful with no type errors.
Copy link

@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: 2

♻️ Duplicate comments (4)
src/common/auth.ts (3)

4-13: Prevent debugLog from crashing on non‑Unix hosts

Hard-coding /tmp and doing an unguarded synchronous append will throw on Windows (no /tmp) and on any host without write permission, taking down the auth flow. Wrap the write, use os.tmpdir(), and keep logging benign if the file write fails.

-import fs from "fs";
-import path from "path";
+import fs from "fs";
+import os from "os";
+import path from "path";
 
-const logFile = path.join("/tmp", "plane-mcp-debug.log");
-function debugLog(message: string) {
+const logFile = path.join(os.tmpdir(), "plane-mcp-debug.log");
+function debugLog(message: string) {
   const timestamp = new Date().toISOString();
   const logMessage = `[${timestamp}] ${message}\n`;
-  fs.appendFileSync(logFile, logMessage);
-  console.error(message);
+  try {
+    fs.appendFileSync(logFile, logMessage);
+  } catch (error) {
+    console.error(`[AUTH] debugLog write failed: ${error}`);
+  }
+  console.error(message);
 }

80-83: Guard access to the axios cookie jar

(instance.defaults as any).jar can be undefined (library update, misconfiguration) and will crash when you call getCookies. Fail fast with an explicit guard so we return a structured error instead of throwing.

-    const jar = (instance.defaults as any).jar as CookieJar;
-    const cookies = await jar.getCookies(host);
+    const maybeJar = (instance.defaults as Record<string, unknown>).jar;
+    if (!(maybeJar instanceof CookieJar)) {
+      debugLog("[AUTH] ERROR: Cookie jar not found on axios instance");
+      return { success: false, error: "cookies", message: "Cookie jar not available for session authentication" };
+    }
+    const jar = maybeJar;
+    const cookies = await jar.getCookies(host);

82-83: Remove cookie value logging

Even truncated values expose session/CSRF tokens once logs leak. Log only cookie names (or counts) to avoid credential disclosure.

-    debugLog(`[AUTH] Cookies after CSRF request: ${cookies.map(c => `${c.key}=${c.value.substring(0, 10)}...`).join(", ")}`);
+    debugLog(`[AUTH] Cookies after CSRF request: ${cookies.map(c => c.key).join(", ")}`);
...
-    debugLog(`[AUTH] Cookies after login: ${loginCookies.map(c => `${c.key}=${c.value.substring(0, 10)}...`).join(", ")}`);
+    debugLog(`[AUTH] Cookies after login: ${loginCookies.map(c => c.key).join(", ")}`);

Also applies to: 125-126

src/common/request-helper.ts (1)

2-12: Harden request debugLog path

Same problem as in auth.ts: /tmp plus unguarded appendFileSync will explode on Windows or restricted hosts, killing every request. Switch to os.tmpdir() and catch write failures.

-import fs from "fs";
-import path from "path";
+import fs from "fs";
+import os from "os";
+import path from "path";
 
-const logFile = path.join("/tmp", "plane-mcp-debug.log");
-function debugLog(message: string) {
+const logFile = path.join(os.tmpdir(), "plane-mcp-debug.log");
+function debugLog(message: string) {
   const timestamp = new Date().toISOString();
   const logMessage = `[${timestamp}] ${message}\n`;
-  fs.appendFileSync(logFile, logMessage);
-  console.error(message);
+  try {
+    fs.appendFileSync(logFile, logMessage);
+  } catch (error) {
+    console.error(`[REQUEST] debugLog write failed: ${error}`);
+  }
+  console.error(message);
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c5fdf5 and 7a31baf.

📒 Files selected for processing (5)
  • src/common/auth.ts (1 hunks)
  • src/common/request-helper.ts (1 hunks)
  • src/schemas.ts (1 hunks)
  • src/tools/auth.ts (1 hunks)
  • src/tools/pages.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tools/auth.ts
  • src/schemas.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/common/request-helper.ts (1)
src/common/auth.ts (2)
  • isSessionAuthenticated (164-167)
  • getAxiosInstance (36-45)
src/tools/pages.ts (2)
src/schemas.ts (2)
  • Page (254-277)
  • Page (279-279)
src/common/request-helper.ts (1)
  • makePlaneRequest (31-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot

This commit fixes critical security and robustness issues identified in code review:

1. **Cross-platform debugLog (auth.ts, request-helper.ts)**
   - Changed from hardcoded `/tmp` to `os.tmpdir()` for Windows compatibility
   - Added try-catch around `fs.appendFileSync` to prevent crashes on write failures
   - Continues execution gracefully if log file is not writable

2. **Cookie jar validation (auth.ts:85-90)**
   - Added runtime guard to verify cookie jar exists before access
   - Checks `instanceof CookieJar` instead of unsafe type cast
   - Returns structured error if jar unavailable instead of throwing
   - Prevents crashes if axios-cookiejar-support misconfigured

3. **Security: Remove cookie value logging (auth.ts:92,135)**
   - Changed from logging truncated cookie values to names only
   - Prevents session/CSRF token exposure in debug logs
   - Logs: `csrftoken, session-id` instead of `csrftoken=abc123..., session-id=def456...`

All changes maintain existing functionality while improving reliability and security.
No breaking changes to API or behavior.
}

return { success: false, error: 'unknown', message: String(error) };
}
Copy link

Choose a reason for hiding this comment

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

Bug: Stale Authentication State After Failed Login

When authenticateWithPassword fails, the isAuthenticated flag isn't reset to false. If a user successfully authenticates once then attempts to re-authenticate with invalid credentials, the failure leaves isAuthenticated as true while the actual session cookies may be invalid or partially updated, causing subsequent authenticated requests to use stale credentials with incorrect authentication state.

Fix in Cursor Fix in Web

Copy link

@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: 2

♻️ Duplicate comments (6)
src/common/request-helper.ts (3)

8-17: This is a duplicate of an existing concern already flagged in past reviews regarding unconditional debug logging that risks PII leakage and uses synchronous I/O. While the try/catch adds some robustness, the core issues remain: no environment flag gating, synchronous file writes on every request, and potential logging of sensitive data (full error responses, cookies, etc.).

Based on past review comments.


69-70: This duplicates a concern already flagged in past reviews about logging cookie values. Line 69 logs the first 10 characters of cookie values which could leak session tokens or CSRF tokens. Only cookie names (keys) should be logged.

Based on past review comments.


78-81: Remove Content-Type header from GET requests.

The session authentication path sets Content-Type: application/json unconditionally (line 79), including for GET requests. This is inconsistent with the API key path (lines 98-100) which correctly only sets Content-Type for non-GET requests. Some HTTP servers may reject GET requests with a Content-Type header since GET requests shouldn't have a body.

Apply this diff to fix:

       const config: AxiosRequestConfig = {
         url,
         method,
-        headers: {
-          "Content-Type": "application/json",
-        },
+        headers: {},
       };
 
+      // Only add Content-Type for non-GET requests
+      if (method.toUpperCase() !== "GET") {
+        config.headers["Content-Type"] = "application/json";
+      }
+
       // Include body for non-GET requests
src/common/auth.ts (3)

8-18: This duplicates an existing concern about the debugLog implementation using synchronous file I/O without environment flag gating and potential PII exposure. The try/catch improves robustness but doesn't address the core security and performance concerns.

Based on past review comments.


150-152: Verify authentication with a test API call before marking as authenticated.

Setting isAuthenticated = true based solely on the presence of a session-id cookie doesn't verify the session actually works. The server could return cookies but reject the credentials, or the cookies might not be properly formatted for subsequent requests.

After line 149, add a verification request using the existing axios instance (avoid importing makePlaneRequest to prevent circular dependency):

     loginCookies.forEach(c => {
       debugLog(`[AUTH] Cookie detail - ${c.key}: domain=${c.domain}, path=${c.path}, httpOnly=${c.httpOnly}, secure=${c.secure}`);
     });
 
+    // Verify the session works with a test API call
+    try {
+      const verifyResponse = await instance.get(`${host}api/v1/users/me/`);
+      if (verifyResponse.status !== 200) {
+        debugLog(`[AUTH] Session verification failed with status: ${verifyResponse.status}`);
+        return { success: false, error: 'credentials', message: 'Session verification failed' };
+      }
+      debugLog("[AUTH] Session verified successfully");
+    } catch (verifyError) {
+      debugLog(`[AUTH] Session verification request failed: ${verifyError}`);
+      return { success: false, error: 'credentials', message: 'Could not verify session validity' };
+    }
+
     isAuthenticated = true;

191-202: Fix error handling and cleanup order in resetAuthentication.

If jar.removeAllCookies() throws at line 195, the function exits before resetting axiosInstance and isAuthenticated (lines 199-201), leaving authentication state inconsistent. Additionally, line 193 uses an unsafe type cast.

Apply this diff to ensure cleanup always completes:

 export async function resetAuthentication(): Promise<void> {
+  try {
     if (axiosInstance) {
-      const jar = (axiosInstance.defaults as any).jar as CookieJar | undefined;
-      if (jar) {
+      const maybeJar = (axiosInstance.defaults as Record<string, unknown>).jar;
+      if (maybeJar instanceof CookieJar) {
+        const jar = maybeJar;
         await jar.removeAllCookies();
         debugLog("[AUTH] Cookie jar cleared");
       }
     }
+  } catch (error) {
+    debugLog(`[AUTH] Error clearing cookies: ${error}`);
+    // Continue with cleanup even if cookie removal fails
+  } finally {
     axiosInstance = null;
     isAuthenticated = false;
     debugLog("[AUTH] Authentication reset");
+  }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a31baf and 1b22916.

📒 Files selected for processing (2)
  • src/common/auth.ts (1 hunks)
  • src/common/request-helper.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/request-helper.ts (1)
src/common/auth.ts (2)
  • isSessionAuthenticated (174-177)
  • getAxiosInstance (41-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
src/common/auth.ts (2)

85-90: LGTM - Cookie jar validation implemented.

Good defensive programming! The runtime validation checks that the jar exists and is a CookieJar instance before use, preventing TypeScript's type safety from being bypassed.


92-92: LGTM - Cookie logging fixed.

Correctly logs only cookie names without values, addressing the security concern from past reviews.

src/common/request-helper.ts (1)

43-43: All review concerns are unfounded—the regex is correctly designed and safe.

The verification confirms:

  1. All pages endpoints are properly matched: Every endpoint in the codebase (/pages/, /pages-summary/, /favorite-pages/, /pages/{id}/description/, /pages/{id}/versions/, etc.) is correctly matched by the regex.

  2. Concern about /pages$: While this pattern is technically redundant (no actual endpoint uses paths without trailing slashes), it causes no harm and doesn't introduce bugs.

  3. Concern about false positives: Invalid. The regex uses leading slash boundaries (/\/pages-summary\/, /\/favorite-pages\/) that prevent matching unintended paths like "old-pages-summary/" or "my-favorite-pages/".

  4. Boundary checks already present: The regex includes leading-slash boundaries in all patterns, properly constraining matches to intended pages endpoints.

Likely an incorrect or invalid review comment.

…d request handling

This commit addresses all remaining CodeRabbit feedback:

1. **Session Verification (auth.ts:150-161)**
   - Added test API call to /api/v1/users/me/ after login
   - Verifies session actually works before marking authenticated
   - Prevents false positives from cookies that don't grant access
   - Returns 'credentials' error if verification fails

2. **Content-Type Header Fix (request-helper.ts:75-86)**
   - Session auth path now only sets Content-Type for non-GET requests
   - Matches API key path behavior (lines 97-100)
   - Prevents potential HTTP server rejections of GET with Content-Type
   - Properly initializes headers as Record<string, string>

3. **Cookie Logging Security (request-helper.ts:69)**
   - Changed from logging cookie values to names only
   - Prevents exposure of session tokens in debug logs
   - Consistent with auth.ts cookie logging fix

4. **resetAuthentication Robustness (auth.ts:191-209)**
   - Wrapped cookie cleanup in try-catch-finally
   - Ensures axiosInstance and isAuthenticated always reset
   - Prevents inconsistent state if jar.removeAllCookies() throws
   - Uses safe jar access with instanceof check

All changes maintain backward compatibility and improve production reliability.
Build verified successful.
Copy link

@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: 2

♻️ Duplicate comments (1)
src/common/auth.ts (1)

120-131: CRITICAL: Stop logging session cookies in plaintext.

Lines 122 and 127 still use JSON.stringify() on response headers and Set-Cookie values, which exposes complete session cookie values in plaintext logs. This is sufficient for session hijacking if logs are compromised.

This issue was flagged in previous reviews and marked as addressed, but the code still contains the vulnerability. Apply this diff to redact sensitive values:

     // Log response details
     debugLog(`[AUTH] Login response status: ${loginResponse.status}`);
-    debugLog(`[AUTH] Login response headers: ${JSON.stringify(loginResponse.headers)}`);
+    const headerNames = Object.keys(loginResponse.headers ?? {});
+    debugLog(`[AUTH] Login response headers present: ${headerNames.join(", ")}`);
 
     // Check if Set-Cookie headers are present
     const setCookieHeader = loginResponse.headers['set-cookie'];
     if (setCookieHeader) {
-      debugLog(`[AUTH] Set-Cookie headers received: ${JSON.stringify(setCookieHeader)}`);
+      debugLog(`[AUTH] Set-Cookie headers received: ${Array.isArray(setCookieHeader) ? setCookieHeader.length : 1} cookie(s)`);
     } else {
       debugLog(`[AUTH] ERROR: No Set-Cookie headers in login response!`);
🧹 Nitpick comments (5)
src/common/auth.ts (4)

8-18: Consider async logging and environment flag to reduce production overhead.

The synchronous fs.appendFileSync blocks the event loop on every request, which can impact latency under load. Additionally, unconditional logging of all requests and responses may expose sensitive data and degrade performance in production.

Consider these improvements:

+const DEBUG_ENABLED = process.env.PLANE_MCP_DEBUG === 'true';
+
 const logFile = path.join(os.tmpdir(), "plane-mcp-debug.log");
-function debugLog(message: string) {
+async function debugLog(message: string) {
+  if (!DEBUG_ENABLED) return;
+  
   const timestamp = new Date().toISOString();
   const logMessage = `[${timestamp}] ${message}\n`;
   try {
-    fs.appendFileSync(logFile, logMessage);
+    await fs.promises.appendFile(logFile, logMessage);
   } catch (error) {
     console.error(`[AUTH] debugLog write failed: ${error}`);
   }
-  console.error(message);
+  if (DEBUG_ENABLED) {
+    console.error(message);
+  }
 }

Note: Since this changes debugLog to async, all callsites would need to be updated to await debugLog(...) or the function should not be awaited (fire-and-forget), which is acceptable for debug logs.


115-118: Consider restricting validateStatus to 2xx responses only.

While the session verification step (lines 151-161) ensures the session is valid, accepting 3xx redirects as successful login responses is fragile and could be confusing. Different server configurations might redirect for various reasons unrelated to successful authentication.

Apply this diff to be more explicit:

         maxRedirects: 0, // Don't follow redirects, we just need the cookies
-        validateStatus: (status) => status >= 200 && status < 400, // Accept redirects as success
+        validateStatus: (status) => status >= 200 && status < 300, // Accept only 2xx as success
       }

The session verification request that follows will catch any authentication failures, making this change safe.


145-148: Consider reducing cookie metadata logging verbosity.

While not logging cookie values (which would be a security issue), logging detailed cookie attributes (domain, path, httpOnly, secure) for every cookie is quite verbose and may not be necessary in production. This information could be useful during development but should ideally be gated behind a debug flag.

Consider removing this detailed logging or gating it behind the same debug flag suggested earlier:

     // Log full cookie details for debugging
-    loginCookies.forEach(c => {
-      debugLog(`[AUTH] Cookie detail - ${c.key}: domain=${c.domain}, path=${c.path}, httpOnly=${c.httpOnly}, secure=${c.secure}`);
-    });
+    if (process.env.PLANE_MCP_DEBUG === 'verbose') {
+      loginCookies.forEach(c => {
+        debugLog(`[AUTH] Cookie detail - ${c.key}: domain=${c.domain}, path=${c.path}, httpOnly=${c.httpOnly}, secure=${c.secure}`);
+      });
+    }

187-190: Consider implementing session expiry detection.

The current implementation only checks a boolean flag without considering session age. Sessions can expire on the server side, and this could lead to using stale credentials that will fail on actual requests.

Consider tracking authentication time and implementing expiry detection:

let authenticationTime: number | null = null;
const SESSION_TIMEOUT_MS = 3600000; // 1 hour, adjust based on server session timeout

// In authenticateWithPassword, after successful verification:
authenticationTime = Date.now();
isAuthenticated = true;

// Update isSessionAuthenticated:
export function isSessionAuthenticated(): boolean {
  if (!isAuthenticated || !authenticationTime) {
    debugLog(`[AUTH] isSessionAuthenticated() - not authenticated`);
    return false;
  }
  
  const isStale = Date.now() - authenticationTime > SESSION_TIMEOUT_MS;
  if (isStale) {
    debugLog(`[AUTH] Session expired, resetting authentication`);
    isAuthenticated = false;
    authenticationTime = null;
    return false;
  }
  
  debugLog(`[AUTH] isSessionAuthenticated() - authenticated and valid`);
  return true;
}

Also update resetAuthentication to clear authenticationTime.

src/common/request-helper.ts (1)

40-46: Consider a more maintainable approach to endpoint routing.

The regex-based detection of pages endpoints works but is brittle and could miss edge cases or require updates as the API evolves. A false positive (non-pages endpoint matched as pages) would route to session auth when API key is needed, and vice versa.

Consider these alternatives:

  1. Explicit endpoint registry:
const PAGES_ENDPOINTS = [
  '/pages/',
  '/pages$',
  '/pages-summary/',
  '/favorite-pages/',
  '/description/',
  '/versions/'
];

const isPagesEndpoint = PAGES_ENDPOINTS.some(pattern => path.includes(pattern));
  1. Caller-specified authentication:
export async function makePlaneRequest<T>(
  method: string, 
  path: string, 
  body: any = null,
  options?: { authMode?: 'session' | 'api_key' }
): Promise<T>

This would make the routing logic more explicit and easier to maintain as the API evolves.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b22916 and 28615e9.

📒 Files selected for processing (2)
  • src/common/auth.ts (1 hunks)
  • src/common/request-helper.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/request-helper.ts (1)
src/common/auth.ts (2)
  • isSessionAuthenticated (187-190)
  • getAxiosInstance (41-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot

Comment on lines +7 to +17
const logFile = path.join(os.tmpdir(), "plane-mcp-debug.log");
function debugLog(message: string) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
try {
fs.appendFileSync(logFile, logMessage);
} catch (error) {
console.error(`[REQUEST] debugLog write failed: ${error}`);
}
console.error(message);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract debugLog to a shared utility module to eliminate duplication.

The debugLog function is duplicated between auth.ts and request-helper.ts with identical implementations. This violates the DRY principle and makes maintenance harder (fixes must be applied in multiple places).

Create a shared src/common/debug.ts module:

// src/common/debug.ts
import fs from "fs";
import os from "os";
import path from "path";

const DEBUG_ENABLED = process.env.PLANE_MCP_DEBUG === 'true';
const logFile = path.join(os.tmpdir(), "plane-mcp-debug.log");

export async function debugLog(message: string) {
  if (!DEBUG_ENABLED) return;
  
  const timestamp = new Date().toISOString();
  const logMessage = `[${timestamp}] ${message}\n`;
  try {
    await fs.promises.appendFile(logFile, logMessage);
  } catch (error) {
    console.error(`debugLog write failed: ${error}`);
  }
  if (DEBUG_ENABLED) {
    console.error(message);
  }
}

Then import and use it in both files:

import { debugLog } from "./debug.js";

This also addresses the synchronous I/O concerns by making it async and adds an environment flag control.

🤖 Prompt for AI Agents
In src/common/request-helper.ts around lines 7 to 17, the local debugLog is
duplicated across files; extract it into a shared module src/common/debug.ts
that exports an async debugLog(message: string) which checks an env flag
(process.env.PLANE_MCP_DEBUG === 'true'), computes the timestamped log message,
uses fs.promises.appendFile to write to path.join(os.tmpdir(),
"plane-mcp-debug.log") inside a try/catch (logging write failures via
console.error), and prints the message to stderr when DEBUG is enabled; then
remove the local debugLog and replace it with an import { debugLog } from
"./debug.js" and update call sites to await debugLog(...) or handle the returned
Promise so there are no unhandled promises.

Comment on lines +125 to +127
debugLog(`[REQUEST] Error: ${error.message}, status: ${error.response?.status}`);
debugLog(`[REQUEST] Error response: ${JSON.stringify(error.response?.data)}`);
throw new Error(`Request failed: ${error.message} (${error.response?.status}). Response: ${JSON.stringify(error.response?.data)}`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

CRITICAL: Avoid logging and exposing full error response data.

Lines 126-127 log and throw the complete error.response?.data, which may contain sensitive project information, user data, or other PII. This violates data privacy principles and could expose sensitive information in logs and error messages shown to users.

Apply this diff to log only error metadata:

       debugLog(`[REQUEST] Error: ${error.message}, status: ${error.response?.status}`);
-      debugLog(`[REQUEST] Error response: ${JSON.stringify(error.response?.data)}`);
-      throw new Error(`Request failed: ${error.message} (${error.response?.status}). Response: ${JSON.stringify(error.response?.data)}`);
+      // Log only non-sensitive error metadata
+      const errorType = error.response?.data?.type || 'unknown';
+      debugLog(`[REQUEST] Error response status: ${error.response?.status}, type: ${errorType}`);
+      throw new Error(`Request failed: ${error.message} (${error.response?.status})`);

If detailed error information is needed for debugging, gate it behind a verbose debug flag:

if (process.env.PLANE_MCP_DEBUG === 'verbose') {
  debugLog(`[REQUEST] Full error response: ${JSON.stringify(error.response?.data)}`);
}
🤖 Prompt for AI Agents
In src/common/request-helper.ts around lines 125 to 127, the code currently logs
and throws the complete error.response?.data which may contain sensitive PII;
change it to log only non-sensitive metadata (e.g., error.message and
error.response?.status and headers) and remove
JSON.stringify(error.response?.data) from both the debug log and the thrown
Error message; if full response data is required for debugging, wrap a
debug-only conditional that logs the full payload only when
process.env.PLANE_MCP_DEBUG === 'verbose'; ensure the Error thrown contains only
a safe summary (message and status) not the full response body.

This commit addresses a CRITICAL security vulnerability identified in code review:

**Security Issue (auth.ts:120-131):**
- Lines 122 and 127 were logging full HTTP headers and Set-Cookie values
- This exposed complete session cookie values in plaintext debug logs
- Session cookies include authentication tokens sufficient for session hijacking
- If debug logs are compromised, attackers could steal valid sessions

**Fix Applied:**
- Line 122: Changed from logging full headers JSON to just header names
  - Before: `JSON.stringify(loginResponse.headers)`
  - After: `Object.keys(loginResponse.headers).join(", ")`
- Line 127: Changed from logging full Set-Cookie values to cookie count
  - Before: `JSON.stringify(setCookieHeader)`
  - After: `${Array.isArray(setCookieHeader) ? setCookieHeader.length : 1} cookie(s)`

**Security Impact:**
- Debug logs now show only metadata (header names, cookie counts)
- No sensitive cookie values or tokens are logged
- Session hijacking via log compromise is no longer possible
- Maintains debugging utility without security risk

This fix completes the security hardening of the authentication flow.
Build verified successful.
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
"integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"license": "MIT",
"peer": true,
Copy link

Choose a reason for hiding this comment

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

Bug: Incorrect Peer Dependency Resolution Breaks Installs

The axios package is marked with "peer": true in package-lock.json despite being listed as a direct dependency in package.json. This causes axios not to be installed when running npm install or npm ci, leading to runtime errors when the authentication and request modules try to import it. The same issue affects tough-cookie at line 2952. This occurs because axios-cookiejar-support declares both as peer dependencies, and npm incorrectly resolved them as peer-only rather than installing them as actual dependencies. The package-lock file needs regeneration after adding both packages to ensure they're installed as direct dependencies.

Fix in Cursor Fix in Web

Copy link
Author

Choose a reason for hiding this comment

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

The "peer": true flag in package-lock.json is not a bug - it's correct npm v7+ behavior.

Why Cursor is Incorrect

The "peer": true flag is metadata that indicates a package serves dual purposes:

  1. Direct dependency (listed in package.json)
  2. Satisfies peer dependency requirements from other packages

In our case, axios is both:

  • A direct dependency of our project
  • A peer dependency required by axios-cookiejar-support

npm deduplicates these into a single installation and marks it with "peer": true in the lockfile.

Evidence the Package IS Installed

Listed in package.json dependencies:

"axios": "1.12.0",
"tough-cookie": "^5.1.2"

Present in node_modules:

$ ls node_modules/ | grep -E "axios|tough-cookie"
axios
axios-cookiejar-support
tough-cookie

Build succeeds:

$ npm run build
> rm -rf build && tsc && chmod 755 build/index.js
# ✓ No errors

Imports resolve correctly in compiled output:

import axios from "axios";
import { wrapper } from "axios-cookiejar-support";
import { CookieJar } from "tough-cookie";

Conclusion

The "peer": true flag does NOT prevent installation. It's correct npm lockfile v3 behavior for deduplicated dependencies. No changes needed.

References:

@koke1997 koke1997 mentioned this pull request Nov 16, 2025
@Nicholas-Schaub
Copy link

I tried this and received an error:
No Session cookies received from server.

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.

2 participants