mirror of
https://github.com/shivammathur/setup-php.git
synced 2026-05-14 01:21:38 +07:00
GHSA-f9f8-rm49-7jv2: Fix GitHub auth handling for composer in affected versions
This commit is contained in:
@@ -31,6 +31,12 @@ function getData(data: Partial<ToolData>): ToolData {
|
||||
};
|
||||
}
|
||||
|
||||
function unsetComposerAuthEnv(): void {
|
||||
delete process.env['GITHUB_TOKEN'];
|
||||
delete process.env['COMPOSER_TOKEN'];
|
||||
delete process.env['COMPOSER_AUTH_JSON'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock fetch.ts
|
||||
*/
|
||||
@@ -423,6 +429,118 @@ describe('Tools tests', () => {
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
version | affected
|
||||
${'1'} | ${false}
|
||||
${'1.0.0-alpha1'} | ${true}
|
||||
${'1.0.0-alpha2'} | ${true}
|
||||
${'1.0.0-alpha3'} | ${true}
|
||||
${'1.0.0-alpha4'} | ${true}
|
||||
${'1.0.0-alpha5'} | ${true}
|
||||
${'1.0.0-alpha6'} | ${true}
|
||||
${'1.0.0-alpha7'} | ${true}
|
||||
${'1.0.0-alpha8'} | ${true}
|
||||
${'1.0.0-alpha9'} | ${true}
|
||||
${'1.0.0-alpha10'} | ${true}
|
||||
${'1.0.0-alpha11'} | ${true}
|
||||
${'1.0.0-beta1'} | ${true}
|
||||
${'1.0.0-beta2'} | ${true}
|
||||
${'1.0.0'} | ${true}
|
||||
${'1.10.27'} | ${true}
|
||||
${'1.10.28'} | ${false}
|
||||
${'2.0.0-alpha1'} | ${true}
|
||||
${'2.0.0-alpha2'} | ${true}
|
||||
${'2.0.0-alpha3'} | ${true}
|
||||
${'2.0.0-RC1'} | ${true}
|
||||
${'2.0.0-RC2'} | ${true}
|
||||
${'2.2.27'} | ${true}
|
||||
${'2.2.28'} | ${false}
|
||||
${'2.3.0-RC1'} | ${true}
|
||||
${'2.3.0-RC2'} | ${true}
|
||||
${'2.9.7'} | ${true}
|
||||
${'2.9.7-RC1'} | ${true}
|
||||
${'2.9.8'} | ${false}
|
||||
${'2.9.0RC1'} | ${false}
|
||||
${'2.9.x-dev'} | ${false}
|
||||
`('checking affected composer version: $version', ({version, affected}) => {
|
||||
expect(tools.skipGitHubAuthForComposerVersion(version)).toBe(affected);
|
||||
});
|
||||
|
||||
it('checking affected composer version with CRLF ranges', async () => {
|
||||
let affected = false;
|
||||
let fixed = true;
|
||||
await jest.isolateModulesAsync(async () => {
|
||||
jest.doMock('fs', () => ({
|
||||
...jest.requireActual('fs'),
|
||||
readFileSync: (
|
||||
filePath: fs.PathOrFileDescriptor,
|
||||
options?: unknown
|
||||
) => {
|
||||
if (String(filePath).includes('composer-gh-auth-no-op')) {
|
||||
return '1.0.0-0 1.10.28\r\n2.0.0-0 2.2.28\r\n2.3.0-0 2.9.8';
|
||||
}
|
||||
return (jest.requireActual('fs') as typeof fs).readFileSync(
|
||||
filePath,
|
||||
options as fs.ObjectEncodingOptions & {flag?: string}
|
||||
);
|
||||
}
|
||||
}));
|
||||
const isolatedTools = await import('../src/tools');
|
||||
affected = isolatedTools.skipGitHubAuthForComposerVersion('2.9.7');
|
||||
fixed = isolatedTools.skipGitHubAuthForComposerVersion('2.9.8');
|
||||
});
|
||||
expect(affected).toBe(true);
|
||||
expect(fixed).toBe(false);
|
||||
});
|
||||
|
||||
it.each`
|
||||
auth_json | expected
|
||||
${'{"github-oauth":{"github.com":"ghs_new-token"},"http-basic":{"repo.example":{"username":"u","password":"p"}}}'} | ${'{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'}
|
||||
${'{"github-oauth":{"github.com":"ghs_new-token"}}'} | ${undefined}
|
||||
${'{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'} | ${'{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'}
|
||||
${'{"nested":{"github-oauth":{"github.com":"ghs_new-token"}}}'} | ${'{"nested":{"github-oauth":{"github.com":"ghs_new-token"}}}'}
|
||||
${'{"github-oauth":'} | ${'{"github-oauth":'}
|
||||
`('cleaning composer auth json', ({auth_json, expected}) => {
|
||||
unsetComposerAuthEnv();
|
||||
process.env['COMPOSER_AUTH_JSON'] = auth_json;
|
||||
tools.cleanComposerAuthJson();
|
||||
expect(process.env['COMPOSER_AUTH_JSON']).toBe(expected);
|
||||
unsetComposerAuthEnv();
|
||||
});
|
||||
|
||||
it.each`
|
||||
version | os | envs | skip_github_auth
|
||||
${'latest'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
|
||||
${'1'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
|
||||
${'2'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
|
||||
${'2.9.7'} | ${'linux'} | ${{}} | ${true}
|
||||
${'2.9.7'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${true}
|
||||
${'2.9.7'} | ${'linux'} | ${{COMPOSER_TOKEN: 'ghs_token'}} | ${true}
|
||||
${'2.9.7'} | ${'linux'} | ${{COMPOSER_AUTH_JSON: '{"github-oauth":{"github.com":"ghs_new-token"}}'}} | ${true}
|
||||
${'2.9.7'} | ${'linux'} | ${{COMPOSER_AUTH_JSON: '{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'}} | ${true}
|
||||
${'2.9.8'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
|
||||
${'2.9.7'} | ${'win32'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${true}
|
||||
`(
|
||||
'checking composer github auth skip flag: $version, $os',
|
||||
async ({version, os, envs, skip_github_auth}) => {
|
||||
unsetComposerAuthEnv();
|
||||
Object.assign(process.env, envs);
|
||||
const data = getData({
|
||||
tool: 'composer',
|
||||
os: os,
|
||||
php_version: '7.4',
|
||||
domain: 'https://getcomposer.org',
|
||||
repository: 'composer/composer',
|
||||
version: version
|
||||
});
|
||||
const script = await tools.addComposer(data);
|
||||
expect(script).toContain(
|
||||
`composer ${version}${skip_github_auth ? ' true' : ''}`
|
||||
);
|
||||
unsetComposerAuthEnv();
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
version | uri
|
||||
${'latest'} | ${'wp-cli/builds/blob/gh-pages/phar/wp-cli.phar?raw=true'}
|
||||
@@ -642,6 +760,7 @@ describe('Tools tests', () => {
|
||||
${'composer:preview'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-preview.phar,https://artifacts.setup-php.com/composer/composer-7.4-preview.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-preview.phar,https://getcomposer.org/composer-preview.phar composer preview'}
|
||||
${'composer, composer:v1'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-1.phar,https://artifacts.setup-php.com/composer/composer-7.4-1.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-1.phar,https://getcomposer.org/composer-1.phar composer'}
|
||||
${'composer:v1, composer:preview, composer:snapshot'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-snapshot.phar,https://artifacts.setup-php.com/composer/composer-7.4-snapshot.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-snapshot.phar,https://getcomposer.org/composer.phar composer snapshot'}
|
||||
${'composer:2.9.7'} | ${'add_tool https://github.com/composer/composer/releases/download/2.9.7/composer.phar,https://getcomposer.org/download/2.9.7/composer.phar composer 2.9.7 true'}
|
||||
`('checking composer setup: $tools_csv', async ({tools_csv, script}) => {
|
||||
expect(await tools.addTools(tools_csv, '7.4', 'linux')).toContain(script);
|
||||
});
|
||||
|
||||
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
3
src/configs/composer-gh-auth-no-op
Normal file
3
src/configs/composer-gh-auth-no-op
Normal file
@@ -0,0 +1,3 @@
|
||||
1.0.0-0 1.10.28
|
||||
2.0.0-0 2.2.28
|
||||
2.3.0-0 2.9.8
|
||||
1
src/configs/composer-gh-auth-warn
Normal file
1
src/configs/composer-gh-auth-warn
Normal file
@@ -0,0 +1 @@
|
||||
Composer %s has a known GitHub token parsing bug that exposes GitHub tokens in the error output. So, GitHub authentication has not been configured for this Composer version. Please update to the latest version of Composer. See: https://github.com/composer/composer/security/advisories/GHSA-f9f8-rm49-7jv2
|
||||
@@ -3,6 +3,7 @@ $composer_home = "$env:APPDATA\Composer"
|
||||
$composer_bin = "$composer_home\vendor\bin"
|
||||
$composer_json = "$composer_home\composer.json"
|
||||
$composer_lock = "$composer_home\composer.lock"
|
||||
$skip_composer_github_auth = $false
|
||||
|
||||
# Function to configure composer.
|
||||
Function Edit-ComposerConfig() {
|
||||
@@ -23,6 +24,7 @@ Function Edit-ComposerConfig() {
|
||||
if (-not(Test-Path $composer_json)) {
|
||||
Set-Content -Path $composer_json -Value "{}"
|
||||
}
|
||||
Get-ToolVersion "composer" $null | Out-Null
|
||||
Set-ComposerEnv
|
||||
Add-Path $composer_bin
|
||||
Set-ComposerAuth
|
||||
@@ -74,8 +76,18 @@ function Test-GitHubPublicAccess {
|
||||
}
|
||||
}
|
||||
|
||||
Function Write-ComposerGhAuthNoOpWarning() {
|
||||
$message = (Get-Content (Join-Path $src 'configs\composer-gh-auth-warn') -Raw).Trim().Replace('%s', $composer_version)
|
||||
if($env:fail_fast -eq 'true') {
|
||||
Add-Log "$cross" "composer" $message
|
||||
} else {
|
||||
Write-Output "::warning::$message"
|
||||
}
|
||||
}
|
||||
|
||||
# Function to setup authentication in composer.
|
||||
Function Set-ComposerAuth() {
|
||||
$token = if ($env:COMPOSER_TOKEN) { $env:COMPOSER_TOKEN } else { $env:GITHUB_TOKEN }
|
||||
if(Test-Path env:COMPOSER_AUTH_JSON) {
|
||||
if(Test-Json -JSON $env:COMPOSER_AUTH_JSON) {
|
||||
Set-Content -Path $composer_home\auth.json -Value $env:COMPOSER_AUTH_JSON
|
||||
@@ -83,13 +95,18 @@ Function Set-ComposerAuth() {
|
||||
Add-Log "$cross" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON"
|
||||
}
|
||||
}
|
||||
if($skip_composer_github_auth) {
|
||||
Write-ComposerGhAuthNoOpWarning
|
||||
}
|
||||
$composer_auth = @()
|
||||
if(Test-Path env:PACKAGIST_TOKEN) {
|
||||
$composer_auth += '"http-basic": {"repo.packagist.com": { "username": "token", "password": "' + $env:PACKAGIST_TOKEN + '"}}'
|
||||
}
|
||||
$write_token = $true
|
||||
$token = if ($env:COMPOSER_TOKEN) { $env:COMPOSER_TOKEN } else { $env:GITHUB_TOKEN }
|
||||
if ($token) {
|
||||
if ($skip_composer_github_auth) {
|
||||
$write_token = $false
|
||||
}
|
||||
if ($env:GITHUB_SERVER_URL -ne "https://github.com" -and -not(Test-GitHubPublicAccess $token)) {
|
||||
$write_token = $false
|
||||
}
|
||||
@@ -210,8 +227,13 @@ Function Add-Tool() {
|
||||
[ValidateNotNull()]
|
||||
$tool,
|
||||
[Parameter(Position = 2, Mandatory = $false)]
|
||||
$ver_param
|
||||
$ver_param,
|
||||
[Parameter(Position = 3, Mandatory = $false)]
|
||||
$skip_composer_github_auth
|
||||
)
|
||||
if($tool -eq "composer") {
|
||||
$script:skip_composer_github_auth = $skip_composer_github_auth -eq 'true'
|
||||
}
|
||||
$urls = $urls -split ','
|
||||
$tool_path = "$bin_dir\$tool"
|
||||
$is_exe = ((($urls[0] | Split-Path -Extension).ToLowerInvariant()) -eq '.exe')
|
||||
|
||||
@@ -3,6 +3,7 @@ export composer_home="$HOME/.composer"
|
||||
export composer_bin="$composer_home/vendor/bin"
|
||||
export composer_json="$composer_home/composer.json"
|
||||
export composer_lock="$composer_home/composer.lock"
|
||||
skip_composer_github_auth=false
|
||||
|
||||
# Function to extract tool version.
|
||||
get_tool_version() {
|
||||
@@ -41,6 +42,7 @@ configure_composer() {
|
||||
echo '{}' | tee "$composer_json" >/dev/null
|
||||
chmod 644 "$composer_json"
|
||||
fi
|
||||
get_tool_version composer >/dev/null
|
||||
set_composer_env
|
||||
add_path "$composer_bin"
|
||||
set_composer_auth
|
||||
@@ -70,22 +72,39 @@ can_access_public_github() {
|
||||
curl --fail -s -H "Authorization: token $1" 'https://api.github.com/' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
composer_gh_auth_no_op() {
|
||||
local message
|
||||
message="$(<"${src:?}"/configs/composer-gh-auth-warn)"
|
||||
message="${message//%s/$composer_version}"
|
||||
if [ "${fail_fast:-false}" = "true" ]; then
|
||||
add_log "${cross:?}" "composer" "$message"
|
||||
else
|
||||
echo "::warning::$message"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to setup authentication in composer.
|
||||
set_composer_auth() {
|
||||
if [ -n "$COMPOSER_AUTH_JSON" ]; then
|
||||
if php -r "json_decode('$COMPOSER_AUTH_JSON'); if(json_last_error() !== JSON_ERROR_NONE) { throw new Exception('invalid json'); }"; then
|
||||
echo "$COMPOSER_AUTH_JSON" | tee "$composer_home/auth.json" >/dev/null
|
||||
token="${COMPOSER_TOKEN:-$GITHUB_TOKEN}"
|
||||
if [ -n "${COMPOSER_AUTH_JSON:-}" ]; then
|
||||
if printf '%s' "$COMPOSER_AUTH_JSON" | jq -e . >/dev/null; then
|
||||
printf '%s' "$COMPOSER_AUTH_JSON" | tee "$composer_home/auth.json" >/dev/null
|
||||
else
|
||||
add_log "${cross:?}" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON"
|
||||
fi
|
||||
fi
|
||||
if [ "$skip_composer_github_auth" = "true" ]; then
|
||||
composer_gh_auth_no_op
|
||||
fi
|
||||
composer_auth=()
|
||||
if [ -n "$PACKAGIST_TOKEN" ]; then
|
||||
composer_auth+=( '"http-basic": {"repo.packagist.com": { "username": "token", "password": "'"$PACKAGIST_TOKEN"'"}}' )
|
||||
fi
|
||||
token="${COMPOSER_TOKEN:-$GITHUB_TOKEN}"
|
||||
if [ -n "$token" ]; then
|
||||
write_token=true
|
||||
if [ "$skip_composer_github_auth" = "true" ]; then
|
||||
write_token=false
|
||||
fi
|
||||
if [ "$GITHUB_SERVER_URL" != "https://github.com" ]; then
|
||||
can_access_public_github "$token" || write_token=false
|
||||
fi
|
||||
@@ -182,6 +201,9 @@ add_tool() {
|
||||
url=$1
|
||||
tool=$2
|
||||
ver_param=$3
|
||||
if [ "$tool" = "composer" ]; then
|
||||
skip_composer_github_auth="${4:-false}"
|
||||
fi
|
||||
tool_path="$tool_path_dir/$tool"
|
||||
if ! [ -d "$tool_path_dir" ]; then
|
||||
sudo mkdir -p "$tool_path_dir"
|
||||
|
||||
45
src/tools.ts
45
src/tools.ts
@@ -95,6 +95,42 @@ interface DeployerManifestEntry {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function skipGitHubAuthForComposerVersion(version: string): boolean {
|
||||
if (!/^\d+\.\d+\.\d+(?:-[\w-]+)?$/.test(version)) {
|
||||
return false;
|
||||
}
|
||||
return fs
|
||||
.readFileSync(
|
||||
path.join(__dirname, '../src/configs/composer-gh-auth-no-op'),
|
||||
'utf8'
|
||||
)
|
||||
.trim()
|
||||
.split(/\r?\n/)
|
||||
.some(range => {
|
||||
const [min, max] = range.trim().split(/\s+/);
|
||||
return (
|
||||
cv.compareVersions(version, min) >= 0 &&
|
||||
cv.compareVersions(version, max) < 0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function cleanComposerAuthJson(): void {
|
||||
try {
|
||||
const auth_json = process.env['COMPOSER_AUTH_JSON'] || '';
|
||||
if (!auth_json.includes('github-oauth')) return;
|
||||
const auth = JSON.parse(auth_json);
|
||||
delete auth['github-oauth'];
|
||||
if (!Object.keys(auth).length) {
|
||||
delete process.env['COMPOSER_AUTH_JSON'];
|
||||
} else {
|
||||
process.env['COMPOSER_AUTH_JSON'] = JSON.stringify(auth);
|
||||
}
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get version in semver format.
|
||||
*
|
||||
@@ -383,6 +419,7 @@ export async function addComposer(data: ToolData): Promise<string> {
|
||||
const version_source_url = `${getcomposer}/download/${channel}/composer.phar`;
|
||||
let cache_url = `${releases_url},${spc_url},${cds_url}`;
|
||||
let source_url = `${getcomposer}/composer.phar`;
|
||||
let skip_composer_github_auth = '';
|
||||
switch (true) {
|
||||
case /^snapshot$/.test(channel):
|
||||
source_url = is_lts ? lts_url : source_url;
|
||||
@@ -393,7 +430,11 @@ export async function addComposer(data: ToolData): Promise<string> {
|
||||
case /^1$/.test(channel):
|
||||
source_url = channel_source_url;
|
||||
break;
|
||||
case /^\d+\.\d+\.\d+[\w-]*$/.test(data.version):
|
||||
case /^\d+\.\d+\.\d+(?:-[\w-]+)?$/.test(data.version):
|
||||
if (skipGitHubAuthForComposerVersion(data.version)) {
|
||||
cleanComposerAuthJson();
|
||||
skip_composer_github_auth = ' true';
|
||||
}
|
||||
cache_url = `${github}/${data.repository}/releases/download/${data.version}/composer.phar`;
|
||||
source_url = version_source_url;
|
||||
break;
|
||||
@@ -402,7 +443,7 @@ export async function addComposer(data: ToolData): Promise<string> {
|
||||
}
|
||||
const use_cache: boolean = (await utils.readEnv('NO_TOOLS_CACHE')) !== 'true';
|
||||
data.url = use_cache ? `${cache_url},${source_url}` : source_url;
|
||||
data.version_parameter = data.version;
|
||||
data.version_parameter = data.version + skip_composer_github_auth;
|
||||
return await addArchive(data);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user