-
Notifications
You must be signed in to change notification settings - Fork 0
[feature] PHP security scanning #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f8a7a60
feature: Add composer-audit and guarddog-php-scan actions
spexii 7c5673d
feature: Add php-security-scan and php-vulnerability-scan reusable wo…
spexii a715848
feat: add ignore-severity input to composer-audit action and php-secu…
spexii 6e5aecc
feat: use bash array for composer audit severity flags
spexii 7d8aada
docs: document ignore-severity input and its inverted logic vs npm au…
spexii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Composer Audit | ||
|
|
||
| Composite action that runs `composer audit` against a project's `composer.lock` to detect known vulnerabilities in PHP dependencies. `composer audit` is a built-in Composer command (available since Composer 2.4; severity filtering requires 2.8) that queries the GitHub Security Advisory database and the Packagist security advisories feed — no binary download or extra installation is required. | ||
|
|
||
| ## Usage | ||
|
|
||
| > Replace `<current-sha>` with the current SHA from the [root README](../../../README.md#current-sha). | ||
|
|
||
| ```yaml | ||
| jobs: | ||
| composer-audit: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
|
||
| - uses: orangitfi/platform-tooling/.github/actions/composer-audit@<current-sha> | ||
| with: | ||
| working-directory: . | ||
| fail-on-findings: true | ||
| ``` | ||
|
|
||
| ## Inputs | ||
|
|
||
| | Input | Required | Default | Description | | ||
| |-------|----------|---------|-------------| | ||
| | `working-directory` | No | `.` | Directory containing `composer.json` and `composer.lock` | | ||
| | `fail-on-findings` | No | `true` | Fail the job if vulnerabilities are found | | ||
| | `ignore-severity` | No | `""` | Space-separated severity levels to ignore: `low`, `medium`, `high`, `critical`. Unlike npm's `audit-level` (minimum threshold), this is inverted — you specify what to *ignore*. To fail only on high and critical, set `"low medium"`. Leave empty to report all severities. | | ||
|
|
||
| ## Outputs | ||
|
|
||
| | Output | Description | | ||
| |--------|-------------| | ||
| | `result` | `passed` or `failed` | | ||
| | `vulnerabilities-found` | Total number of vulnerabilities found | | ||
|
|
||
| ## Requirements | ||
|
|
||
| - `composer.lock` must exist in the working directory. The action fails immediately with a clear error if it is missing. `composer audit` reads the lockfile only — it does not install dependencies. | ||
| - Composer ≥ 2.8 must be available on the runner. It is pre-installed on all GitHub-hosted `ubuntu-latest` runners. The action verifies the version at runtime and exits with a clear error if the requirement is not met. | ||
| - No packages are installed during the scan — `composer audit` operates against the committed lockfile. | ||
|
|
||
| ## Notes | ||
|
|
||
| - The action writes a summary table to `$GITHUB_STEP_SUMMARY` on every run. When vulnerabilities are found, a collapsible block containing the full human-readable `composer audit` output is appended. | ||
| - `composer audit` requires outbound HTTPS access to `packagist.org` and the GitHub Advisory API (`api.github.com`). Ensure these endpoints are reachable from your runner. | ||
| - To suppress a specific advisory that does not apply to your usage (e.g. a vulnerability in a code path you do not exercise), add it to the `ignored-security-advisories` list in your `composer.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "config": { | ||
| "audit": { | ||
| "ignored": ["CVE-2024-12345"] | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - Start with `fail-on-findings: false` when adding this action to an existing repository to understand the current baseline before enabling hard failures. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| name: "Composer Audit" | ||
| description: > | ||
| Run composer audit against a project's composer.lock to detect known | ||
| vulnerabilities in PHP dependencies. Requires Composer >= 2.8 which is | ||
| pre-installed on GitHub-hosted ubuntu-latest runners. No binary download | ||
| is needed — composer audit is a built-in Composer command. | ||
|
|
||
| inputs: | ||
| working-directory: | ||
| description: "Directory containing composer.json and composer.lock" | ||
| required: false | ||
| default: "." | ||
| fail-on-findings: | ||
| description: "Fail the job if vulnerabilities are found (default: true)" | ||
| required: false | ||
| default: "true" | ||
| ignore-severity: | ||
| description: > | ||
| Space-separated list of severity levels to ignore. Accepted values: | ||
| low, medium, high, critical. Example: "low medium" ignores low and | ||
| medium advisories and only fails on high/critical. | ||
| Requires Composer >= 2.8. Leave empty to report all severities. | ||
| required: false | ||
| default: "" | ||
|
|
||
| outputs: | ||
| result: | ||
| description: "Scan result: passed or failed" | ||
| value: ${{ steps.audit.outputs.result }} | ||
| vulnerabilities-found: | ||
| description: "Total number of vulnerabilities found" | ||
| value: ${{ steps.audit.outputs.vulnerabilities-found }} | ||
|
|
||
| runs: | ||
| using: "composite" | ||
| steps: | ||
| - name: Run composer audit | ||
| id: audit | ||
| shell: bash | ||
| working-directory: ${{ inputs.working-directory }} | ||
| env: | ||
| FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }} | ||
| IGNORE_SEVERITY: ${{ inputs.ignore-severity }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| REPORT="/tmp/composer-audit-report.json" | ||
|
|
||
| # Verify composer.lock exists | ||
| if [ ! -f "composer.lock" ]; then | ||
| echo "::error::composer.lock not found in $(pwd). composer audit requires a lockfile." | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Verify Composer >= 2.8 (required for --ignore-severity) | ||
| COMPOSER_VERSION="$(composer --version --no-ansi 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1)" | ||
| COMPOSER_MAJOR="$(echo "${COMPOSER_VERSION}" | cut -d. -f1)" | ||
| COMPOSER_MINOR="$(echo "${COMPOSER_VERSION}" | cut -d. -f2)" | ||
| if [ "${COMPOSER_MAJOR}" -lt 2 ] || { [ "${COMPOSER_MAJOR}" -eq 2 ] && [ "${COMPOSER_MINOR}" -lt 8 ]; }; then | ||
| echo "::error::composer audit requires Composer >= 2.8. Found: ${COMPOSER_VERSION}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Build --ignore-severity flags (flag is repeatable, not comma-separated) | ||
| SEVERITY_FLAGS=() | ||
| for SEV in ${IGNORE_SEVERITY}; do | ||
| SEVERITY_FLAGS+=("--ignore-severity=${SEV}") | ||
| done | ||
|
|
||
| { | ||
| echo "## Composer Audit" | ||
| echo "" | ||
| echo "| Setting | Value |" | ||
| echo "|---------|-------|" | ||
| echo "| Working directory | \`${{ inputs.working-directory }}\` |" | ||
| echo "| Composer version | \`${COMPOSER_VERSION}\` |" | ||
| if [ -n "${IGNORE_SEVERITY}" ]; then | ||
| echo "| Ignored severities | \`${IGNORE_SEVERITY}\` |" | ||
| fi | ||
| echo "" | ||
| } >> "${GITHUB_STEP_SUMMARY}" | ||
|
|
||
| EXIT_CODE=0 | ||
| composer audit --format=json --no-interaction "${SEVERITY_FLAGS[@]}" 1>"${REPORT}" 2>/dev/null || EXIT_CODE=$? | ||
|
|
||
| TOTAL=0 | ||
| if command -v jq >/dev/null 2>&1 && [ -f "${REPORT}" ]; then | ||
| TOTAL="$(jq '[.advisories | to_entries[] | .value[]] | length' "${REPORT}" 2>/dev/null || echo 0)" | ||
| fi | ||
|
|
||
| if [ "${EXIT_CODE}" -eq 0 ]; then | ||
| echo "result=passed" >> "${GITHUB_OUTPUT}" | ||
| echo "vulnerabilities-found=0" >> "${GITHUB_OUTPUT}" | ||
| echo "No vulnerabilities found." >> "${GITHUB_STEP_SUMMARY}" | ||
| else | ||
| echo "result=failed" >> "${GITHUB_OUTPUT}" | ||
| echo "vulnerabilities-found=${TOTAL}" >> "${GITHUB_OUTPUT}" | ||
|
|
||
| # Re-run without --format=json for human-readable summary output | ||
| HUMAN_OUTPUT="$(composer audit --no-interaction "${SEVERITY_FLAGS[@]}" 2>&1 || true)" | ||
|
|
||
| { | ||
| echo "**${TOTAL} vulnerability/vulnerabilities found.**" | ||
| echo "" | ||
| echo "<details><summary>Audit output</summary>" | ||
| echo "" | ||
| echo '```' | ||
| echo "${HUMAN_OUTPUT}" | ||
| echo '```' | ||
| echo "" | ||
| echo "</details>" | ||
| } >> "${GITHUB_STEP_SUMMARY}" | ||
|
|
||
| if [ "${FAIL_ON_FINDINGS}" = "true" ]; then | ||
| echo "::error::composer audit found ${TOTAL} vulnerabilities. See the job summary for details." | ||
| exit 1 | ||
| else | ||
| echo "::warning::composer audit found ${TOTAL} vulnerabilities (scan is non-blocking)." | ||
| fi | ||
| fi | ||
|
|
||
| - name: Cleanup | ||
| if: always() | ||
| shell: bash | ||
| run: rm -f /tmp/composer-audit-report.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Guarddog Packagist Supply-Chain Scan | ||
|
|
||
| Composite action that installs [guarddog](https://github.com/DataDog/guarddog) and verifies all PHP packages declared in `composer.json` for supply-chain threats **before any dependency is installed**. | ||
|
|
||
| Guarddog detects malicious packages through static heuristics including typosquatting, code injection, data exfiltration, and other indicators of compromise. | ||
|
|
||
| ## ⚠️ Experimental Warning | ||
|
|
||
| > **guarddog's Packagist (PHP) support exists but has received less production testing than its npm and PyPI equivalents.** Before enabling `fail-on-findings: true` in production pipelines, manually run `guarddog packagist verify composer.json` against your own project to validate that results are meaningful and noise levels are acceptable. Report unexpected behaviour to the [guarddog issue tracker](https://github.com/DataDog/guarddog/issues). | ||
| > | ||
| > It is strongly recommended to start with `fail-on-findings: false` to observe guarddog output before treating failures as blocking. | ||
|
|
||
| ## Usage | ||
|
|
||
| > Replace `<current-sha>` with the current SHA from the [root README](../../../README.md#current-sha). | ||
|
|
||
| ```yaml | ||
| jobs: | ||
| guarddog-php: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
|
||
| - uses: orangitfi/platform-tooling/.github/actions/guarddog-php-scan@<current-sha> | ||
| with: | ||
| working-directory: . | ||
| fail-on-findings: false # recommended until behaviour is verified | ||
| ``` | ||
|
|
||
| ## Inputs | ||
|
|
||
| | Input | Required | Default | Description | | ||
| |-------|----------|---------|-------------| | ||
| | `working-directory` | No | `.` | Directory containing `composer.json` | | ||
| | `fail-on-findings` | No | `true` | Fail the job if supply-chain threats are detected | | ||
|
|
||
| ## Requirements | ||
|
|
||
| - `composer.json` must exist in the working directory. The action fails immediately with a clear error if it is missing. The scan runs against the manifest only — no dependencies are installed. | ||
| - `pipx` must be available on the runner. It is pre-installed on all GitHub-hosted `ubuntu-latest` runners. guarddog is installed at runtime via `pipx install guarddog`. | ||
|
|
||
| ## Notes | ||
|
|
||
| - The action writes a summary table and an experimental caveat to `$GITHUB_STEP_SUMMARY` on every run. When threats are detected, a collapsible block containing the full guarddog output is appended. | ||
| - guarddog queries the Packagist registry to retrieve package metadata. Ensure outbound HTTPS access to `packagist.org` is permitted on your runner. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| name: "Guarddog Packagist Supply-Chain Scan" | ||
| description: > | ||
| Install guarddog and verify PHP packages listed in composer.json for | ||
| supply-chain threats (typosquatting, code injection, exfiltration, etc.) | ||
| BEFORE any dependency is installed. | ||
|
|
||
| WARNING: guarddog Packagist support is experimental. Verify behaviour | ||
| against your own composer.json before enabling fail-on-findings in | ||
| production pipelines. See README for details. | ||
|
|
||
| inputs: | ||
| working-directory: | ||
| description: "Directory containing composer.json" | ||
| required: false | ||
| default: "." | ||
| fail-on-findings: | ||
| description: "Fail the step if guarddog detects issues (default: true)" | ||
| required: false | ||
| default: "true" | ||
|
|
||
| runs: | ||
| using: "composite" | ||
| steps: | ||
| - name: Install guarddog | ||
| shell: bash | ||
| run: pipx install guarddog | ||
|
|
||
| - name: Run guarddog packagist verify | ||
| shell: bash | ||
| working-directory: ${{ inputs.working-directory }} | ||
| env: | ||
| FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| if [ ! -f "composer.json" ]; then | ||
| echo "::error::composer.json not found in $(pwd). guarddog packagist verify requires a composer.json." | ||
| exit 1 | ||
| fi | ||
|
|
||
| { | ||
| echo "## Guarddog Packagist Supply-Chain Scan" | ||
| echo "" | ||
| echo "> ⚠️ **Experimental:** guarddog Packagist support has received less production testing than npm/PyPI. Verify results manually before treating failures as blocking." | ||
| echo "" | ||
| echo "| Setting | Value |" | ||
| echo "|---------|-------|" | ||
| echo "| Working directory | \`${{ inputs.working-directory }}\` |" | ||
| echo "" | ||
| } >> "${GITHUB_STEP_SUMMARY}" | ||
|
|
||
| EXIT_CODE=0 | ||
| guarddog packagist verify composer.json 2>&1 | tee /tmp/guarddog-php-output.txt || EXIT_CODE=$? | ||
|
|
||
| if [ "${EXIT_CODE}" -eq 0 ]; then | ||
| echo "No supply-chain threats detected." >> "${GITHUB_STEP_SUMMARY}" | ||
| else | ||
| { | ||
| echo "**Supply-chain threats detected.**" | ||
| echo "" | ||
| echo "<details><summary>Guarddog output</summary>" | ||
| echo "" | ||
| echo '```' | ||
| cat /tmp/guarddog-php-output.txt | ||
| echo '```' | ||
| echo "" | ||
| echo "</details>" | ||
| } >> "${GITHUB_STEP_SUMMARY}" | ||
|
|
||
| if [ "${FAIL_ON_FINDINGS}" = "true" ]; then | ||
| echo "::error::guarddog detected potential supply-chain threats in Packagist packages. See the job summary for details." | ||
| exit 1 | ||
| else | ||
| echo "::warning::guarddog detected potential supply-chain threats (scan is non-blocking)." | ||
| fi | ||
| fi | ||
|
|
||
| - name: Cleanup | ||
| if: always() | ||
| shell: bash | ||
| run: rm -f /tmp/guarddog-php-output.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # php-security-scan | ||
|
|
||
| Reusable workflow that runs three security scans **in parallel** against PHP / Symfony repositories before build or test steps run. All three jobs must pass for the workflow to succeed. | ||
|
|
||
| > Replace `<current-sha>` with the current SHA from the [root README](../../README.md#current-sha). | ||
|
|
||
| ## Jobs | ||
|
|
||
| | Job | Tool | What it checks | | ||
| |-----|------|----------------| | ||
| | `gitleaks` | gitleaks v8.24.3 | Secrets and credentials committed to git history | | ||
| | `composer-audit` | composer audit (built-in, Composer ≥ 2.8) | Known CVEs in PHP dependencies (via composer.lock) | | ||
| | `guarddog-php` | guarddog ⚠️ experimental | Supply-chain threats in Packagist packages (typosquatting, exfiltration, malicious code) | | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Minimal — run on every push and PR | ||
|
|
||
| ```yaml | ||
| on: [push, pull_request] | ||
|
|
||
| jobs: | ||
| security: | ||
| uses: orangitfi/platform-tooling/.github/workflows/php-security-scan.yml@<current-sha> | ||
| ``` | ||
|
|
||
| ### With custom options | ||
|
|
||
| ```yaml | ||
| jobs: | ||
| security: | ||
| uses: orangitfi/platform-tooling/.github/workflows/php-security-scan.yml@<current-sha> | ||
| with: | ||
| working-directory: ./app | ||
| fail-on-findings: false # warn but don't block while onboarding | ||
| ``` | ||
|
|
||
| ### Recommended position in a pipeline | ||
|
|
||
| ```yaml | ||
| jobs: | ||
| security: | ||
| uses: orangitfi/platform-tooling/.github/workflows/php-security-scan.yml@<current-sha> | ||
|
|
||
| test: | ||
| needs: security | ||
| uses: orangitfi/platform-tooling/.github/workflows/php-ci.yml@<current-sha> | ||
| ``` | ||
|
|
||
| ## Parameters | ||
|
|
||
| | Input | Default | Description | | ||
| |-------|---------|-------------| | ||
| | `working-directory` | `.` | Directory containing `composer.json` and `composer.lock` | | ||
| | `fail-on-findings` | `true` | Fail the workflow if any scan detects issues | | ||
| | `ignore-severity` | `""` | Space-separated severity levels to ignore in composer audit: `low`, `medium`, `high`, `critical`. Inverted compared to npm's `audit-level` — you list what to ignore rather than the minimum to fail on. Example: `"low medium"` fails only on high/critical. | | ||
|
|
||
| ## What it does | ||
|
|
||
| - **gitleaks**: checks full git history (`fetch-depth: 0`) for leaked API keys, tokens, passwords, and other secrets | ||
| - **composer-audit**: reads the committed `composer.lock` and queries the GitHub Security Advisory database and Packagist security advisories feed for known CVEs — no packages are installed | ||
| - **guarddog-php**: checks each package in `composer.json` against heuristic rules for supply-chain attack indicators — no packages are installed | ||
|
|
||
| All three jobs run on separate runners and produce output in the GitHub Actions job summary. | ||
|
|
||
| ## ⚠️ Note on guarddog Packagist support | ||
|
|
||
| The `guarddog-php` job uses guarddog's `packagist` subcommand, which exists but has received less real-world testing than the `npm` and `pypi` equivalents. False positives or unexpected failures are possible. It is strongly recommended to run the workflow with `fail-on-findings: false` initially and manually inspect guarddog output before enabling hard failures. See the [guarddog-php-scan action README](../actions/guarddog-php-scan/README.md) for details. | ||
|
|
||
| ## When it has value | ||
|
|
||
| - **Pre-install gate**: both `composer-audit` and `guarddog-php` run before any `composer install`. A malicious or vulnerable package is caught before it ever executes on your runner. | ||
| - **Complementary coverage**: `composer-audit` finds CVE-tracked vulnerabilities (entries in the GitHub Advisory database); guarddog finds behavioural threats that have no CVE entry yet (obfuscated code, suspicious registry metadata, typosquatted names). Together they cover both vectors. | ||
| - **Parallel execution**: all three scans run simultaneously — total wall-clock time is the slowest scan (~30–60 s), not the sum. | ||
|
|
||
| ## Tips | ||
|
|
||
| - `composer.lock` must be committed. `composer audit` operates against the lockfile and will fail with a clear error if it is missing. Generate it with `composer install` or `composer update`. | ||
| - If gitleaks flags a test fixture as a false positive, add an allowlist entry to `.gitleaks.toml` in the consuming repo. See the [gitleaks-scan action README](../actions/gitleaks-scan/README.md#handling-false-positives). | ||
| - To suppress a specific `composer-audit` advisory, add it to the `config.audit.ignored` list in `composer.json`. See the [composer-audit action README](../actions/composer-audit/README.md#notes). | ||
| - To tighten or loosen the vulnerability threshold, use `ignore-severity` (e.g. `"low medium"` to fail only on high/critical). Note the inverted logic compared to npm's `audit-level` — Composer requires you to specify which severities to suppress rather than a minimum failing level. | ||
| - Start with `fail-on-findings: false` when adding to an existing repository to understand the current baseline before enabling hard failures. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All others have the severity threshold. Should this have also. It maybe impossible to get all minors fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Composer (2.8+) has inverted logic for this, it has
ignore-severityinstead of setting the lowest level to target. Added thisignore-severityinput tocomposer-audit/action.ymland threaded it throughphp-security-scan.yml. Bumped the minimum Composer version check from 2.4 to 2.8 accordingly.ubuntu-latestships 2.8+ so no practical impact for consumers.