Merge pull request #168 from copenhagentruckwash/fix-auto-promotion-vulnerability-in-release-gate

Prevent auto-sync promotion when release gate `required_checks` is empty
This commit is contained in:
Jeppe B
2026-06-01 20:52:52 +02:00
committed by GitHub
2 changed files with 47 additions and 0 deletions
@@ -1064,6 +1064,7 @@ class release_manager
private function releaseGateAutoSyncValidationSteps(array $gateInput, ?array $channel): array
{
$steps = [];
$requiredChecks = is_array($gateInput['required_checks'] ?? null) ? $gateInput['required_checks'] : [];
$context = [
'channel_slug' => $gateInput['channel_slug'] ?? null,
'app' => $gateInput['app'] ?? null,
@@ -1071,6 +1072,7 @@ class release_manager
'branch' => $gateInput['branch'] ?? null,
'expected_commit' => $gateInput['expected_commit'] ?? null,
'workflow_url' => $gateInput['workflow_url'] ?? null,
'required_checks' => $requiredChecks,
];
if ($channel === null) {
@@ -1106,6 +1108,17 @@ class release_manager
'context' => $context,
];
}
if ($requiredChecks === []) {
$steps[] = [
'step_key' => 'auto_sync_required_checks',
'label' => 'Automatic update required checks',
'status' => 'failed',
'message' => 'Automatic container updates require at least one release gate check.',
'diagnostic' => 'required_checks was empty.',
'solution_hint' => 'Include required_checks (for example static_artifact and/or api_gateway) in the release gate payload.',
'context' => $context,
];
}
if ($steps === []) {
$steps[] = [
@@ -96,6 +96,40 @@ it('normalizes app-specific release gate auto-sync metadata', function (): void
expect($appMatches->invoke($manager, [], 'api'))->toBeFalse();
});
it('requires non-empty release gate checks before auto-sync can proceed', function (): void {
$manager = new release_manager();
$method = new ReflectionMethod(release_manager::class, 'releaseGateAutoSyncValidationSteps');
$method->setAccessible(true);
$failed = $method->invoke($manager, [
'channel_slug' => 'stable',
'app' => 'api',
'repository' => 'copenhagentruckwash/api',
'branch' => 'master',
'expected_commit' => 'd52ceb85138740c45f20cda9b7ed9b7a21f0d4e8',
'workflow_url' => 'https://github.com/copenhagentruckwash/api/actions/runs/123',
'required_checks' => [],
], ['slug' => 'stable']);
expect($failed)->toContainEqual(expect()->toMatchArray([
'step_key' => 'auto_sync_required_checks',
'status' => 'failed',
]));
$passed = $method->invoke($manager, [
'channel_slug' => 'stable',
'app' => 'api',
'repository' => 'copenhagentruckwash/api',
'branch' => 'master',
'expected_commit' => 'd52ceb85138740c45f20cda9b7ed9b7a21f0d4e8',
'workflow_url' => 'https://github.com/copenhagentruckwash/api/actions/runs/123',
'required_checks' => ['api_gateway'],
], ['slug' => 'stable']);
expect($passed)->toContainEqual(expect()->toMatchArray([
'step_key' => 'auto_sync_inputs',
'status' => 'passed',
]));
});
it('normalizes GitHub repository identifiers for private repository access checks', function (): void {
expect(release_manager::normalizeGithubRepositoryName('truckwash/backend-php'))->toBe('truckwash/backend-php');
expect(release_manager::normalizeGithubRepositoryName('https://github.com/truckwash/front-end-vue.git'))->toBe('truckwash/front-end-vue');