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
159 changes: 159 additions & 0 deletions .github/workflows/generate-security-scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
name: Scorecard analysis workflow

# Only use on public/private repositories where Code Scanning has been enabled.
on:
workflow_call:
secrets:
repo_token:
required: false
inputs:
results_file:
required: true
type: string
default: "results.sarif"
results_format:
required: true
type: string
default: "sarif" # sarif or json only
upload_type:
required: false
type: string
default: "artifact" # art(i|e)fact or dashboard only
publish_results:
required: false
type: boolean
default: false
file_mode:
required: false
type: string
default: "archive" # archive or git only
repo_token:
required: false
type: string
custom_scan:
required: false
type: boolean
default: false
description: >-
When true, run the Scorecard CLI directly (via Docker) restricted to the checks in
`custom_checks`, instead of the full `ossf/scorecard-action` suite. Note that
`publish_results` has no effect in this mode - see the workflow's README section.
custom_checks:
required: false
type: string
default: ""
description: >-
Comma-separated list of Scorecard check names to run when `custom_scan` is true,
e.g. "Branch-Protection,Code-Review". Required (non-empty) when `custom_scan` is true.
scorecard_version:
required: false
type: string
default: "v5.5.0"
description: >-
Pinned tag of the `ghcr.io/ossf/scorecard` CLI image used only when `custom_scan` is
true. Does not affect the version of `ossf/scorecard-action` used for the default scan.

# Use workflow-level read-all in caller workflows.
# permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write

steps:
- name: "Checkout code"
uses: actions/checkout@v6.0.2
with:
persist-credentials: false

- name: "Validate custom scan inputs"
if: ${{ inputs.custom_scan == true && inputs.custom_checks == '' }}
run: |
echo "::error::custom_checks must be a non-empty, comma-separated list of check names when custom_scan is true (e.g. 'Branch-Protection,Code-Review')."
exit 1

- name: "Run analysis"
if: ${{ inputs.custom_scan != true }}
uses: ossf/scorecard-action@v2.4.3
with:
file_mode: ${{inputs.file_mode}}
publish_results: ${{inputs.publish_results}}
results_file: ${{inputs.results_file}}
results_format: ${{inputs.results_format}}
repo_token: ${{secrets.repo_token}}

# Runs the Scorecard CLI directly so that `--checks` can restrict the run to
# `custom_checks`. `ossf/scorecard-action` has no equivalent input - see the
# workflow's README section for why `publish_results` is not supported here.
- name: "Run custom analysis"
if: ${{ inputs.custom_scan == true }}
env:
GITHUB_AUTH_TOKEN: ${{ secrets.repo_token }}
ENABLE_SARIF: ${{ inputs.results_format == 'sarif' && '1' || '' }}
run: |
# Generate a Scorecard policy file that enables all checks specified in `custom_checks`.
ALL_CHECKS="Binary-Artifacts Branch-Protection CI-Tests CII-Best-Practices Code-Review \
Contributors Dangerous-Workflow Dependency-Update-Tool Fuzzing License Maintained \
Packaging Pinned-Dependencies SAST Security-Policy Signed-Releases Token-Permissions \
Vulnerabilities Webhooks"

IFS=',' read -ra ENABLED <<< "${{ inputs.custom_checks }}"

{
echo "version: 1"
echo "policies:"
for check in $ALL_CHECKS; do
trimmed_check="$check"
enabled=false
for e in "${ENABLED[@]}"; do
if [ "$(echo "$e" | xargs)" = "$trimmed_check" ]; then
enabled=true
fi
done
echo " $trimmed_check:"
if $enabled; then
echo " mode: enforced"
echo " score: 1"
else
echo " mode: disabled"
fi
done
} > scorecard-policy.yml

docker run --rm \
--user "$(id -u):$(id -g)" \
-e GITHUB_AUTH_TOKEN \
-e ENABLE_SARIF \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
"ghcr.io/ossf/scorecard:${{ inputs.scorecard_version }}" \
--repo="github.com/${{ github.repository }}" \
--checks="${{ inputs.custom_checks }}" \
--policy="scorecard-policy.yml" \
--format="${{ inputs.results_format }}" \
--file-mode="${{ inputs.file_mode }}" \
--show-details \
--output="${{ inputs.results_file }}"

# Upload the results as artifacts.
# Commenting out will disable uploads of run results in SARIF format to the repository Actions tab.
# https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts
- name: "Upload artifact"
uses: actions/upload-artifact@v7.0.1
if: ${{inputs.upload_type == 'artifact' || inputs.upload_type == 'artefact'}}
with:
name: SARIF file
path: ${{inputs.results_file}}
retention-days: 5

# Upload the results to GitHub's code scanning dashboard.
# Commenting out will disable upload of results to your repo's Code Scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v4.35.3
if: ${{inputs.upload_type == 'dashboard'}}
with:
sarif_file: ${{inputs.results_file}}
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Shared github workflows created by the `digicatapult` organisation.
**Security & analysis**

- [Generate SBOM](#generate-sbom-examples)
- [Generate Security Scorecard](#generate-security-scorecard-examples)
- [Scan Secrets](#scan-secrets-examples)
- [Scan Vulnerabilities](#scan-vulnerabilities-examples)
- [ZAP Scan](#zap-scan-examples)
Expand Down Expand Up @@ -367,6 +368,53 @@ This GitHub Actions workflow generates an SBOM for a project. It allows flexibil
6. **Upload Artifact**: Optionally uploads the generated SBOM file as a workflow artifact.
7. **Upload SBOM to Dependency Track**: Optionally uploads the CycloneDX SBOM to a DT server. Docker Scout SBOMs are currently incompatible with DT due to inaccuracies in the CycloneDX spec implementation; CycloneDX-NPM is a more faithful implementation. To upload successfully, the step must have a DT hostname via the `DTRACK_HOSTNAME` secret, a parent project GUID (`DTRACK_PARENT_GUID`), and an API key (`DTRACK_APIKEY`) with both the `BOM_UPLOAD` and `PROJECT_CREATION_UPLOAD` permissions.

### [Generate Security Scorecard](.github/workflows/generate-security-scorecard.yml) ([examples](examples/generate-security-scorecard.md))

Runs an [OpenSSF Scorecard](https://github.com/ossf/scorecard) analysis against the callee repository. By default this runs the full `ossf/scorecard-action` check suite. When `custom_scan` is `true`, it instead runs the Scorecard CLI directly (via Docker), restricted to the checks listed in `custom_checks` (e.g. `Branch-Protection`), since `ossf/scorecard-action` has no input for selecting individual checks.

#### Inputs

| Input | Type | Description | Default | Required |
| ----------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -------- |
| results_file | string | File path to store the Scorecard results | `results.sarif` | true |
| results_format | string | Format of the results. Options: `sarif`, `json` | `sarif` | true |
| upload_type | string | How results are published. Options: `artifact`/`artefact` uploads a workflow artifact, `dashboard` uploads to GitHub Code Scanning | `artifact` | false |
| publish_results | boolean | Publishes results to the public [Scorecard REST API](https://api.scorecard.dev) and enables the Scorecard badge. Only supported by the default path - see the note below | `false` | false |
| file_mode | string | Method used to fetch repository files. Options: `archive`, `git` | `archive` | false |
| repo_token | string | GitHub token with read access, used to authenticate Scorecard's GitHub API calls | `""` | false |
| custom_scan | boolean | Runs the Scorecard CLI directly (via Docker), restricted to `custom_checks`, instead of the full `ossf/scorecard-action` suite | `false` | false |
| custom_checks | string | Comma-separated list of Scorecard check names to run when `custom_scan` is `true`, e.g. `Branch-Protection,Code-Review`. Required (non-empty) when `custom_scan` is `true` | `""` | false |
| scorecard_version | string | Pinned tag of the `ghcr.io/ossf/scorecard` CLI image, used only when `custom_scan` is `true` | `v5.5.0` | false |

#### Permissions

| Access | Jobs used | Level | Reason | Conditions |
| ------------------------ | ---------- | -------- | ----------------------------------------------------------------- | ---------- |
| `security-events: write` | `analysis` | Workflow | To POST new code scanning alerts based on the SARIF report | N/A |
| `id-token: write` | `analysis` | Workflow | Required by `ossf/scorecard-action` to publish results/OIDC flows | N/A |

#### Workflow Description

This GitHub Actions workflow runs an OpenSSF Scorecard analysis and publishes the results.

1. **Checkout code**: Checks out the callee repository.
2. **Validate custom scan inputs**: Fails fast with a clear error if `custom_scan` is `true` but `custom_checks` is empty, rather than letting the CLI silently run all checks.
3. **Run analysis** _(default path, `custom_scan: false`)_: Runs `ossf/scorecard-action`, which always executes the complete set of Scorecard checks.
4. **Run custom analysis** _(`custom_scan: true`)_: Runs the pinned `ghcr.io/ossf/scorecard` CLI image directly via `docker run`, passing `--checks` for the `custom_checks` list, `--format`/`--file-mode` matching the other inputs, and `--output` to the shared `results_file` path so the upload steps below work unchanged. `ENABLE_SARIF=1` is set automatically when `results_format` is `sarif`, since SARIF output is feature-flagged in the CLI.
5. **Upload artifact** / **Upload to code-scanning**: Uploads `results_file` as a workflow artifact or to GitHub's Code Scanning dashboard, depending on `upload_type`.

#### `custom_scan` limitations

> [!NOTE]
> `publish_results` and the Scorecard badge/REST API integration are **not available** when `custom_scan` is `true`.
>
> `ossf/scorecard-action`'s `publish_results` input does two things that cannot be reimplemented with the Scorecard CLI:
>
> 1. It submits results for public repositories to the [Scorecard REST API](https://api.scorecard.dev), which OSSF ingests for cross-project analysis at scale.
> 2. It's a prerequisite for displaying a [Scorecard badge](https://openssf.org/blog/2022/09/08/show-off-your-security-score-announcing-scorecards-badges/) that gets updated in real time, thanks to the above REST API.
>
> Both integrations are tied to the full check suite produced by `ossf/scorecard-action`; a partial, custom-checks run isn't a valid input for either. If you need `publish_results`, use the default (`custom_scan: false`) path instead.

### [Poetry Static checks](.github/workflows/static-checks-poetry.yml) ([examples](examples/static-checks-poetry.md))

Runs static analysis for Poetry projects (default matrix includes `pylint`, `black`, `ruff`, `mypy`, and `bandit`). Each command runs as a separate matrix job so a single failing check does not interrupt the others. Optional TruffleHog (secrets) and Semgrep CE (vulnerabilities) scans can be enabled alongside the static analysis matrix.
Expand Down
49 changes: 49 additions & 0 deletions examples/generate-security-scorecard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Security Scorecard analysis

## Using [generate-security-scorecard.yml](../.github/workflows/generate-security-scorecard.yml) in callers

`security-events: write` and `id-token: write` permissions are used. They are invoked at the workflow level for the `analysis` job.

### Default scan (full check suite, results published)

Runs the complete `ossf/scorecard-action` check suite and uploads results to GitHub Code Scanning. `publish_results: true` also submits results to the public [Scorecard REST API](https://api.scorecard.dev) and enables the Scorecard badge. These benefits are only available in this default mod, using `ossf/scorecard-action`, and cannot be achieved with the CLI.

```yaml
jobs:
generate-security-scorecard:
uses: digicatapult/shared-workflows/.github/workflows/generate-security-scorecard.yml@main
permissions:
security-events: write
id-token: write
with:
publish_results: true
upload_type: artifact
results_format: sarif
results_file: ${{ github.event.repository.name }}-scorecard.sarif
secrets:
repo_token: ${{ secrets.GITHUB_TOKEN }}
```

### Custom scan (restricted check set)

Runs only the listed checks via the Scorecard CLI directly, instead of the full suite. Useful when only a subset of checks (e.g. branch protection) is relevant to a repository, without the noise of unrelated findings.

```yaml
jobs:
generate-security-scorecard:
uses: digicatapult/shared-workflows/.github/workflows/generate-security-scorecard.yml@main
permissions:
security-events: write
id-token: write
with:
custom_scan: true
custom_checks: "Branch-Protection,Code-Review"
upload_type: artifact
results_format: sarif
results_file: ${{ github.event.repository.name }}-scorecard.sarif
secrets:
repo_token: ${{ secrets.GITHUB_TOKEN }}
```

> [!IMPORTANT]
> `publish_results` has no effect when `custom_scan` is `true`. Public transparency (submission to the Scorecard REST API) and the Scorecard README badge are both tied to the full `ossf/scorecard-action` check suite and can't be reimplemented for a partial, custom-checks run. If you need either of those, use the default (`custom_scan: false`) path instead.