-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathutils.test.ts
More file actions
90 lines (81 loc) · 3.58 KB
/
Copy pathutils.test.ts
File metadata and controls
90 lines (81 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import {
getGitLabApiBase,
getGitLabResourcePath,
normalizeGitLabHost,
UnsafeGitLabHostError,
} from '@/tools/gitlab/utils'
describe('normalizeGitLabHost', () => {
it('defaults to gitlab.com when the host is empty, blank, or not a string', () => {
expect(normalizeGitLabHost(undefined)).toBe('gitlab.com')
expect(normalizeGitLabHost(null)).toBe('gitlab.com')
expect(normalizeGitLabHost('')).toBe('gitlab.com')
expect(normalizeGitLabHost(' ')).toBe('gitlab.com')
expect(normalizeGitLabHost(42)).toBe('gitlab.com')
})
it('strips protocol and trailing slashes from a self-managed host', () => {
expect(normalizeGitLabHost('gitlab.example.com')).toBe('gitlab.example.com')
expect(normalizeGitLabHost('https://gitlab.example.com')).toBe('gitlab.example.com')
expect(normalizeGitLabHost('http://gitlab.example.com/')).toBe('gitlab.example.com')
expect(normalizeGitLabHost(' https://gitlab.example.com// ')).toBe('gitlab.example.com')
})
it('preserves an explicit port and IDN punycode labels', () => {
expect(normalizeGitLabHost('gitlab.example.com:8443')).toBe('gitlab.example.com:8443')
expect(normalizeGitLabHost('xn--80ak6aa92e.com')).toBe('xn--80ak6aa92e.com')
})
it('rejects hosts that could redirect the request authority (SSRF / token exfiltration)', () => {
const unsafe = [
'legit.com@evil.com',
'user:pass@evil.com',
'gitlab.com#@evil.com',
'gitlab.com /api',
'line\nbreak.com',
'evil.com/path',
'evil.com?x=1',
'[::1]',
'a..b.com',
'.gitlab.com',
'gitlab.com.',
]
for (const host of unsafe) {
expect(() => normalizeGitLabHost(host), host).toThrow(UnsafeGitLabHostError)
}
})
it('accepts bare IP literals at the STRUCTURAL layer by design (private/metadata IPs are rejected later by the fetch-layer DNS guard)', () => {
// This guard is structural only — it prevents authority confusion (userinfo,
// path, whitespace). SSRF to private/loopback/metadata addresses is the
// responsibility of validateUrlWithDNS / secureFetchWithValidation at fetch
// time, the single SSRF chokepoint shared by tools, webhooks, and connectors.
// These hosts are therefore structurally valid here, then blocked at fetch.
expect(normalizeGitLabHost('127.0.0.1')).toBe('127.0.0.1')
expect(normalizeGitLabHost('169.254.169.254')).toBe('169.254.169.254')
expect(normalizeGitLabHost('localhost')).toBe('localhost')
})
})
describe('getGitLabApiBase', () => {
it('builds the v4 REST base for the default and self-managed hosts', () => {
expect(getGitLabApiBase(undefined)).toBe('https://gitlab.com/api/v4')
expect(getGitLabApiBase('gitlab.example.com')).toBe('https://gitlab.example.com/api/v4')
expect(getGitLabApiBase('https://gitlab.example.com:8443/')).toBe(
'https://gitlab.example.com:8443/api/v4'
)
})
it('propagates rejection of unsafe hosts', () => {
expect(() => getGitLabApiBase('legit.com@evil.com')).toThrow(UnsafeGitLabHostError)
})
})
describe('getGitLabResourcePath', () => {
it('builds project and group path segments', () => {
expect(getGitLabResourcePath('project', 42)).toBe('projects/42')
expect(getGitLabResourcePath('group', 7)).toBe('groups/7')
})
it('URL-encodes namespaced paths and trims whitespace', () => {
expect(getGitLabResourcePath('project', ' mygroup/myproject ')).toBe(
'projects/mygroup%2Fmyproject'
)
expect(getGitLabResourcePath('group', 'parent/child')).toBe('groups/parent%2Fchild')
})
})