feat: add GitLab support and upload API#630
Conversation
📝 WalkthroughWalkthroughThis 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
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
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (8)
server/database/db.jsserver/git-platforms/constants.jsserver/git-platforms/github-platform.jsserver/git-platforms/gitlab-platform.jsserver/git-platforms/index.jsserver/index.jsserver/routes/agent.jsserver/routes/upload.js
| getActiveGitlabToken: (userId) => { | ||
| return credentialsDb.getActiveCredential(userId, 'github_token'); // todo 临时使用 github_token | ||
| }, |
There was a problem hiding this comment.
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.
| 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.
| // 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}`)); |
There was a problem hiding this comment.
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).
| 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}`)); |
There was a problem hiding this comment.
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.
| // Default to GITLAB | ||
| console.warn('⚠️ Could not detect platform, defaulting to GITLAB'); | ||
| return PLATFORMS.GITLAB; |
There was a problem hiding this comment.
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.
| // 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.
| // 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]; |
There was a problem hiding this comment.
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.
| // Upload API Routes (protected) | ||
| app.use('/api/upload', uploadRoutes); |
There was a problem hiding this comment.
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.
| // 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.
| // 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) | ||
|
|
There was a problem hiding this comment.
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.
| // 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.
| 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); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
|
@xuanxiang what is the purpose of this PR? Is it to have the API work also with gitlab only? |
Summary
This PR adds GitLab platform support and a new upload API endpoint:
GitLab Platform Support: Added GitLab as an alternative to GitHub, including:
Refactored Git Platform Architecture: Created a modular structure:
server/git-platforms/constants.js: Platform-specific constantsserver/git-platforms/github-platform.js: GitHub-specific implementationserver/git-platforms/gitlab-platform.js: GitLab-specific implementationserver/git-platforms/index.js: Unified platform interfaceUpload API: New
/uploadendpoint for handling file uploadsDatabase Enhancements: Added
hasUsersByUsernamemethod for user validationFiles Changed
server/database/db.js: Added user validation and GitLab token operationsserver/git-platforms/: New directory with platform abstraction layerserver/routes/agent.js: Updated to support multiple git platformsserver/routes/upload.js: New upload endpointserver/index.js: Registered upload routesCo-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com
Summary by CodeRabbit