> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pharussecurity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Actions

> Generate a CycloneDX SBOM with Syft on every GitHub release and send it to Pharus with curl.

<Info>
  **Plans:** Every plan. **Who:** an owner or admin creates the API token; anyone who can edit the repository's workflows adds the step.
</Info>

This workflow generates a CycloneDX JSON SBOM with `anchore/sbom-action` (Syft) whenever you publish a release, and sends it to Pharus. It is the same workflow as the **GitHub Actions** tab of **Connect a pipeline** on the **SBOMs** page.

## Before you start

<Steps>
  <Step title="Create a token">
    In Pharus, go to **Settings → API tokens** and create a token for the product family. See [create a token](/api-reference/introduction#create-a-token).
  </Step>

  <Step title="Store it as a secret">
    In GitHub, open the repository's **Settings → Secrets and variables → Actions** and add a repository secret named `PHARUS_TOKEN` with the token as its value.
  </Step>
</Steps>

## The workflow

Save this as `.github/workflows/pharus-sbom.yml`:

```yaml theme={null}
# .github/workflows/pharus-sbom.yml
name: SBOM to Pharus
on:
  release:
    types: [published]
  workflow_dispatch:

jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anchore/sbom-action@v0
        with:
          format: cyclonedx-json
          output-file: sbom.cdx.json
          upload-artifact: false
      - name: Send SBOM to Pharus
        env:
          PHARUS_TOKEN: ${{ secrets.PHARUS_TOKEN }}
          PRODUCT_VERSION: ${{ github.event.release.tag_name || github.ref_name }}
        run: |
          gzip -c sbom.cdx.json | curl --fail-with-body -sS -X POST "https://app.pharussecurity.com/api/v1/sboms" \
            -H "Authorization: Bearer $PHARUS_TOKEN" \
            -H "Content-Type: application/json" \
            -H "Content-Encoding: gzip" \
            -H "X-Pharus-Source: github" \
            -H "X-Pharus-Version: $PRODUCT_VERSION" \
            -H "X-Pharus-Commit: $GITHUB_SHA" \
            -H "X-Pharus-Branch: $GITHUB_REF_NAME" \
            -H "X-Pharus-Repo: $GITHUB_REPOSITORY" \
            -H "X-Pharus-Run-Url: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
            --data-binary @-
```

What it does:

* **Trigger**: every published release, and by hand from the **Actions** tab (`workflow_dispatch`).
* **Generate**: `anchore/sbom-action` runs Syft on the checked-out repository and writes `sbom.cdx.json`.
* **Version**: the release tag becomes the product version in Pharus. A run started by hand uses the branch or tag name.
* **Send**: the step gzips the file and posts it to [`POST /api/v1/sboms`](/api-reference/sboms). The `X-Pharus-*` headers fill in the **Pipeline** card on the SBOM's page, with a link back to the run.
* **Fail loudly**: `--fail-with-body` fails the step on any error and prints Pharus' message in the log.

## Variations

<AccordionGroup>
  <Accordion title="Send SPDX instead of CycloneDX">
    Set `format: spdx-json` and `output-file: sbom.spdx.json` on `anchore/sbom-action`, and gzip `sbom.spdx.json` in the last step. Pharus reads SPDX JSON 2.2 and 2.3.
  </Accordion>

  <Accordion title="Scan a container image">
    Add `image:` with the image reference to `anchore/sbom-action`, for example `image: ghcr.io/acme/gateway-g3:4.2`, to describe the image instead of the source tree.
  </Accordion>

  <Accordion title="Already using sbomify-action">
    sbomify-action saves the SBOM it uploads as `sbom_output.json`. Add the **Send SBOM to Pharus** step after it, with `gzip -c sbom_output.json` and `X-Pharus-Source: sbomify`. The **sbomify** tab of **Connect a pipeline** has the step ready to copy.
  </Accordion>
</AccordionGroup>

## Check that it worked

A successful run logs Pharus' answer, for example:

```json theme={null}
{"id":"6f1c2a9e-4b7d-4e0a-9a51-2d3c8e7f1b40","status":"parsed","components":19,"version":"v4.2","url":"https://app.pharussecurity.com/sboms/6f1c2a9e-4b7d-4e0a-9a51-2d3c8e7f1b40","duplicate":false}
```

In Pharus, the SBOM appears under **SBOMs** with **GitHub Actions** as its **Source**, and the token's **Last used** time changes under **Settings → API tokens**. A re-run for the same release answers with `"duplicate":true`.

If the step fails, the log shows the `error` code. The most common:

| Status | Fix |
| - | - |
| 401 | The secret is missing or wrong, or the token was revoked, expired or belongs to an archived family |
| 402 | The workspace is read-only; an owner subscribes under **Settings → Billing** |
| 413 | The SBOM is too large, even gzipped |
| 415 | The file isn't CycloneDX JSON 1.4–1.7 or SPDX JSON 2.2–2.3 |

All codes are listed under [errors](/api-reference/sboms#errors).

## Related

<CardGroup cols={2}>
  <Card title="Upload an SBOM" icon="upload" href="/api-reference/sboms" />

  <Card title="API introduction" icon="key-round" href="/api-reference/introduction" />

  <Card title="Azure Pipelines" icon="cloud" href="/api-reference/ci/azure-pipelines" />

  <Card title="GitLab CI" icon="git-merge" href="/api-reference/ci/gitlab-ci" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.