fix: allow slash remote push targets (#4109)
This commit is contained in:
parent
053e172585
commit
e3c30712ca
|
|
@ -335,6 +335,22 @@ describe('git remote operations', () => {
|
|||
])
|
||||
})
|
||||
|
||||
it('fetches explicit publish target remotes whose names contain slashes', async () => {
|
||||
gitExecFileAsyncMock
|
||||
.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
||||
.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
||||
|
||||
await gitFetch('/repo', {
|
||||
remoteName: 'foo/bar',
|
||||
branchName: 'feature/fix'
|
||||
})
|
||||
|
||||
expect(gitExecFileAsyncMock.mock.calls).toEqual([
|
||||
[['check-ref-format', '--branch', 'feature/fix'], { cwd: '/repo' }],
|
||||
[['fetch', '--prune', 'foo/bar'], { cwd: '/repo' }]
|
||||
])
|
||||
})
|
||||
|
||||
it('normalizes fetch authentication errors to a friendly message', async () => {
|
||||
gitExecFileAsyncMock.mockRejectedValueOnce(new Error('Authentication failed'))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { assertGitPushTargetShape } from './git-push-target-validation'
|
||||
|
||||
describe('assertGitPushTargetShape', () => {
|
||||
it('accepts slash-separated git remote names', () => {
|
||||
expect(() =>
|
||||
assertGitPushTargetShape({ remoteName: 'foo/bar', branchName: 'feature/fix' })
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('rejects remote names with empty or parent segments', () => {
|
||||
expect(() =>
|
||||
assertGitPushTargetShape({ remoteName: 'foo//bar', branchName: 'feature/fix' })
|
||||
).toThrow('Invalid git remote name')
|
||||
expect(() =>
|
||||
assertGitPushTargetShape({ remoteName: 'foo/../bar', branchName: 'feature/fix' })
|
||||
).toThrow('Invalid git remote name')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { GitPushTarget } from './types'
|
||||
|
||||
const SAFE_REMOTE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/
|
||||
const SAFE_REMOTE_NAME_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
|
||||
const GITHUB_CLONE_URL = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\.git$/
|
||||
const GITHUB_SSH_URL = /^git@github\.com:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\.git$/
|
||||
|
||||
|
|
@ -10,6 +10,22 @@ function assertString(value: unknown, name: string): asserts value is string {
|
|||
}
|
||||
}
|
||||
|
||||
function isSafeRemoteName(remoteName: string): boolean {
|
||||
if (remoteName.length === 0 || remoteName.length > 100) {
|
||||
return false
|
||||
}
|
||||
return remoteName.split('/').every((segment) => {
|
||||
// Git accepts slash-separated remote names; each segment still needs to be
|
||||
// a concrete name so persisted push targets cannot smuggle path traversal.
|
||||
return (
|
||||
segment !== '' &&
|
||||
segment !== '.' &&
|
||||
segment !== '..' &&
|
||||
SAFE_REMOTE_NAME_SEGMENT.test(segment)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function assertGitPushTargetShape(target: unknown): asserts target is GitPushTarget {
|
||||
if (typeof target !== 'object' || target === null) {
|
||||
throw new Error('Invalid PR push target.')
|
||||
|
|
@ -17,11 +33,7 @@ export function assertGitPushTargetShape(target: unknown): asserts target is Git
|
|||
const candidate = target as Record<string, unknown>
|
||||
assertString(candidate.remoteName, 'remote name')
|
||||
assertString(candidate.branchName, 'branch name')
|
||||
if (
|
||||
!SAFE_REMOTE_NAME.test(candidate.remoteName) ||
|
||||
candidate.remoteName === '.' ||
|
||||
candidate.remoteName === '..'
|
||||
) {
|
||||
if (!isSafeRemoteName(candidate.remoteName)) {
|
||||
throw new Error(`Invalid git remote name: ${candidate.remoteName}`)
|
||||
}
|
||||
if (!candidate.branchName || candidate.branchName.startsWith('-')) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue