Skip to content
Merged
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
46 changes: 46 additions & 0 deletions .github/workflows/action-smoke-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Action Smoke Test

# issue #213: action.yml has no C code for ctest to exercise, so this is
# its regression test -- runs the composite action, at the exact commit
# under test (`uses: ./`), against this repo itself, and checks it
# actually produced a SARIF file. Separate from the "SARIF Upload" job in
# ci.yml, which builds cfusa from source; this job installs the latest
# *released* binary, since that's what a real adopting project's workflow
# does via `uses: SoundMatt/c-FuSa@<tag>`.
on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read
security-events: write

jobs:
smoke-test:
name: Action Smoke Test
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Run c-FuSa via this action
id: cfusa
uses: ./
with:
dir: '.'
sarif-file: 'action-smoke-results.sarif'
# This repo's own findings are all WARNING/INFO (see the
# "Self-qualification"/"SARIF Upload" jobs in ci.yml) -- don't
# gate this smoke test on that pre-existing backlog, only on
# the action actually running end-to-end.
fail-on-error: 'false'

- name: Verify SARIF was produced
run: |
set -euo pipefail
f="${{ steps.cfusa.outputs.sarif-file }}"
test -s "$f" || { echo "::error::expected a non-empty SARIF file at $f"; exit 1; }
grep -q '"version"' "$f" || { echo "::error::$f doesn't look like SARIF"; exit 1; }
echo "exit-code output: ${{ steps.cfusa.outputs.exit-code }}"
echo "OK: $f ($(wc -c < "$f") bytes)"
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,38 @@ Copy `.cfusa.json.template` and `.cfusa-reqs.json.template` to start a new proje

## GitHub Actions Integration

A first-party composite action (`action.yml`, this repo) installs a pinned
`cfusa` release binary, runs `cfusa check`, and uploads results to the
GitHub Security tab as SARIF — no manual build step needed in the calling
repo:

```yaml
- uses: actions/checkout@v4

- name: c-FuSa safety & security check
uses: SoundMatt/c-FuSa@v0.6.1 # pin a release tag, or use "main"
with:
dir: src/ # default: "."
strict: 'false' # gate on WARNING too, not just ERROR
changed-since: ${{ github.event_name == 'pull_request' && 'origin/main' || '' }}
fail-on-error: 'true' # default: fail the step on gated findings
```

SARIF is always uploaded (via `github/codeql-action/upload-sarif`) even
when `fail-on-error: false`, so findings still surface as code-scanning
annotations without blocking the build. See
[`action.yml`](action.yml) for every input/output, and
[`.github/workflows/action-smoke-test.yml`](.github/workflows/action-smoke-test.yml)
for a working end-to-end example (this repo dogfoods its own action).

Prefer to build from source instead of installing a release binary (e.g.
to check an unreleased fix)? Do it directly:

```yaml
- name: cfusa safety check
run: |
cmake -B build && cmake --build build
./build/cfusa check --dir src/ --format sarif --output results.sarif
./build/cfusa trace --req-coverage 80 --sec-tested 70

- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
Expand Down
146 changes: 146 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: 'c-FuSa Safety & Security Check'
description: >-
Runs c-FuSa (MISRA-C/CERT-C/CWE static analysis for functional-safety
and cybersecurity C projects), uploads results to the GitHub code
scanning tab as SARIF, and gates the workflow on findings.
author: 'SoundMatt'
branding:
icon: 'shield'
color: 'blue'

inputs:
dir:
description: 'Directory to analyze (passed to `cfusa check --dir`).'
required: false
default: '.'
version:
description: >-
c-FuSa release tag to install (e.g. "v0.6.1"). "latest" resolves the
most recent GitHub release at run time.
required: false
default: 'latest'
strict:
description: 'Exit non-zero on WARNING findings too, not just ERROR.'
required: false
default: 'false'
changed-since:
description: >-
Only report findings on lines changed since this git ref (`cfusa
check --changed-since`) — e.g. "origin/main" on a pull request, to
gate only on newly-introduced findings rather than a project's full
existing backlog. Leave empty to check the whole tree.
required: false
default: ''
sarif-file:
description: 'Path to write the SARIF report to.'
required: false
default: 'cfusa-results.sarif'
fail-on-error:
description: >-
Fail this workflow step when `cfusa check`'s exit-code gate trips
(any ERROR finding, or any WARNING finding too under `strict`).
SARIF is uploaded either way, so code-scanning annotations still
appear even when this is "false".
required: false
default: 'true'

outputs:
sarif-file:
description: 'Path to the generated SARIF report.'
value: ${{ steps.run-cfusa.outputs.sarif-file }}
exit-code:
description: "cfusa check's raw exit code (0 = clean, 1 = gated findings)."
value: ${{ steps.run-cfusa.outputs.exit-code }}

runs:
using: 'composite'
steps:
- name: Install cfusa
id: install
shell: bash
run: |
set -euo pipefail
version="${{ inputs.version }}"
if [ "$version" = "latest" ]; then
# Capture the full response into a variable before parsing it,
# rather than piping curl straight into `grep -m1`: grep -m1
# exits (closing its stdin pipe) right after its first match,
# and if curl is still writing later bytes of a large response
# body when that happens it can get SIGPIPE'd -- observed in CI
# as an intermittent "curl: (23) Failure writing output to
# destination" that, under `set -eo pipefail`, aborted this
# entire step before the actual binary download ever ran. Once
# curl has fully finished into a variable there's no reader to
# race.
resp=$(curl -fsSL https://api.github.com/repos/SoundMatt/c-FuSa/releases/latest) || {
echo "::error::Failed to query the GitHub releases API for the latest c-FuSa release."
exit 1
}
version=$(printf '%s' "$resp" | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/')
if [ -z "$version" ]; then
echo "::error::Could not resolve the latest c-FuSa release tag from the GitHub API."
exit 1
fi
fi

case "$(uname -s)-$(uname -m)" in
Linux-x86_64) asset='cfusa-linux-amd64' ;;
Darwin-arm64) asset='cfusa-darwin-arm64' ;;
*)
echo "::error::Unsupported runner platform $(uname -s)-$(uname -m)."\
" c-FuSa currently ships prebuilt cfusa-linux-amd64 and"\
" cfusa-darwin-arm64 release binaries only — see issue #211"\
" for native-Windows status."
exit 1
;;
esac

url="https://github.com/SoundMatt/c-FuSa/releases/download/${version}/${asset}"
echo "Installing cfusa ${version} (${asset})"
curl -fsSL -o cfusa-bin "$url"
chmod +x cfusa-bin
echo "bin=$(pwd)/cfusa-bin" >> "$GITHUB_OUTPUT"
./cfusa-bin version

- name: Run cfusa check
id: run-cfusa
shell: bash
run: |
set -uo pipefail
args=(check --dir "${{ inputs.dir }}" --format sarif --output "${{ inputs.sarif-file }}")
if [ "${{ inputs.strict }}" = "true" ]; then
args+=(--strict)
fi
if [ -n "${{ inputs.changed-since }}" ]; then
args+=(--changed-since "${{ inputs.changed-since }}")
fi

"${{ steps.install.outputs.bin }}" "${args[@]}"
rc=$?

echo "exit-code=$rc" >> "$GITHUB_OUTPUT"
echo "sarif-file=${{ inputs.sarif-file }}" >> "$GITHUB_OUTPUT"

# rc 0 (clean) and rc 1 (gated findings, handled by the separate
# "Enforce findings gate" step below so SARIF still gets uploaded
# first) are both normal outcomes here. rc 2/3 (bad --changed-since
# ref, or a failed SARIF write) are tool/usage errors -- always
# fatal, independent of fail-on-error, since there is no SARIF
# file to upload in either case.
if [ "$rc" -gt 1 ]; then
echo "::error::cfusa check exited $rc (usage error, not a findings gate) — see the log above."
exit "$rc"
fi
exit 0

- name: Upload SARIF to code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ inputs.sarif-file }}

- name: Enforce findings gate
if: ${{ inputs.fail-on-error == 'true' && steps.run-cfusa.outputs.exit-code == '1' }}
shell: bash
run: |
echo "::error::cfusa check reported gating findings (ERROR${{ inputs.strict == 'true' && ', or WARNING under strict mode,' || '' }}). See the Security tab (code scanning) for details."
exit 1
Loading