From 01055472f7cc19f0646d34592eb69b6ff0e4e439 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Fri, 4 Sep 2026 16:12:35 -0500 Subject: [PATCH 1/2] Add check for game capture lib signature * Verify game capture libraries have "OBS Project, LLC" signature --- .github/workflows/tests.yml | 32 ++++++++- scripts/ci/verify_game_signatures.ts | 100 +++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 scripts/ci/verify_game_signatures.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e9fc3d762f41..4152e8ca47aa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,13 +41,17 @@ jobs: - '!*.yml' - '!package.json' - '!yarn.lock' + deps: + - 'package.json' + - 'yarn.lock' outputs: has-testable-changes: ${{ steps.filter.outputs.src }} + has-dependency-changes: ${{ steps.filter.outputs.deps }} prepare-frontend-tests: name: 'Prepare Frontend Tests' needs: testable-changes - if: needs.testable-changes.outputs.has-testable-changes == 'true' || github.event_name == 'push' + if: needs.testable-changes.outputs.has-testable-changes == 'true' || needs.testable-changes.outputs.has-dependency-changes == 'true' || github.event_name == 'push' runs-on: windows-2022 timeout-minutes: 80 steps: @@ -79,7 +83,31 @@ jobs: path: ${{ runner.temp }}/frontend-test-build.7z compression-level: 0 retention-days: 1 + verify-game-capture-binary-signatures: + name: 'Verify Game Capture Binary Signatures' + needs: [testable-changes, prepare-frontend-tests] + if: needs.testable-changes.outputs.has-testable-changes == 'true' || needs.testable-changes.outputs.has-dependency-changes == 'true' || github.event_name == 'push' + runs-on: windows-2022 + steps: + - name: 'Download Test Build' + uses: actions/download-artifact@v4 + with: + name: frontend-test-build + path: ${{ runner.temp }}/frontend-test-artifact + - name: 'Prepare Test Runner' + run: | + $projectDirectory = Join-Path $env:RUNNER_TEMP 'frontend-test-project' + New-Item -ItemType Directory -Force -Path $projectDirectory | Out-Null + 7z x "$env:RUNNER_TEMP\frontend-test-artifact\frontend-test-build.7z" "-o$projectDirectory" -y -bb0 + Set-Location $projectDirectory + if (-not (Test-Path package.json)) { + throw "Test build does not contain package.json at $projectDirectory" + } + shell: powershell + - name: 'Run Game Capture Binary Signature Verification' + run: yarn ts-node scripts/ci/verify_game_signatures.ts + working-directory: ${{ runner.temp }}/frontend-test-project test: name: 'Frontend Tests' needs: prepare-frontend-tests @@ -161,7 +189,7 @@ jobs: if: always() # Note: every required job in this workflow must be accounted in the results collation # to work properly as a required check - needs: [testable-changes, prepare-frontend-tests, test, es-lint-strict-nulls] + needs: [testable-changes, prepare-frontend-tests, verify-game-capture-binary-signatures, test, es-lint-strict-nulls] runs-on: ubuntu-latest steps: - name: 'Check CI Results' diff --git a/scripts/ci/verify_game_signatures.ts b/scripts/ci/verify_game_signatures.ts new file mode 100644 index 000000000000..1d276f6deb31 --- /dev/null +++ b/scripts/ci/verify_game_signatures.ts @@ -0,0 +1,100 @@ +import * as path from 'path'; +import execa from 'execa'; +import { promises as fs } from 'fs'; + +if (process.platform !== 'win32') { + console.error('verify_game_signatures.ts can only be run on Windows runners.'); + process.exit(1); +} +// Expected Authenticode publisher (certificate simple name) of the game capture binaries. +const GAME_CAPTURE_PUBLISHER = 'OBS Project, LLC'; + +// List of the binaries needed for game capture +const gameCaptureDependencies = [ + 'get-graphics-offsets32.exe', + 'get-graphics-offsets64.exe', + 'graphics-hook32.dll', + 'graphics-hook64.dll', + 'inject-helper32.exe', + 'inject-helper64.exe', +]; + +// Verifies the Authenticode signature of Windows game capture binaries using PowerShell. +// Exits the process with code 1 if a binary is unsigned/tampered/untrusted or not published by 'OBS Project, LLC'. +async function verifyGameCaptureBinarySignatures(dir: string): Promise { + for (const bin of gameCaptureDependencies) { + const filePath = path.join(dir, 'data', 'obs-plugins', 'win-capture', bin); + try { + await fs.access(filePath); + } catch { + console.error(`Signature verification failed for ${bin}: file not found at ${filePath}`); + process.exit(1); + } + const escapedPath = filePath.replace(/'/g, "''"); + // The publisher is compared against the certificate's simple name rather than against the + // raw Subject DN: Windows quotes any RDN value containing a comma, so the DN reads + // CN="OBS Project, LLC", ... and a bare `CN=OBS Project, LLC` pattern never matches it. + // Exit codes: 1 = unsigned/tampered/untrusted, 2 = wrong publisher, 0 = valid. + const script = [ + "$ErrorActionPreference = 'Stop'", + `try { $sig = Get-AuthenticodeSignature -LiteralPath '${escapedPath}' } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 1 }`, + 'if ($null -eq $sig -or $null -eq $sig.SignerCertificate) { [Console]::Error.WriteLine("no signature"); exit 1 }', + "if ($sig.Status -ne 'Valid') { [Console]::Error.WriteLine(\"status=$($sig.Status): $($sig.StatusMessage)\"); exit 1 }", + "$cn = $sig.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false)", + `if ($cn -cne '${GAME_CAPTURE_PUBLISHER}') { [Console]::Error.WriteLine("publisher=$cn"); exit 2 }`, + 'exit 0', + ].join('; '); + const encodedCommand = Buffer.from(script, 'utf16le').toString('base64'); + + try { + await execa( + 'powershell', + ['-NonInteractive', '-NoProfile', '-EncodedCommand', encodedCommand], + { + stdio: 'pipe', + timeout: 30000, + }, + ); + } catch (e: unknown) { + const err = e as { exitCode?: number; timedOut?: boolean; stderr?: string }; + // powershell.exe wraps redirected stderr in a CLIXML envelope; drop it so the real + // message (status=..., publisher=...) is what gets logged. + const stderr = err.stderr || ''; + const clixmlDetail = stderr.match(/]*>([^<]*)<\/S>/)?.[1]; + const detail = + clixmlDetail || + stderr + .split(/\r?\n/) + .map(line => line.trim()) + .find( + line => + line && + !line.startsWith('#< CLIXML') && + !line.startsWith(' { + console.error('Signature verification failed:', err); + process.exit(1); +}); From 44873c510be27e5250ca7296945885ad2fb0e368 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Fri, 4 Sep 2026 20:43:56 -0500 Subject: [PATCH 2/2] Add test to verify HEVC encoder is present --- .github/workflows/tests.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4152e8ca47aa..9fb0765c4fb0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,7 +83,7 @@ jobs: path: ${{ runner.temp }}/frontend-test-build.7z compression-level: 0 retention-days: 1 - verify-game-capture-binary-signatures: + verify-binary: name: 'Verify Game Capture Binary Signatures' needs: [testable-changes, prepare-frontend-tests] if: needs.testable-changes.outputs.has-testable-changes == 'true' || needs.testable-changes.outputs.has-dependency-changes == 'true' || github.event_name == 'push' @@ -108,6 +108,16 @@ jobs: - name: 'Run Game Capture Binary Signature Verification' run: yarn ts-node scripts/ci/verify_game_signatures.ts working-directory: ${{ runner.temp }}/frontend-test-project + - name: 'Verify HEVC Encoder is present' + run: | + output=$(./ffmpeg.exe -hide_banner -encoders 2>/dev/null | grep hevc) + if [ -z "$output" ]; then + echo "HEVC encoder not found in ffmpeg encoders list. This may indicate that the HEVC encoder is missing or not properly configured." + exit 1 + fi + echo "HEVC encoder found: $output" + working-directory: ${{ runner.temp }}/frontend-test-project/node_modules/obs-studio-node + shell: bash test: name: 'Frontend Tests' needs: prepare-frontend-tests @@ -189,7 +199,7 @@ jobs: if: always() # Note: every required job in this workflow must be accounted in the results collation # to work properly as a required check - needs: [testable-changes, prepare-frontend-tests, verify-game-capture-binary-signatures, test, es-lint-strict-nulls] + needs: [testable-changes, prepare-frontend-tests, verify-binary, test, es-lint-strict-nulls] runs-on: ubuntu-latest steps: - name: 'Check CI Results'