fix: support generic SSH Git usernames

This commit is contained in:
Florian Pfitzer 2026-09-08 10:08:36 +02:00 committed by Andras Bacsai
parent 1e8a27b084
commit e9bf2551ed
5 changed files with 39 additions and 9 deletions

View file

@ -79,9 +79,9 @@ protected function canonicalManualWebhookRepository(?string $gitRepository): ?st
if (is_array($parts) && isset($parts['scheme'])) {
$path = data_get($parts, 'path');
} elseif (Str::startsWith($gitRepository, 'git@') && str_contains($gitRepository, ':')) {
} elseif (preg_match('/^[A-Za-z0-9._-]+@[^:]+:/', $gitRepository) === 1) {
$path = Str::after($gitRepository, ':');
// scp-style SSH URLs embed a custom port as "git@host:2222/owner/repo".
// scp-style SSH URLs embed a custom port as "user@host:2222/owner/repo".
// Strip the leading numeric port segment so the path matches the webhook
// payload's owner/repo, consistent with convertGitUrl() in shared.php.
$path = preg_replace('#^\d+/#', '', $path) ?? $path;

View file

@ -137,10 +137,8 @@ public function loadBranch()
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url'));
}
if (str($this->repository_url)->startsWith('git@')) {
$github_instance = str($this->repository_url)->after('git@')->before(':');
$repository = str($this->repository_url)->after(':')->before('.git');
$this->repository_url = 'https://'.str($github_instance).'/'.$repository;
if (preg_match('/^(?<user>[A-Za-z0-9._-]+)@(?<host>[^:]+):(?<repository>.+)$/', $this->repository_url, $matches) === 1) {
$this->repository_url = 'https://'.$matches['host'].'/'.$matches['repository'];
}
if (
(str($this->repository_url)->startsWith('https://') ||

View file

@ -77,15 +77,15 @@ public function validate(string $attribute, mixed $value, Closure $fail): void
}
// Validate based on URL type
if (str_starts_with($value, 'git@')) {
if (preg_match('/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+:/', $value)) {
if (! $this->allowSSH) {
$fail('SSH URLs are not allowed.');
return;
}
// Validate SSH URL format (git@host:user/repo.git)
if (! preg_match('/^git@[a-zA-Z0-9\.\-]+:[a-zA-Z0-9\-_\/\.~]+$/', $value)) {
// Validate scp-style SSH URL format (user@host:user/repo.git)
if (! preg_match('/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+:[a-zA-Z0-9\-_\/.~]+$/', $value)) {
$fail('The :attribute is not a valid SSH repository URL.');
return;

View file

@ -542,6 +542,29 @@ function createApplicationWithWebhook(string $repo = 'test-org/test-repo', strin
expect($response->getContent())->not->toContain('No applications found');
});
test('github matches an ssh repository URL with a non-git username', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'custom-user@git.example.com:test-org/test-repo.git',
]);
$secret = $app->manual_webhook_secret_github;
$payload = json_encode([
'ref' => 'refs/heads/main',
'repository' => ['full_name' => 'test-org/test-repo'],
'after' => 'abc123',
'commits' => [],
]);
$response = $this->call('POST', '/webhooks/source/github/events/manual', [], [], [], [
'HTTP_X-GitHub-Event' => 'push',
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, $secret),
'CONTENT_TYPE' => 'application/json',
], $payload);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
test('gitlab matches scp-style ssh repository URL with custom port', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'git@gitlab.example.com:2222/services/xyz.git',

View file

@ -107,6 +107,7 @@
'git@github.com:user/repo.git',
'git@gitlab.com:user/repo.git',
'git@bitbucket.org:user/repo.git',
'custom-user@git.example.com:organization/repository.git',
];
foreach ($validUrls as $url) {
@ -115,6 +116,14 @@
}
});
it('rejects non-SSH email-like repository URLs', function () {
$rule = new ValidGitRepositoryUrl;
$validator = Validator::make(['url' => 'custom-user@git.example.com'], ['url' => $rule]);
expect($validator->fails())->toBeTrue();
});
it('rejects SSH URLs when not allowed', function () {
$rule = new ValidGitRepositoryUrl(allowSSH: false);