Skip to content

feat: add GitLab support and upload API#630

Open
xuanxiang wants to merge 1 commit intositeboon:mainfrom
xuanxiang:feat/gitlab-and-upload-api
Open

feat: add GitLab support and upload API#630
xuanxiang wants to merge 1 commit intositeboon:mainfrom
xuanxiang:feat/gitlab-and-upload-api

Conversation

@xuanxiang
Copy link
Copy Markdown

@xuanxiang xuanxiang commented Apr 10, 2026

Summary

This PR adds GitLab platform support and a new upload API endpoint:

  • GitLab Platform Support: Added GitLab as an alternative to GitHub, including:

    • GitLab token management (create, read, delete, toggle)
    • GitLab API integration for project operations
    • GitLab platform constants and configuration
  • Refactored Git Platform Architecture: Created a modular structure:

    • server/git-platforms/constants.js: Platform-specific constants
    • server/git-platforms/github-platform.js: GitHub-specific implementation
    • server/git-platforms/gitlab-platform.js: GitLab-specific implementation
    • server/git-platforms/index.js: Unified platform interface
  • Upload API: New /upload endpoint for handling file uploads

  • Database Enhancements: Added hasUsersByUsername method for user validation

Files Changed

  • server/database/db.js: Added user validation and GitLab token operations
  • server/git-platforms/: New directory with platform abstraction layer
  • server/routes/agent.js: Updated to support multiple git platforms
  • server/routes/upload.js: New upload endpoint
  • server/index.js: Registered upload routes

Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com

Summary by CodeRabbit

  • New Features
    • Added GitLab platform support, enabling seamless integration with GitLab repositories for cloning, branch creation, and merge request management alongside existing GitHub support
    • Added file upload functionality supporting single and multiple file uploads with configurable storage directories

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

This PR introduces GitLab platform support alongside GitHub by implementing a Git platform abstraction layer. Database support for GitLab tokens is added, along with platform-specific implementations for repository operations. A file upload endpoint is also introduced.

Changes

Cohort / File(s) Summary
Database Layer
server/database/db.js
Added userDb.hasUsersByUsername() for username lookup and introduced gitlabTokensDb object with CRUD methods (createGitlabToken, getGitlabTokens, getActiveGitlabToken, deleteGitlabToken, toggleGitlabToken) for managing GitLab tokens.
Git Platform Infrastructure
server/git-platforms/constants.js, server/git-platforms/github-platform.js, server/git-platforms/gitlab-platform.js, server/git-platforms/index.js
Added platform constants (identifiers, credential types, URL patterns); implemented GitHubPlatform and GitLabPlatform classes with methods for URL parsing, repository cloning, branch creation, and PR/MR creation; created GitPlatformFactory for platform detection and instantiation from repository URLs.
Route and API Updates
server/index.js, server/routes/agent.js, server/routes/upload.js
Mounted new upload routes at /api/upload; refactored agent route to support both GitHub and GitLab platforms via factory pattern, replacing GitHub-only logic with platform-agnostic operations; added file upload endpoint with multer configuration supporting single and multiple file uploads up to 100MB.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Agent as Agent Route
    participant Factory as GitPlatformFactory
    participant Platform as GitHub/GitLab Platform
    participant Git as Git Operations
    participant API as GitHub/GitLab API

    Client->>Agent: POST /api/agent (with githubUrl, token)
    activate Agent
    Agent->>Factory: detectPlatform(url)
    activate Factory
    Factory-->>Agent: GITHUB or GITLAB
    deactivate Factory
    
    Agent->>Platform: create(platform, token, customDomain, apiVersion)
    activate Platform
    Platform-->>Agent: Platform instance
    deactivate Platform
    
    Agent->>Platform: cloneRepo(url, projectPath)
    activate Platform
    Platform->>Git: git clone [with optional token embedding]
    activate Git
    Git-->>Platform: Repository cloned
    deactivate Git
    Platform-->>Agent: Cloned successfully
    deactivate Platform
    
    Agent->>Platform: parseUrl(url)
    Platform-->>Agent: {owner, repo}
    
    Agent->>Platform: createBranch(owner, repo, branchName)
    activate Platform
    Platform->>API: Create branch reference
    activate API
    API-->>Platform: Branch created
    deactivate API
    Platform-->>Agent: Branch created
    deactivate Platform
    
    Agent->>Platform: createPR(owner, repo, branchName, title, body)
    activate Platform
    Platform->>API: Create pull/merge request
    activate API
    API-->>Platform: PR/MR created
    deactivate API
    Platform-->>Agent: {number, url}
    deactivate Platform
    
    Agent-->>Client: git-pr event with PR/MR details
    deactivate Agent
Loading

Suggested Reviewers

  • blackmammoth
  • viper151

Poem

🐰 Hop and hop through GitHub and GitLab too,
Platform-agnostic cloning, branch-making anew,
Tokens and uploads dance in the code,
Multiple platforms share the same road,
A factory born to detect and create! 🌟

🚥 Pre-merge checks | ✅ 3
✅ 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 accurately summarizes the two main features added: GitLab support and upload API. It is concise, specific, and directly reflects the primary changes in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

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

🤖 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/database/db.js`:
- Around line 634-636: The getActiveGitlabToken function is returning the GitHub
token key; update getActiveGitlabToken to call
credentialsDb.getActiveCredential(userId, 'gitlab_token') instead of
'github_token' so GitLab flows in server/routes/agent.js receive the correct
secret; ensure any remaining TODO/comments reflect this change.

In `@server/git-platforms/github-platform.js`:
- Around line 101-135: The code embeds the PAT in cloneUrl and logs raw git
stderr, risking credential leaks; instead, stop injecting this.token into
cloneUrl and clone using the original url, supplying credentials via a temporary
GIT_ASKPASS helper and safe env for spawn (set env.GIT_ASKPASS to a short
executable that prints the token, set GIT_TERMINAL_PROMPT=0 and pass the
composed env to spawn in the gitProcess invocation), ensure the helper file is
created with restrictive perms and removed after clone, and sanitize or redact
occurrences of this.token before any logging of stderr/stdout (references:
cloneUrl, this.token, spawn, gitProcess, cloneDir).

In `@server/git-platforms/gitlab-platform.js`:
- Around line 141-179: The code embeds the GitLab token into cloneUrl and then
logs raw git stderr, risking secret leakage into .git/config and logs; instead
stop injecting the token into cloneUrl (leave cloneUrl as the original url), and
authenticate the clone by creating a temporary credential source (e.g., a
temporary .netrc containing "machine <host> login oauth2 password <token>" or
using GIT_ASKPASS) and run spawn('git', ['clone', '--depth','1', cloneUrl,
cloneDir], { env: { ...process.env, HOME: tempHome } }) so credentials are
provided to git without altering the repo config, and update the
gitProcess.stderr handler and error path to redact the token from any logged
stderr/exception (replace occurrences of this.token before console.error or
reject) to ensure the token never appears in logs or .git/config.

In `@server/git-platforms/index.js`:
- Around line 75-80: The current extraction for PLATFORMS.GITLAB sets
customDomain from url.match(...) but only captures the hostname and drops
explicit ports; update the logic in the GitLab branch (where PLATFORMS.GITLAB
and variable customDomain are handled, and the domainMatch extraction occurs) to
preserve host:port — either adjust the regex to capture an optional :port (e.g.
include [^\/]+ to allow colon) or parse the URL with the standard URL
constructor and use urlObj.host (which includes the port) before assigning
customDomain so createPR() and getBranchUrl() target the correct origin for
self-hosted instances.
- Around line 39-41: The code currently defaults unknown hosts to
PLATFORMS.GITLAB which causes invalid GitLab-specific behavior; change this to
reject unsupported remotes instead — inside the platform-detection function (the
code that currently logs "Could not detect platform, defaulting to GITLAB" and
returns PLATFORMS.GITLAB) replace the return with throwing or returning a clear
unsupported-platform error (e.g., throw new Error(`Unsupported git remote host:
${host}`) or return a sentinel like PLATFORMS.UNKNOWN) and ensure callers of
that function handle the error/sentinel (so MR/URL generation paths don't assume
GitLab). Ensure you reference PLATFORMS and the detection function name when
making the change.

In `@server/index.js`:
- Around line 408-409: The upload endpoints are mounted without authentication;
wrap or insert the authentication middleware when mounting uploadRoutes so only
authenticated requests can write files — e.g., ensure authenticateToken (the
middleware used elsewhere) is imported and applied to the route mount (use
authenticateToken with app.use('/api/upload', ...) or add it to the router in
server/routes/upload.js) so uploadRoutes cannot be called unauthenticated.

In `@server/routes/agent.js`:
- Around line 896-905: Remove the debug log that prints tokens to stdout — the
console.log("tokenToUse:", tokenToUse) in server/routes/agent.js leaks secrets;
instead delete that line (or replace with a non-sensitive log such as logging
only which platform was chosen or a boolean flag). Ensure changes touch the
block that sets tokenToUse (variables gitPlatform, githubToken,
githubTokensDb.getActiveGithubToken, gitlabTokensDb.getActiveGitlabToken) so no
token values are ever written to logs.

In `@server/routes/upload.js`:
- Around line 14-43: Replace caller-controlled directory and raw originalname
usage with a safe, fixed-root scheme: in getDirectoryFromReq and the
storage.destination callback ignore absolute or upstream paths from
req.query/headers and instead map any allowed selector to a subdirectory under a
defined UPLOAD_ROOT (use path.join(UPLOAD_ROOT, selector) only after validating
selector contains no path separators), then normalize the resolved path and
verify it startsWith the UPLOAD_ROOT before creating it; in the storage.filename
callback sanitize file.originalname by taking path.basename, stripping any ../
or path separators, applying a whitelist or character filter, and preferably
prefixing with a unique token (timestamp/UUID) to avoid overwrites. Ensure
references: getDirectoryFromReq, storage (multer.diskStorage), destination
callback, and filename callback are updated accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 90b42c06-2beb-433b-aaaf-0d64b1b8bc8e

📥 Commits

Reviewing files that changed from the base of the PR and between 388134c and df51844.

📒 Files selected for processing (8)
  • server/database/db.js
  • server/git-platforms/constants.js
  • server/git-platforms/github-platform.js
  • server/git-platforms/gitlab-platform.js
  • server/git-platforms/index.js
  • server/index.js
  • server/routes/agent.js
  • server/routes/upload.js

Comment on lines +634 to +636
getActiveGitlabToken: (userId) => {
return credentialsDb.getActiveCredential(userId, 'github_token'); // todo 临时使用 github_token
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Return the GitLab token here, not the GitHub token.

Line 635 still queries 'github_token', so GitLab requests will either get null when only a GitLab token exists or send the wrong secret when both are configured. That breaks the new GitLab clone/MR path in server/routes/agent.js.

Suggested fix
   getActiveGitlabToken: (userId) => {
-    return credentialsDb.getActiveCredential(userId, 'github_token'); // todo 临时使用 github_token
+    return credentialsDb.getActiveCredential(userId, 'gitlab_token');
   },
📝 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
getActiveGitlabToken: (userId) => {
return credentialsDb.getActiveCredential(userId, 'github_token'); // todo 临时使用 github_token
},
getActiveGitlabToken: (userId) => {
return credentialsDb.getActiveCredential(userId, 'gitlab_token');
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/database/db.js` around lines 634 - 636, The getActiveGitlabToken
function is returning the GitHub token key; update getActiveGitlabToken to call
credentialsDb.getActiveCredential(userId, 'gitlab_token') instead of
'github_token' so GitLab flows in server/routes/agent.js receive the correct
secret; ensure any remaining TODO/comments reflect this change.

Comment on lines +101 to +135
// Prepare git clone URL with authentication if token is provided
let cloneUrl = url;
if (this.token) {
// Convert HTTPS URL to authenticated URL
// Example: https://github.com/user/repo -> https://token@github.com/user/repo
cloneUrl = url.replace('https://github.com', `https://${this.token}@github.com`);
}

console.log('🔄 Cloning repository:', url);
console.log('📁 Destination:', cloneDir);

// Execute git clone
const gitProcess = spawn('git', ['clone', '--depth', '1', cloneUrl, cloneDir], {
stdio: ['pipe', 'pipe', 'pipe']
});

let stdout = '';
let stderr = '';

gitProcess.stdout.on('data', (data) => {
stdout += data.toString();
});

gitProcess.stderr.on('data', (data) => {
stderr += data.toString();
console.log('Git stderr:', data.toString());
});

gitProcess.on('close', (code) => {
if (code === 0) {
console.log('✅ Repository cloned successfully');
resolve(cloneDir);
} else {
console.error('❌ Git clone failed:', stderr);
reject(new Error(`Git clone failed: ${stderr}`));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid putting the PAT in the clone URL.

Line 106 injects the token into the remote URL, which means the secret can end up persisted in .git/config. Then Lines 124-135 log raw git stderr, which often echoes the failing URL back on auth errors. That creates an avoidable credential-leak path in both disk state and logs.

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

In `@server/git-platforms/github-platform.js` around lines 101 - 135, The code
embeds the PAT in cloneUrl and logs raw git stderr, risking credential leaks;
instead, stop injecting this.token into cloneUrl and clone using the original
url, supplying credentials via a temporary GIT_ASKPASS helper and safe env for
spawn (set env.GIT_ASKPASS to a short executable that prints the token, set
GIT_TERMINAL_PROMPT=0 and pass the composed env to spawn in the gitProcess
invocation), ensure the helper file is created with restrictive perms and
removed after clone, and sanitize or redact occurrences of this.token before any
logging of stderr/stdout (references: cloneUrl, this.token, spawn, gitProcess,
cloneDir).

Comment on lines +141 to +179
let cloneUrl = url;
if (this.token) {
// For HTTPS URLs, convert to authenticated URL
if (url.startsWith('https://')) {
// GitLab uses: https://oauth2:TOKEN@gitlab.com/owner/repo
cloneUrl = url.replace('https://', `https://oauth2:${this.token}@`);
} else {
// For SSH URLs, token is not used for clone
console.log('ℹ️ Token provided but using SSH URL - ensure SSH key is configured');
}
}

console.log('🔄 Cloning repository:', url);
console.log('📁 Destination:', cloneDir);

// Execute git clone
const gitProcess = spawn('git', ['clone', '--depth', '1', cloneUrl, cloneDir], {
stdio: ['pipe', 'pipe', 'pipe']
});

let stdout = '';
let stderr = '';

gitProcess.stdout.on('data', (data) => {
stdout += data.toString();
});

gitProcess.stderr.on('data', (data) => {
stderr += data.toString();
console.log('Git stderr:', data.toString());
});

gitProcess.on('close', (code) => {
if (code === 0) {
console.log('✅ Repository cloned successfully');
resolve(cloneDir);
} else {
console.error('❌ Git clone failed:', stderr);
reject(new Error(`Git clone failed: ${stderr}`));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't embed the GitLab token in the remote URL.

Lines 145-146 build an authenticated clone URL, so the token can be written into .git/config for the cloned repo. Lines 168-179 then log raw git stderr, which can surface that same URL on failures. This is a secret-handling regression in the new GitLab path.

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

In `@server/git-platforms/gitlab-platform.js` around lines 141 - 179, The code
embeds the GitLab token into cloneUrl and then logs raw git stderr, risking
secret leakage into .git/config and logs; instead stop injecting the token into
cloneUrl (leave cloneUrl as the original url), and authenticate the clone by
creating a temporary credential source (e.g., a temporary .netrc containing
"machine <host> login oauth2 password <token>" or using GIT_ASKPASS) and run
spawn('git', ['clone', '--depth','1', cloneUrl, cloneDir], { env: {
...process.env, HOME: tempHome } }) so credentials are provided to git without
altering the repo config, and update the gitProcess.stderr handler and error
path to redact the token from any logged stderr/exception (replace occurrences
of this.token before console.error or reject) to ensure the token never appears
in logs or .git/config.

Comment on lines +39 to +41
// Default to GITLAB
console.warn('⚠️ Could not detect platform, defaulting to GITLAB');
return PLATFORMS.GITLAB;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject unsupported remotes instead of silently treating them as GitLab.

Lines 39-41 turn every unrecognized host into PLATFORMS.GITLAB. That means a non-GitHub/non-GitLab remote can still flow into GitLab-specific URL parsing, branch-link generation, and MR creation logic, yielding bogus links or misleading runtime failures instead of a clear "unsupported platform" error.

Suggested fix
-    console.warn('⚠️ Could not detect platform, defaulting to GITLAB');
-    return PLATFORMS.GITLAB;
+    throw new Error(`Unsupported git platform for URL: ${url}`);
📝 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
// Default to GITLAB
console.warn('⚠️ Could not detect platform, defaulting to GITLAB');
return PLATFORMS.GITLAB;
// Default to GITLAB
throw new Error(`Unsupported git platform for URL: ${url}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/git-platforms/index.js` around lines 39 - 41, The code currently
defaults unknown hosts to PLATFORMS.GITLAB which causes invalid GitLab-specific
behavior; change this to reject unsupported remotes instead — inside the
platform-detection function (the code that currently logs "Could not detect
platform, defaulting to GITLAB" and returns PLATFORMS.GITLAB) replace the return
with throwing or returning a clear unsupported-platform error (e.g., throw new
Error(`Unsupported git remote host: ${host}`) or return a sentinel like
PLATFORMS.UNKNOWN) and ensure callers of that function handle the error/sentinel
(so MR/URL generation paths don't assume GitLab). Ensure you reference PLATFORMS
and the detection function name when making the change.

Comment on lines +75 to +80
// For GitLab, extract custom domain from URL
let customDomain = null;
if (platform === PLATFORMS.GITLAB) {
const domainMatch = url.match(/(?:https?:\/\/|ssh:\/\/git@|git@)([^:\/]+)/);
if (domainMatch) {
customDomain = domainMatch[1];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve explicit host ports when extracting the GitLab base URL.

Lines 78-80 only capture the hostname, so an input like https://gitlab.example.com:8443/group/repo.git becomes gitlab.example.com. GitLabPlatform.createPR() and getBranchUrl() then target the wrong origin for self-hosted instances running on a non-default HTTP port.

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

In `@server/git-platforms/index.js` around lines 75 - 80, The current extraction
for PLATFORMS.GITLAB sets customDomain from url.match(...) but only captures the
hostname and drops explicit ports; update the logic in the GitLab branch (where
PLATFORMS.GITLAB and variable customDomain are handled, and the domainMatch
extraction occurs) to preserve host:port — either adjust the regex to capture an
optional :port (e.g. include [^\/]+ to allow colon) or parse the URL with the
standard URL constructor and use urlObj.host (which includes the port) before
assigning customDomain so createPR() and getBranchUrl() target the correct
origin for self-hosted instances.

Comment on lines +408 to +409
// Upload API Routes (protected)
app.use('/api/upload', uploadRoutes);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Actually protect the upload routes.

Line 409 mounts uploadRoutes without authenticateToken, even though the route is labeled "protected". Given server/routes/upload.js lets the caller choose the destination path, this currently exposes a new unauthenticated file-write API.

Suggested fix
 // Upload API Routes (protected)
-app.use('/api/upload', uploadRoutes);
+app.use('/api/upload', authenticateToken, uploadRoutes);
📝 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
// Upload API Routes (protected)
app.use('/api/upload', uploadRoutes);
// Upload API Routes (protected)
app.use('/api/upload', authenticateToken, uploadRoutes);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 408 - 409, The upload endpoints are mounted
without authentication; wrap or insert the authentication middleware when
mounting uploadRoutes so only authenticated requests can write files — e.g.,
ensure authenticateToken (the middleware used elsewhere) is imported and applied
to the route mount (use authenticateToken with app.use('/api/upload', ...) or
add it to the router in server/routes/upload.js) so uploadRoutes cannot be
called unauthenticated.

Comment on lines +896 to +905
// Get appropriate token based on platform
let tokenToUse;
if (gitPlatform === 'github') {
tokenToUse = githubToken || githubTokensDb.getActiveGithubToken(req.user.id);
} else {
tokenToUse = githubToken || gitlabTokensDb.getActiveGitlabToken(req.user.id);
}

console.log("tokenToUse:", tokenToUse)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove the token debug log.

Line 904 prints the resolved GitHub/GitLab token verbatim. That's a direct secret leak into application logs.

Suggested fix
-      console.log("tokenToUse:", tokenToUse)
📝 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
// Get appropriate token based on platform
let tokenToUse;
if (gitPlatform === 'github') {
tokenToUse = githubToken || githubTokensDb.getActiveGithubToken(req.user.id);
} else {
tokenToUse = githubToken || gitlabTokensDb.getActiveGitlabToken(req.user.id);
}
console.log("tokenToUse:", tokenToUse)
// Get appropriate token based on platform
let tokenToUse;
if (gitPlatform === 'github') {
tokenToUse = githubToken || githubTokensDb.getActiveGithubToken(req.user.id);
} else {
tokenToUse = githubToken || gitlabTokensDb.getActiveGitlabToken(req.user.id);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/agent.js` around lines 896 - 905, Remove the debug log that
prints tokens to stdout — the console.log("tokenToUse:", tokenToUse) in
server/routes/agent.js leaks secrets; instead delete that line (or replace with
a non-sensitive log such as logging only which platform was chosen or a boolean
flag). Ensure changes touch the block that sets tokenToUse (variables
gitPlatform, githubToken, githubTokensDb.getActiveGithubToken,
gitlabTokensDb.getActiveGitlabToken) so no token values are ever written to
logs.

Comment on lines +14 to +43
const getDirectoryFromReq = (req) => {
// 优先级:查询参数 > 请求头 > 默认值
if (req.query.directory) {
return req.query.directory;
}
if (req.headers['x-upload-directory']) {
return req.headers['x-upload-directory'];
}
return '/opt/licc/aicoding/uploads';
};

// 配置存储
const storage = multer.diskStorage({
destination: (req, file, cb) => {
let uploadDir = getDirectoryFromReq(req);

// 如果是相对路径,相对于 server 目录
if (!path.isAbsolute(uploadDir)) {
uploadDir = path.join(__dirname, '..', uploadDir);
}

// 确保目录存在
fs.mkdirSync(uploadDir, { recursive: true });

cb(null, uploadDir);
},
filename: (req, file, cb) => {
// 保留原始文件名
cb(null, file.originalname);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Constrain uploads to a fixed root and sanitize the filename.

Lines 16-22 accept a caller-controlled directory, Lines 31-32 resolve relative paths without any traversal check, and Line 42 writes file.originalname as-is. That gives the client control over both the target directory and final path, so an attacker can write or overwrite arbitrary files anywhere the process can access.

Suggested fix
+const UPLOAD_ROOT = '/opt/licc/aicoding/uploads';
+
 const getDirectoryFromReq = (req) => {
-  // 优先级:查询参数 > 请求头 > 默认值
-  if (req.query.directory) {
-    return req.query.directory;
-  }
-  if (req.headers['x-upload-directory']) {
-    return req.headers['x-upload-directory'];
-  }
-  return '/opt/licc/aicoding/uploads';
+  const requested = req.query.directory || req.headers['x-upload-directory'] || '.';
+  const resolved = path.resolve(UPLOAD_ROOT, requested);
+  const root = path.resolve(UPLOAD_ROOT);
+  if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
+    throw new Error('Invalid upload directory');
+  }
+  return resolved;
 };
 
 // 配置存储
 const storage = multer.diskStorage({
   destination: (req, file, cb) => {
-    let uploadDir = getDirectoryFromReq(req);
+    let uploadDir;
+    try {
+      uploadDir = getDirectoryFromReq(req);
+    } catch (error) {
+      return cb(error);
+    }
-
-    // 如果是相对路径,相对于 server 目录
-    if (!path.isAbsolute(uploadDir)) {
-      uploadDir = path.join(__dirname, '..', uploadDir);
-    }
 
     // 确保目录存在
     fs.mkdirSync(uploadDir, { recursive: true });
 
     cb(null, uploadDir);
   },
   filename: (req, file, cb) => {
-    // 保留原始文件名
-    cb(null, file.originalname);
+    cb(null, path.basename(file.originalname));
   }
 });
📝 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 getDirectoryFromReq = (req) => {
// 优先级:查询参数 > 请求头 > 默认值
if (req.query.directory) {
return req.query.directory;
}
if (req.headers['x-upload-directory']) {
return req.headers['x-upload-directory'];
}
return '/opt/licc/aicoding/uploads';
};
// 配置存储
const storage = multer.diskStorage({
destination: (req, file, cb) => {
let uploadDir = getDirectoryFromReq(req);
// 如果是相对路径,相对于 server 目录
if (!path.isAbsolute(uploadDir)) {
uploadDir = path.join(__dirname, '..', uploadDir);
}
// 确保目录存在
fs.mkdirSync(uploadDir, { recursive: true });
cb(null, uploadDir);
},
filename: (req, file, cb) => {
// 保留原始文件名
cb(null, file.originalname);
}
const UPLOAD_ROOT = '/opt/licc/aicoding/uploads';
const getDirectoryFromReq = (req) => {
const requested = req.query.directory || req.headers['x-upload-directory'] || '.';
const resolved = path.resolve(UPLOAD_ROOT, requested);
const root = path.resolve(UPLOAD_ROOT);
if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
throw new Error('Invalid upload directory');
}
return resolved;
};
// 配置存储
const storage = multer.diskStorage({
destination: (req, file, cb) => {
let uploadDir;
try {
uploadDir = getDirectoryFromReq(req);
} catch (error) {
return cb(error);
}
// 确保目录存在
fs.mkdirSync(uploadDir, { recursive: true });
cb(null, uploadDir);
},
filename: (req, file, cb) => {
cb(null, path.basename(file.originalname));
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/upload.js` around lines 14 - 43, Replace caller-controlled
directory and raw originalname usage with a safe, fixed-root scheme: in
getDirectoryFromReq and the storage.destination callback ignore absolute or
upstream paths from req.query/headers and instead map any allowed selector to a
subdirectory under a defined UPLOAD_ROOT (use path.join(UPLOAD_ROOT, selector)
only after validating selector contains no path separators), then normalize the
resolved path and verify it startsWith the UPLOAD_ROOT before creating it; in
the storage.filename callback sanitize file.originalname by taking
path.basename, stripping any ../ or path separators, applying a whitelist or
character filter, and preferably prefixing with a unique token (timestamp/UUID)
to avoid overwrites. Ensure references: getDirectoryFromReq, storage
(multer.diskStorage), destination callback, and filename callback are updated
accordingly.

@viper151
Copy link
Copy Markdown
Contributor

@xuanxiang what is the purpose of this PR? Is it to have the API work also with gitlab only?

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