From a3c39576df938c6b769f6cff51e2e8123f2a8452 Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Tue, 19 May 2026 21:23:43 -0700 Subject: [PATCH 1/2] filter secrets at logger callback; register basic-auth encoded blob --- .../Downloader/Type/GitHubTokenSetupTrait.php | 5 +- src/bootstrap.php | 17 +++ tests/GlobalsFunctionsTest.php | 104 ++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/GlobalsFunctionsTest.php diff --git a/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php b/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php index 34e350d4..412a4c1c 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php +++ b/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php @@ -16,8 +16,9 @@ trait GitHubTokenSetupTrait // GITHUB_TOKEN support if (($token = getenv('GITHUB_TOKEN')) !== false && ($user = getenv('GITHUB_USER')) !== false) { logger()->debug("Using 'GITHUB_TOKEN' with user {$user} for authentication"); - spc_add_log_filter([$user, $token]); - return ['Authorization: Basic ' . base64_encode("{$user}:{$token}")]; + $encoded = base64_encode("{$user}:{$token}"); + spc_add_log_filter([$user, $token, $encoded]); + return ["Authorization: Basic {$encoded}"]; } if (($token = getenv('GITHUB_TOKEN')) !== false) { logger()->debug("Using 'GITHUB_TOKEN' for authentication"); diff --git a/src/bootstrap.php b/src/bootstrap.php index 4e640fea..c300e1f6 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -33,6 +33,23 @@ ConsoleLogger::$date_format = 'H:i:s'; ConsoleLogger::$format = '[%date% %level_long%] %body%'; $ob_logger = new ConsoleLogger(LogLevel::WARNING); +$ob_logger->addLogCallback(function ($level, &$output, &$message, &$context, bool $shouldLog) { + global $spc_log_filters; + if (!is_array($spc_log_filters)) { + $spc_log_filters = []; + } + // filter message and context + $output = str_replace($spc_log_filters, '***', $output); + $message = str_replace($spc_log_filters, '***', $message); + $context = array_map(function ($item) use ($spc_log_filters) { + if (is_string($item)) { + return str_replace($spc_log_filters, '***', $item); + } + return $item; + }, $context); + return true; +}); + // setup log file if (filter_var(getenv('SPC_ENABLE_LOG_FILE'), FILTER_VALIDATE_BOOLEAN)) { // init spc log files diff --git a/tests/GlobalsFunctionsTest.php b/tests/GlobalsFunctionsTest.php new file mode 100644 index 00000000..b1b047db --- /dev/null +++ b/tests/GlobalsFunctionsTest.php @@ -0,0 +1,104 @@ +assertSame(['secret-value', 'other'], $GLOBALS['spc_log_filters']); + } + + public function testWriteLogMasksRegisteredValues(): void + { + spc_add_log_filter(['octocat', 'ghp_abcdef1234567890']); + + $stream = fopen('php://memory', 'r+'); + spc_write_log($stream, 'user=octocat token=ghp_abcdef1234567890'); + rewind($stream); + $written = stream_get_contents($stream); + fclose($stream); + + $this->assertSame('user=*** token=***', $written); + } + + public function testLoggerCallbackMasksOutput(): void + { + $token = 'ghp_abcdef1234567890'; + spc_add_log_filter($token); + + $stream = fopen('php://memory', 'r+'); + $logger = new ConsoleLogger(LogLevel::DEBUG, $stream, false); + $logger->addLogCallback(function ($level, &$output, &$message, &$context, bool $shouldLog) { + global $spc_log_filters; + if (!is_array($spc_log_filters)) { + $spc_log_filters = []; + } + $output = str_replace($spc_log_filters, '***', $output); + $message = str_replace($spc_log_filters, '***', $message); + $context = array_map(function ($item) use ($spc_log_filters) { + if (is_string($item)) { + return str_replace($spc_log_filters, '***', $item); + } + return $item; + }, $context); + return true; + }); + + $logger->debug("[PASSTHRU] curl -H\"Authorization: Bearer {$token}\" https://api.github.com/x"); + + rewind($stream); + $written = stream_get_contents($stream); + fclose($stream); + + $this->assertStringNotContainsString($token, $written); + $this->assertStringContainsString('***', $written); + } + + public function testGitHubTokenTraitRegistersEncodedBasicAuthBlob(): void + { + $user = 'octocat'; + $token = 'ghp_abcdef1234567890'; + $original_token = getenv('GITHUB_TOKEN'); + $original_user = getenv('GITHUB_USER'); + + putenv("GITHUB_TOKEN={$token}"); + putenv("GITHUB_USER={$user}"); + + try { + $headers = \StaticPHP\Artifact\Downloader\Type\GitHubRelease::getGitHubTokenHeadersStatic(); + + $encoded = base64_encode("{$user}:{$token}"); + $this->assertSame(["Authorization: Basic {$encoded}"], $headers); + $this->assertContains($user, $GLOBALS['spc_log_filters']); + $this->assertContains($token, $GLOBALS['spc_log_filters']); + $this->assertContains($encoded, $GLOBALS['spc_log_filters']); + } finally { + $original_token === false ? putenv('GITHUB_TOKEN') : putenv("GITHUB_TOKEN={$original_token}"); + $original_user === false ? putenv('GITHUB_USER') : putenv("GITHUB_USER={$original_user}"); + } + } +} From 52d234f1f4eb12cf1c071ddaa869a054a4ba7d09 Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Tue, 19 May 2026 21:59:18 -0700 Subject: [PATCH 2/2] fix windows test failures: path separators and arch normalization --- src/StaticPHP/Artifact/Artifact.php | 2 +- src/StaticPHP/Artifact/ArtifactCache.php | 4 ++-- tests/StaticPHP/Artifact/ArtifactTest.php | 15 ++++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/StaticPHP/Artifact/Artifact.php b/src/StaticPHP/Artifact/Artifact.php index e241d4fe..224bcff3 100644 --- a/src/StaticPHP/Artifact/Artifact.php +++ b/src/StaticPHP/Artifact/Artifact.php @@ -327,7 +327,7 @@ class Artifact public function getSourceRoot(): string { if (isset($this->config['metadata']['source-root'])) { - return $this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/'); + return FileSystem::convertPath($this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/')); } return $this->getSourceDir(); } diff --git a/src/StaticPHP/Artifact/ArtifactCache.php b/src/StaticPHP/Artifact/ArtifactCache.php index 5a2c8bac..2b0c8b54 100644 --- a/src/StaticPHP/Artifact/ArtifactCache.php +++ b/src/StaticPHP/Artifact/ArtifactCache.php @@ -223,8 +223,8 @@ class ArtifactCache public function getCacheFullPath(array $cache_info): string { return match ($cache_info['cache_type']) { - 'archive', 'file' => DOWNLOAD_PATH . '/' . $cache_info['filename'], - 'git' => DOWNLOAD_PATH . '/' . $cache_info['dirname'], + 'archive', 'file' => FileSystem::convertPath(DOWNLOAD_PATH . '/' . $cache_info['filename']), + 'git' => FileSystem::convertPath(DOWNLOAD_PATH . '/' . $cache_info['dirname']), 'local' => $cache_info['dirname'], // local dirname is absolute path default => throw new SPCInternalException("Unknown cache type: {$cache_info['cache_type']}"), }; diff --git a/tests/StaticPHP/Artifact/ArtifactTest.php b/tests/StaticPHP/Artifact/ArtifactTest.php index e1cb8ccd..0effb109 100644 --- a/tests/StaticPHP/Artifact/ArtifactTest.php +++ b/tests/StaticPHP/Artifact/ArtifactTest.php @@ -254,11 +254,16 @@ class ArtifactTest extends TestCase ApplicationContext::initialize(); ApplicationContext::set(ArtifactCache::class, $cache); + // Use a platform-appropriate absolute path: a Unix-style /tmp/... isn't + // absolute on Windows (no drive letter), so the test would otherwise + // fall through into the SOURCE_PATH-prefixed branch on Windows. + $extract = DIRECTORY_SEPARATOR === '\\' ? 'C:\tmp\my-pkg-extract' : '/tmp/my-pkg-extract'; + $artifact = new Artifact('my-pkg', [ - 'source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz', 'extract' => '/tmp/my-pkg-extract'], + 'source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz', 'extract' => $extract], ]); - $expected = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/tmp/my-pkg-extract'); + $expected = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $extract); $this->assertSame($expected, $artifact->getSourceDir()); } @@ -703,11 +708,7 @@ class ArtifactTest extends TestCase 'Windows' => 'windows', default => 'linux', }; - $arch = php_uname('m'); - if ($arch === 'arm64') { - $arch = 'aarch64'; - } - return "{$os}-{$arch}"; + return "{$os}-" . arch2gnu(php_uname('m')); } private function injectArtifactConfig(string $name, array $config): void