@zereight/mcp-gitlab 2.1.64 → 2.1.66
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 +21 -5
- package/README.md +20 -6
- package/README.zh-CN.md +20 -6
- package/build/downloads/proxy.js +14 -7
- package/build/index.js +364 -244
- package/build/scripts/generate-tool-docs.js +3 -1
- package/build/test/path-segment-encoding.test.js +73 -17
- package/build/test/response-masking.test.js +33 -0
- package/build/test/sse-session-limits.test.js +273 -0
- package/build/test/test-download-attachment.js +11 -0
- package/build/test/test-geteffectiveprojectid.js +1 -1
- package/build/test/test-job-artifacts.js +22 -0
- package/build/test/test-permission-mode.js +103 -12
- package/build/test/test-remote-downloads.js +14 -1
- package/build/test/utils/graphql-query.test.js +72 -0
- package/build/test/utils/jmespath-tool-result.test.js +91 -0
- package/build/test/utils/safe-redirect-fetch.test.js +463 -0
- package/build/test/utils/tool-args.test.js +32 -0
- package/build/test/utils/url.test.js +128 -0
- package/build/test-note.js +2 -1
- package/build/test-resolve-issue-note.js +4 -3
- package/build/tools/registry.js +16 -2
- package/build/utils/graphql-query.js +31 -9
- package/build/utils/jmespath-tool-result.js +78 -0
- package/build/utils/safe-redirect-fetch.js +321 -0
- package/build/utils/tool-args.js +12 -3
- package/build/utils/url.js +102 -0
- package/package.json +3 -1
package/build/utils/url.js
CHANGED
|
@@ -17,3 +17,105 @@ export function normalizeGitLabApiUrl(url) {
|
|
|
17
17
|
}
|
|
18
18
|
return normalizedUrl;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Dot segments anywhere in a decoded value, including after a percent-encoded
|
|
22
|
+
* separator ("..%2F..%2Fuser" decodes to a path-traversal sequence).
|
|
23
|
+
*/
|
|
24
|
+
const DOT_SEGMENT_PATTERN = /(^|[\\/])\.\.?([\\/]|$)/;
|
|
25
|
+
/** A decoded value that starts with a path separator (absolute-path payload). */
|
|
26
|
+
const LEADING_SEPARATOR_PATTERN = /^[\\/]/;
|
|
27
|
+
/** NUL, newline and the other control characters: never valid in a path segment. */
|
|
28
|
+
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
29
|
+
/**
|
|
30
|
+
* How many times a value is percent-decoded before the checks run. A payload can be
|
|
31
|
+
* encoded more than once ("%252E%252E%252Fuser" only becomes "../user" after two
|
|
32
|
+
* decodes), so the checks run on the form the receiving server may eventually see.
|
|
33
|
+
*/
|
|
34
|
+
const MAX_DECODE_PASSES = 5;
|
|
35
|
+
const INVALID_SEGMENT_MESSAGE = "Cannot use value as a GitLab URL path segment: it is or decodes to a '.'/'..' path segment, " +
|
|
36
|
+
"a control character such as NUL, or an absolute path";
|
|
37
|
+
const INVALID_PATH_MESSAGE = "Cannot use value as a GitLab URL path: it is empty or it has an empty, leading or trailing segment";
|
|
38
|
+
/**
|
|
39
|
+
* A percent escape that actually decodes to a byte: `%` followed by two hex digits.
|
|
40
|
+
*
|
|
41
|
+
* `decodeURIComponent` also throws for a `%` that starts no escape, and a literal
|
|
42
|
+
* percent sign is valid in a file or branch name ("report-100%.pdf"), so the two
|
|
43
|
+
* failures have to be told apart before either is treated as a broken payload.
|
|
44
|
+
*/
|
|
45
|
+
const VALID_ESCAPE_PATTERN = /%[0-9a-fA-F]{2}/;
|
|
46
|
+
/**
|
|
47
|
+
* Percent-decode a value until decoding stops changing it.
|
|
48
|
+
*
|
|
49
|
+
* Returns null when the value cannot be reduced to a form that can be validated:
|
|
50
|
+
* a malformed escape next to a valid one, or more encoding layers than
|
|
51
|
+
* {@link MAX_DECODE_PASSES}. A guard must not treat "could not inspect the value"
|
|
52
|
+
* as "safe", so both cases are rejected by the callers rather than passed through.
|
|
53
|
+
*
|
|
54
|
+
* A `%` that starts no valid escape is a literal character instead: the value is
|
|
55
|
+
* returned as it is and the encoder turns the sign into `%25`, so
|
|
56
|
+
* `get_file_contents("report-100%.pdf")` keeps working. Only a value that mixes a
|
|
57
|
+
* real escape with a broken one cannot be inspected, and that form is rejected.
|
|
58
|
+
*/
|
|
59
|
+
function decodeFully(value) {
|
|
60
|
+
let current = value;
|
|
61
|
+
for (let pass = 0; pass < MAX_DECODE_PASSES; pass++) {
|
|
62
|
+
let decoded;
|
|
63
|
+
try {
|
|
64
|
+
decoded = decodeURIComponent(current);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Undecodable. With a valid escape left in it the value cannot be inspected
|
|
68
|
+
// reliably (the escape may still hide a separator or a dot segment), so it
|
|
69
|
+
// fails closed; without one every `%` is literal and `current` — already
|
|
70
|
+
// decoded as far as it can be — is the form the server will see.
|
|
71
|
+
return VALID_ESCAPE_PATTERN.test(current) ? null : current;
|
|
72
|
+
}
|
|
73
|
+
if (decoded === current) {
|
|
74
|
+
return current;
|
|
75
|
+
}
|
|
76
|
+
current = decoded;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Encode a single value for use as one GitLab URL path segment.
|
|
82
|
+
*
|
|
83
|
+
* Rejects values that are, decode to, or start with dot segments or an absolute
|
|
84
|
+
* path, so a caller-supplied id cannot escape the intended route prefix.
|
|
85
|
+
* Percent-encoded separators are decoded before the check because the receiving
|
|
86
|
+
* server may treat them as separators; a value may be encoded any number of times
|
|
87
|
+
* and is still validated in its fully decoded form.
|
|
88
|
+
*
|
|
89
|
+
* @throws {Error} when the value is a dot segment, contains one, contains a control
|
|
90
|
+
* character, starts with a separator, or is not valid percent-encoding
|
|
91
|
+
*/
|
|
92
|
+
export function encodeGitLabPathSegment(value) {
|
|
93
|
+
const segment = String(value);
|
|
94
|
+
const decodedSegment = decodeFully(segment);
|
|
95
|
+
if (decodedSegment === null ||
|
|
96
|
+
DOT_SEGMENT_PATTERN.test(decodedSegment) ||
|
|
97
|
+
LEADING_SEPARATOR_PATTERN.test(decodedSegment) ||
|
|
98
|
+
CONTROL_CHARACTER_PATTERN.test(decodedSegment)) {
|
|
99
|
+
throw new Error(INVALID_SEGMENT_MESSAGE);
|
|
100
|
+
}
|
|
101
|
+
return encodeURIComponent(decodedSegment);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Encode a slash-separated path so each segment is encoded and validated
|
|
105
|
+
* individually. Legitimate encoded separators (for example a tag such as
|
|
106
|
+
* "release%2F1.0") stay allowed; dot segments are rejected.
|
|
107
|
+
*
|
|
108
|
+
* An empty path, or one with an empty, leading or trailing segment, is rejected
|
|
109
|
+
* rather than silently collapsed: `"/etc/passwd"` would otherwise encode to a
|
|
110
|
+
* leading-separator path instead of the intended relative one.
|
|
111
|
+
*
|
|
112
|
+
* @throws {Error} when the path is empty or has an empty segment, or when any
|
|
113
|
+
* segment is a dot segment or contains one
|
|
114
|
+
*/
|
|
115
|
+
export function encodeGitLabPath(value) {
|
|
116
|
+
const segments = value.split("/");
|
|
117
|
+
if (segments.some(segment => segment === "")) {
|
|
118
|
+
throw new Error(INVALID_PATH_MESSAGE);
|
|
119
|
+
}
|
|
120
|
+
return segments.map(encodeGitLabPathSegment).join("/");
|
|
121
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.66",
|
|
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": [
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"express": "^5.1.0",
|
|
79
79
|
"express-rate-limit": "^8.5.2",
|
|
80
80
|
"fetch-cookie": "^3.1.0",
|
|
81
|
+
"jmespath": "^0.16.0",
|
|
81
82
|
"open": "^10.2.0",
|
|
82
83
|
"pino": "^9.7.0",
|
|
83
84
|
"pino-pretty": "^13.0.0",
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
},
|
|
91
92
|
"devDependencies": {
|
|
92
93
|
"@types/express": "^5.0.2",
|
|
94
|
+
"@types/jmespath": "^0.15.2",
|
|
93
95
|
"@types/node": "^22.13.10",
|
|
94
96
|
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
|
95
97
|
"@typescript-eslint/parser": "^8.21.0",
|