#!/usr/bin/env php * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoStandards.Scripts.Validate * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /validate/check_no_secrets.php * BRIEF: Checks for potential secrets in committed files (advisory) */ declare(strict_types=1); require_once __DIR__ . '/../../vendor/autoload.php'; use MokoEnterprise\CliFramework; /** * Scans all tracked non-binary files for common secret patterns (advisory — always exits 0). */ class CheckNoSecrets extends CliFramework { /** Regex matching suspicious key=value or key: value assignments. */ private const SECRET_PATTERN = '/(password|api[_\-]?key|secret|token|private[_\-]?key)\s*[:=]\s*["\'][^"\']{8,}/i'; /** * Substrings that mark a line as a known-safe false positive. * Dolibarr CSRF token functions generate nonces at runtime — not credentials. */ private const SAFE_SUBSTRINGS = ['newToken()', 'checkToken()', 'currentToken()']; /** Binary file extensions to skip. */ private const BINARY_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'zip', 'tar', 'gz']; /** * Configure available arguments. */ protected function configure(): void { $this->setDescription('Checks for potential secrets in committed files (advisory)'); $this->addArgument('--path', 'Repository path to check', '.'); } /** * Run the secrets scan (advisory — always exits 0). * * @return int Exit code: always 0. */ protected function run(): int { $path = $this->getArgument('--path'); $output = shell_exec('git -C ' . escapeshellarg($path) . ' ls-files 2>/dev/null') ?? ''; $all = array_values(array_filter(explode("\n", $output))); $files = array_filter($all, function (string $f): bool { return !in_array(strtolower(pathinfo($f, PATHINFO_EXTENSION)), self::BINARY_EXTENSIONS, true); }); $files = array_values($files); $total = count($files); $found = 0; $this->section('Scanning for secret patterns'); foreach ($files as $i => $file) { $this->progress($i + 1, $total, $file); $fullPath = $path . '/' . $file; if (!is_file($fullPath)) { continue; } $lines = explode("\n", (string) file_get_contents($fullPath)); $flagged = false; foreach ($lines as $line) { if (!preg_match(self::SECRET_PATTERN, $line)) { continue; } // Skip known-safe patterns (e.g. Dolibarr CSRF token functions) $safe = false; foreach (self::SAFE_SUBSTRINGS as $sub) { if (str_contains($line, $sub)) { $safe = true; break; } } if (!$safe) { $flagged = true; break; } } if ($flagged) { $this->progress($i + 1, $total, '', true); $this->status(false, $file, 'potential secret pattern detected'); $found++; } } $this->progress($total, $total, '', true); $this->printSummary($total - $found, $found, $this->elapsed()); if ($found > 0) { $this->log('WARNING', 'Advisory — review flagged files manually'); } return 0; } } $script = new CheckNoSecrets('check_no_secrets', 'Checks for potential secrets in committed files'); exit($script->execute());