Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/actions/artifactory-oidc/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: "Artifactory OIDC - PHP/Composer"
description: "Exchange GitHub OIDC token for Artifactory access and configure Composer to resolve through Artifactory"

inputs:
artifactory_url:
description: "Artifactory base URL (e.g. https://segment.jfrog.io)"
required: true

runs:
using: "composite"
steps:
- name: Exchange OIDC token for Artifactory access token
id: oidc
shell: bash
env:
ARTIFACTORY_URL: ${{ inputs.artifactory_url }}
run: |
# Request OIDC token from GitHub
OIDC_TOKEN=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=jfrog-github" | jq -r '.value')

if [ -z "$OIDC_TOKEN" ] || [ "$OIDC_TOKEN" = "null" ]; then
echo "::error::Failed to obtain OIDC token"
exit 1
fi

# Exchange OIDC token for Artifactory access token
ART_TOKEN=$(curl -sS -X POST \
"$ARTIFACTORY_URL/access/api/v1/oidc/token" \
-H "Content-Type: application/json" \
-d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token\": \"$OIDC_TOKEN\", \"subject_token_type\": \"urn:ietf:params:oauth:token-type:id_token\", \"provider_name\": \"github\"}" \
| jq -r '.access_token')

if [ -z "$ART_TOKEN" ] || [ "$ART_TOKEN" = "null" ]; then
echo "::error::Failed to exchange OIDC token for Artifactory access token"
exit 1
fi

echo "::add-mask::$ART_TOKEN"
echo "art_token=$ART_TOKEN" >> "$GITHUB_OUTPUT"

- name: Configure Composer to use Artifactory
shell: bash
env:
ART_TOKEN: ${{ steps.oidc.outputs.art_token }}
ARTIFACTORY_URL: ${{ inputs.artifactory_url }}
run: |
HOST=$(echo "$ARTIFACTORY_URL" | sed 's|https://||')

# Configure Composer to use the Artifactory Packagist mirror
composer config --global repositories.packagist composer "https://:${ART_TOKEN}@${HOST}/artifactory/api/composer/virtual-php-thirdparty"

# Disable the default packagist.org to force all resolution through Artifactory
composer config --global repositories.packagist.org false

echo "Composer configured to resolve packages through Artifactory"
158 changes: 158 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
name: CI

on:
push:
branches: [master, main]
pull_request:
workflow_dispatch:

permissions:
id-token: write
contents: read

env:
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}

jobs:
coding-standard:
name: Coding Standards
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 # v2
with:
php-version: "8.2"
coverage: none
tools: cs2pr

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: ./.github/actions/artifactory-oidc
with:
artifactory_url: ${{ vars.ARTIFACTORY_URL }}

- name: Install Composer dependencies
run: composer install --no-scripts --no-interaction --prefer-dist

- name: Check coding standards
continue-on-error: true
run: ./vendor/bin/phpcs -s --report-full --report-checkstyle=./phpcs-report.xml

- name: Show PHPCS results in PR
run: cs2pr ./phpcs-report.xml

lint:
name: "Lint: PHP ${{ matrix.php }}"
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
strategy:
fail-fast: false
matrix:
php: ["8.1", "8.2", "8.3", "8.4"]

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 # v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: cs2pr

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: ./.github/actions/artifactory-oidc
with:
artifactory_url: ${{ vars.ARTIFACTORY_URL }}

- name: Install Composer dependencies
run: composer install --no-scripts --no-interaction --prefer-dist

- name: Lint against parse errors
run: composer lint

test:
name: "Test: PHP ${{ matrix.php }}"
needs: [coding-standard, lint]
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
strategy:
fail-fast: false
matrix:
php: ["8.1", "8.2", "8.3", "8.4"]

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 # v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
ini-values: error_reporting=E_ALL, display_errors=On
extensions: curl, json, mbstring

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: ./.github/actions/artifactory-oidc
with:
artifactory_url: ${{ vars.ARTIFACTORY_URL }}

- name: Install Composer dependencies
run: composer install --no-scripts --no-interaction --prefer-dist

- name: Run tests
run: ./vendor/bin/phpunit --no-coverage

- name: Run tests with coverage
if: ${{ matrix.php == '8.2' }}
run: ./vendor/bin/phpunit

- name: Upload coverage to Codecov
if: ${{ success() && matrix.php == '8.2' }}
uses: codecov/codecov-action@v5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

The codecov-action is pinned to a mutable semantic version tag instead of a full commit SHA, allowing attackers who compromise the codecov repository to inject malicious code into your workflow.

More details about this

The codecov/codecov-action@v5 action is pinned to a semantic version tag (@v5) instead of a full commit SHA. This creates a security risk because the maintainers of the codecov-action repository could push a malicious update to the v5 tag, and your workflow would automatically pull and execute the compromised code without warning.

Exploit scenario:

  1. A attacker gains write access to the codecov/codecov-action repository (or the account of a maintainer)
  2. They force-push a malicious commit to the existing v5 tag, overwriting the legitimate code with a backdoor
  3. On your next workflow run, the step Upload coverage to Codecov checks out and executes the malicious version of codecov-action
  4. The backdoor could steal your CODECOV_TOKEN secret (which is already exposed to this action) or inject code into your build artifacts
  5. Unlike actions pinned to a commit SHA like actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683, GitHub cannot detect this tampering because the tag points to whatever the attacker pushed

Other actions in this workflow like actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 and shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 correctly use full commit SHAs, making them immutable against this attack.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@e851fdd58d965fa1d6a5b0db7cb65a4d42b992fc # v5
View step-by-step instructions
  1. Replace uses: codecov/codecov-action@v5 with a full 40-character commit SHA from the codecov/codecov-action repository, for example uses: codecov/codecov-action@<full-commit-sha>.
  2. Add the version as a comment after the SHA, such as # v5, so it stays readable while still being immutable.
  3. Choose the SHA from the exact v5 release or tag you intend to use, instead of a moving tag reference. This prevents the workflow from silently picking up different action code later.

Alternatively, if you do not want to depend on the third-party action at all, replace the uses: step with a run: step that uploads coverage through a trusted tool already installed in the job.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by third-party-action-not-pinned-to-commit-sha.

Need help with this issue? Consult our appsec team or ask in #help-appsec on Slack.

You can view more details about this finding in the Semgrep AppSec Platform.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Mutable tag @v5 on codecov-action allows silent code changes by action maintainer, enabling supply-chain attacks against your CI/CD pipeline and secrets.

More details about this

The codecov/codecov-action@v5 step uses a mutable tag (v5) instead of pinning to a specific commit SHA. This allows the action maintainer to silently update what code runs when your workflow executes.

Attack scenario: An attacker compromises the codecov organization and updates the v5 tag to point to malicious code. When your CI/CD pipeline runs, it automatically uses the compromised version without any notification. The malicious action has access to your repository contents and secrets (including CODECOV_TOKEN), allowing the attacker to exfiltrate your source code, steal credentials, or manipulate your build artifacts.

This type of attack has real precedent—the trivy-action and kics-github-action were both compromised in similar supply-chain attacks that affected thousands of repositories.

Other steps in your workflow like actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 and shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 are correctly pinned to specific commit SHAs and are safe from this risk.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@0d3f4b1b85f4a1d8c2e7b6f9a4c3d2e1f0a9b8c7 # v5 - replace with the exact 40-character commit SHA for the intended v5 release from codecov/codecov-action
View step-by-step instructions
  1. Replace the mutable action tag with a full 40-character commit SHA in the workflow step.
    Change uses: codecov/codecov-action@v5 to uses: codecov/codecov-action@<full-commit-sha>, and keep the version as a comment such as # v5.x.x.

  2. Pin the SHA to the exact release you intend to use from the codecov/codecov-action repository, for example by taking the commit behind the current v5 release instead of using the tag name directly.
    This prevents the action reference from changing if the tag is later moved.

  3. Keep the rest of the step unchanged, including the existing with: inputs and CODECOV_TOKEN handling, so only the action reference becomes immutable.

Alternatively, if you need a newer Codecov release, update to that release first and then pin uses: to that release’s full commit SHA instead of a branch or tag.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by github-actions-mutable-action-tag.

Need help with this issue? Consult our appsec team or ask in #help-appsec on Slack.

You can view more details about this finding in the Semgrep AppSec Platform.

with:
files: ./build/logs/clover.xml
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

build-verification:
name: Build Verification
needs: [test]
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 # v2
with:
php-version: "8.2"
coverage: none

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: ./.github/actions/artifactory-oidc
with:
artifactory_url: ${{ vars.ARTIFACTORY_URL }}

- name: Install Composer dependencies (production)
run: composer install --no-scripts --no-interaction --prefer-dist --no-dev --optimize-autoloader

- name: Verify autoload
run: php -r "require 'vendor/autoload.php'; echo 'Autoload OK' . PHP_EOL;"

- name: Verify package structure
run: |
# Ensure required files exist for Packagist
test -f composer.json
test -f LICENSE.md
test -d lib
echo "Package structure verified"
40 changes: 28 additions & 12 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,61 @@ name: E2E Tests

on:
push:
branches: [master]
branches: [master, main]
pull_request:
branches: [master]
branches: [master, main]
workflow_dispatch:
inputs:
e2e_tests_ref:
description: 'Branch or ref of sdk-e2e-tests to use'
description: "Branch or ref of sdk-e2e-tests to use"
required: false
default: 'main'
default: "main"

permissions:
id-token: write
contents: read

env:
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}

jobs:
e2e-tests:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
# Skip for forks — E2E tests require internal infrastructure
if: ${{ !github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-x64

steps:
- name: Checkout SDK
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
path: sdk

- name: Checkout sdk-e2e-tests
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: segmentio/sdk-e2e-tests
ref: ${{ inputs.e2e_tests_ref || 'main' }}
token: ${{ secrets.E2E_TESTS_TOKEN }}
path: sdk-e2e-tests

- name: Setup PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@cf4cade2721270509d5b1c766dad417c269cbb11 # v2
with:
php-version: '8.2'
php-version: "8.2"
extensions: curl, json

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"

- name: Configure Artifactory
uses: ./sdk/.github/actions/artifactory-oidc
with:
artifactory_url: ${{ vars.ARTIFACTORY_URL }}

- name: Install SDK dependencies
working-directory: sdk
run: composer install --no-scripts --no-interaction --prefer-dist

- name: Run E2E tests
working-directory: sdk-e2e-tests
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
composer.lock
vendor
composer.phar
test/analytics.log
Expand Down
Loading