@zereight/mcp-gitlab 2.1.45 → 2.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +4 -1
- package/README.md +135 -129
- package/README.zh-CN.md +4 -1
- package/build/config.js +1 -1
- package/build/index.js +87 -122
- package/build/oauth-proxy.js +151 -87
- package/build/schemas.js +20 -2
- package/build/server/request-helpers.js +63 -0
- package/build/server/version.js +28 -0
- package/build/stateless/consumed-proxy-code-cache.js +82 -0
- package/build/stateless/index.js +1 -0
- package/build/stateless/stored-tokens.js +4 -2
- package/build/test/mcp-oauth-tests.js +96 -0
- package/build/test/nullable-gitlab-response-fields.test.js +16 -1
- package/build/test/server/request-helpers.test.js +33 -0
- package/build/test/server/version.test.js +13 -0
- package/build/test/stateless/callback-proxy.test.js +109 -0
- package/build/test/stateless/consumed-proxy-code-cache.test.js +150 -0
- package/build/test/test-download-attachment.js +27 -0
- package/build/test/test-get-file-contents.js +128 -0
- package/build/test/test-job-artifacts.js +27 -2
- package/build/test/test-list-group-members.js +150 -0
- package/build/test/test-merge-request-approval-state-tools.js +42 -0
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/test/test-upload-markdown.js +38 -7
- package/build/test/utils/helpers-path.test.js +104 -0
- package/build/tools/registry.js +8 -1
- package/build/utils/helpers.js +205 -0
- package/build/utils/logger.js +4 -0
- package/package.json +2 -2
package/build/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
2
|
import { toJSONSchema } from "../utils/schema.js";
|
|
3
3
|
import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, SSE, STREAMABLE_HTTP, } from "../config.js";
|
|
4
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupMilestonesSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, } from "../schemas.js";
|
|
4
|
+
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupMilestonesSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListGroupMembersSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, } from "../schemas.js";
|
|
5
5
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
6
6
|
// Define all available tools
|
|
7
7
|
export const allTools = [
|
|
@@ -449,6 +449,11 @@ export const allTools = [
|
|
|
449
449
|
description: "List members of a GitLab project",
|
|
450
450
|
inputSchema: toJSONSchema(ListProjectMembersSchema),
|
|
451
451
|
},
|
|
452
|
+
{
|
|
453
|
+
name: "list_group_members",
|
|
454
|
+
description: "List members of a GitLab group with optional name or username search",
|
|
455
|
+
inputSchema: toJSONSchema(ListGroupMembersSchema),
|
|
456
|
+
},
|
|
452
457
|
{
|
|
453
458
|
name: "list_labels",
|
|
454
459
|
description: "List labels for a project",
|
|
@@ -1161,6 +1166,7 @@ export const readOnlyTools = new Set([
|
|
|
1161
1166
|
"get_project",
|
|
1162
1167
|
"list_projects",
|
|
1163
1168
|
"list_project_members",
|
|
1169
|
+
"list_group_members",
|
|
1164
1170
|
"get_pipeline",
|
|
1165
1171
|
"list_pipelines",
|
|
1166
1172
|
"list_deployments",
|
|
@@ -1477,6 +1483,7 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1477
1483
|
"list_projects",
|
|
1478
1484
|
"update_project",
|
|
1479
1485
|
"list_project_members",
|
|
1486
|
+
"list_group_members",
|
|
1480
1487
|
"list_namespaces",
|
|
1481
1488
|
"get_namespace",
|
|
1482
1489
|
"verify_namespace",
|
package/build/utils/helpers.js
CHANGED
|
@@ -1,3 +1,208 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Reject absolute paths and directory-traversal sequences for user-supplied
|
|
5
|
+
* local filesystem paths (e.g. local_path / file_path tool arguments).
|
|
6
|
+
*
|
|
7
|
+
* Returns the normalized relative path on success. This is a lexical check
|
|
8
|
+
* only — callers that touch the filesystem must also use
|
|
9
|
+
* {@link resolveSafeExistingPath}, {@link resolveSafeOutputDir}, or
|
|
10
|
+
* {@link resolveSafeOutputFile}.
|
|
11
|
+
*/
|
|
12
|
+
export function assertSafeRelativePath(inputPath, label = "path") {
|
|
13
|
+
const normalized = path.normalize(inputPath);
|
|
14
|
+
if (path.isAbsolute(normalized) ||
|
|
15
|
+
normalized === ".." ||
|
|
16
|
+
normalized.startsWith(".." + path.sep) ||
|
|
17
|
+
normalized.includes(path.sep + ".." + path.sep)) {
|
|
18
|
+
throw new Error(`Invalid ${label}: directory traversal is not allowed.`);
|
|
19
|
+
}
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
/** Snapshot and canonicalize the trusted base directory once per operation. */
|
|
23
|
+
function resolveTrustedBaseDir(baseDir) {
|
|
24
|
+
const anchor = baseDir ?? process.cwd();
|
|
25
|
+
return fs.realpathSync(anchor);
|
|
26
|
+
}
|
|
27
|
+
function isInsideBase(candidate, baseReal) {
|
|
28
|
+
const relative = path.relative(baseReal, candidate);
|
|
29
|
+
// "" = same path; absolute relative = different Windows volume root.
|
|
30
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Walk each path component under baseReal and reject symbolic links so a
|
|
34
|
+
* repository-controlled symlink cannot escape the trusted base directory.
|
|
35
|
+
*/
|
|
36
|
+
function assertNoSymlinkComponents(baseReal, relative, label) {
|
|
37
|
+
const parts = relative.split(path.sep).filter(part => part.length > 0 && part !== ".");
|
|
38
|
+
let current = baseReal;
|
|
39
|
+
for (const part of parts) {
|
|
40
|
+
const next = path.join(current, part);
|
|
41
|
+
let st;
|
|
42
|
+
try {
|
|
43
|
+
st = fs.lstatSync(next);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const code = err.code;
|
|
47
|
+
if (code === "ENOENT") {
|
|
48
|
+
// Remaining components do not exist yet (typical for mkdir output dirs).
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
if (st.isSymbolicLink()) {
|
|
54
|
+
throw new Error(`Invalid ${label}: symbolic links are not allowed.`);
|
|
55
|
+
}
|
|
56
|
+
current = next;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a user-supplied relative path to an existing file/dir that is
|
|
61
|
+
* guaranteed to stay inside `baseDir` after symlink resolution.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveSafeExistingPath(inputPath, label = "path", baseDir) {
|
|
64
|
+
const relative = assertSafeRelativePath(inputPath, label);
|
|
65
|
+
const baseReal = resolveTrustedBaseDir(baseDir);
|
|
66
|
+
assertNoSymlinkComponents(baseReal, relative, label);
|
|
67
|
+
const absolute = path.resolve(baseReal, relative);
|
|
68
|
+
if (!isInsideBase(absolute, baseReal)) {
|
|
69
|
+
throw new Error(`Invalid ${label}: path escapes the allowed directory.`);
|
|
70
|
+
}
|
|
71
|
+
if (!fs.existsSync(absolute)) {
|
|
72
|
+
throw new Error(`File not found: ${relative}`);
|
|
73
|
+
}
|
|
74
|
+
const real = fs.realpathSync(absolute);
|
|
75
|
+
if (!isInsideBase(real, baseReal)) {
|
|
76
|
+
throw new Error(`Invalid ${label}: path escapes the allowed directory.`);
|
|
77
|
+
}
|
|
78
|
+
return real;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a user-supplied relative output directory under `baseDir`, creating
|
|
82
|
+
* it if needed. Rejects symlink components and verifies the final real path
|
|
83
|
+
* remains inside the base.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveSafeOutputDir(inputDir, label = "local_path", baseDir) {
|
|
86
|
+
const relative = assertSafeRelativePath(inputDir, label);
|
|
87
|
+
const baseReal = resolveTrustedBaseDir(baseDir);
|
|
88
|
+
assertNoSymlinkComponents(baseReal, relative, label);
|
|
89
|
+
const absolute = path.resolve(baseReal, relative);
|
|
90
|
+
// Reject escapes before mkdir so we never create dirs outside the base.
|
|
91
|
+
if (!isInsideBase(absolute, baseReal)) {
|
|
92
|
+
throw new Error(`Invalid ${label}: path escapes the allowed directory.`);
|
|
93
|
+
}
|
|
94
|
+
fs.mkdirSync(absolute, { recursive: true });
|
|
95
|
+
const real = fs.realpathSync(absolute);
|
|
96
|
+
if (!isInsideBase(real, baseReal)) {
|
|
97
|
+
throw new Error(`Invalid ${label}: path escapes the allowed directory.`);
|
|
98
|
+
}
|
|
99
|
+
return real;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build a write destination under a trusted directory. Rejects an existing
|
|
103
|
+
* destination symlink so createWriteStream cannot follow it outside the base.
|
|
104
|
+
*
|
|
105
|
+
* When `inputDir` is omitted, the file is resolved under `baseDir` itself
|
|
106
|
+
* (still rejecting symlink destinations).
|
|
107
|
+
*/
|
|
108
|
+
export function resolveSafeOutputFile(filename, inputDir, label = "local_path", baseDir) {
|
|
109
|
+
if (!filename ||
|
|
110
|
+
filename === "." ||
|
|
111
|
+
filename === ".." ||
|
|
112
|
+
filename.includes("/") ||
|
|
113
|
+
filename.includes("\\") ||
|
|
114
|
+
filename.includes(path.sep)) {
|
|
115
|
+
throw new Error(`Invalid ${label}: directory separators are not allowed in filename.`);
|
|
116
|
+
}
|
|
117
|
+
const baseReal = resolveTrustedBaseDir(baseDir);
|
|
118
|
+
const dirReal = inputDir ? resolveSafeOutputDir(inputDir, label, baseReal) : baseReal;
|
|
119
|
+
const dest = path.join(dirReal, filename);
|
|
120
|
+
try {
|
|
121
|
+
const st = fs.lstatSync(dest);
|
|
122
|
+
if (st.isSymbolicLink()) {
|
|
123
|
+
throw new Error(`Invalid ${label}: symbolic links are not allowed.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
const code = err.code;
|
|
128
|
+
if (code !== "ENOENT") {
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return dest;
|
|
133
|
+
}
|
|
134
|
+
const READ_OPEN_FLAGS = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0);
|
|
135
|
+
const WRITE_OPEN_FLAGS = fs.constants.O_WRONLY |
|
|
136
|
+
fs.constants.O_CREAT |
|
|
137
|
+
fs.constants.O_TRUNC |
|
|
138
|
+
(fs.constants.O_NOFOLLOW ?? 0);
|
|
139
|
+
/**
|
|
140
|
+
* Validate and read a file in one step so callers never reuse a path string
|
|
141
|
+
* after a symlink swap (final-component TOCTOU mitigation via O_NOFOLLOW).
|
|
142
|
+
*/
|
|
143
|
+
export function readSafeExistingFile(inputPath, label = "path", baseDir) {
|
|
144
|
+
const relative = assertSafeRelativePath(inputPath, label);
|
|
145
|
+
const baseReal = resolveTrustedBaseDir(baseDir);
|
|
146
|
+
assertNoSymlinkComponents(baseReal, relative, label);
|
|
147
|
+
const absolute = path.resolve(baseReal, relative);
|
|
148
|
+
if (!isInsideBase(absolute, baseReal)) {
|
|
149
|
+
throw new Error(`Invalid ${label}: path escapes the allowed directory.`);
|
|
150
|
+
}
|
|
151
|
+
if (!fs.existsSync(absolute)) {
|
|
152
|
+
throw new Error(`File not found: ${relative}`);
|
|
153
|
+
}
|
|
154
|
+
const fd = fs.openSync(absolute, READ_OPEN_FLAGS);
|
|
155
|
+
try {
|
|
156
|
+
const st = fs.fstatSync(fd);
|
|
157
|
+
if (st.isSymbolicLink()) {
|
|
158
|
+
throw new Error(`Invalid ${label}: symbolic links are not allowed.`);
|
|
159
|
+
}
|
|
160
|
+
const real = fs.realpathSync(absolute);
|
|
161
|
+
if (!isInsideBase(real, baseReal)) {
|
|
162
|
+
throw new Error(`Invalid ${label}: path escapes the allowed directory.`);
|
|
163
|
+
}
|
|
164
|
+
return { buffer: fs.readFileSync(fd), basename: path.basename(relative) };
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
fs.closeSync(fd);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate an output destination and open a write stream immediately so the
|
|
172
|
+
* path is not handed back for a separate createWriteStream call.
|
|
173
|
+
*/
|
|
174
|
+
export function openSafeOutputWriteStream(filename, inputDir, label = "local_path", baseDir) {
|
|
175
|
+
if (!filename ||
|
|
176
|
+
filename === "." ||
|
|
177
|
+
filename === ".." ||
|
|
178
|
+
filename.includes("/") ||
|
|
179
|
+
filename.includes("\\") ||
|
|
180
|
+
filename.includes(path.sep)) {
|
|
181
|
+
throw new Error(`Invalid ${label}: directory separators are not allowed in filename.`);
|
|
182
|
+
}
|
|
183
|
+
const dirReal = inputDir
|
|
184
|
+
? resolveSafeOutputDir(inputDir, label, baseDir)
|
|
185
|
+
: resolveTrustedBaseDir(baseDir);
|
|
186
|
+
const dest = path.join(dirReal, filename);
|
|
187
|
+
try {
|
|
188
|
+
const st = fs.lstatSync(dest);
|
|
189
|
+
if (st.isSymbolicLink()) {
|
|
190
|
+
throw new Error(`Invalid ${label}: symbolic links are not allowed.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const code = err.code;
|
|
195
|
+
if (code !== "ENOENT") {
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const fd = fs.openSync(dest, WRITE_OPEN_FLAGS, 0o666);
|
|
200
|
+
const stream = fs.createWriteStream(null, {
|
|
201
|
+
fd,
|
|
202
|
+
autoClose: true,
|
|
203
|
+
});
|
|
204
|
+
return { stream, path: dest };
|
|
205
|
+
}
|
|
1
206
|
/**
|
|
2
207
|
* Estimate the number of merge commits that will be added based on the merge method.
|
|
3
208
|
*/
|
package/build/utils/logger.js
CHANGED
|
@@ -18,6 +18,10 @@ const REDACT_PATHS = [
|
|
|
18
18
|
"*.sessionId",
|
|
19
19
|
"ctx.sessionId",
|
|
20
20
|
"context.sessionId",
|
|
21
|
+
"approval_password",
|
|
22
|
+
"*.approval_password",
|
|
23
|
+
"arguments.approval_password",
|
|
24
|
+
"params.arguments.approval_password",
|
|
21
25
|
];
|
|
22
26
|
/**
|
|
23
27
|
* Create a pino logger with consistent redaction, level, format, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.47",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test": "npm run test:all",
|
|
54
54
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
55
55
|
"test:mock": "bash scripts/run-mock-tests.sh",
|
|
56
|
-
"test:stateless": "npm run build && node --import tsx/esm --test --experimental-test-isolation=none test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
56
|
+
"test:stateless": "npm run build && node --import tsx/esm --test --experimental-test-isolation=none test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts test/stateless/consumed-proxy-code-cache.test.ts",
|
|
57
57
|
"test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
|
|
58
58
|
"test:live": "node test/validate-api.js",
|
|
59
59
|
"test:consumer-smoke": "bash scripts/consumer-install-smoke.sh",
|