Skip to content

Commit 4db124b

Browse files
authored
feat: add basic SARIF to GitHub annotation converter (#1)
Creates a MVP that converts SARIF files into GitHub Actions. This is very basic, the following limitations are present: 1. It may not work with all security tools, only Semgrep is currently confirmed as working. Thus there is no guarantee that will work for every tool. 2. The annotations are very basic thus very cryptic (i.e., not helpful for users) - this will likely be improved at some point (if it is possible) 3. Documentation is lacking and in general the project was not battle-tested yet
1 parent 8b9ffe5 commit 4db124b

24 files changed

Lines changed: 1245 additions & 0 deletions

.github/dependabot.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates
2+
version: 2
3+
updates:
4+
- package-ecosystem: uv
5+
directory: "/"
6+
schedule:
7+
interval: weekly
8+
open-pull-requests-limit: 3
9+
cooldown:
10+
default-days: 21 # 3 weeks
11+
- package-ecosystem: github-actions
12+
directory: "/"
13+
schedule:
14+
interval: weekly
15+
open-pull-requests-limit: 3
16+
cooldown:
17+
default-days: 21 # 3 weeks
18+
- package-ecosystem: docker
19+
directory: "/"
20+
schedule:
21+
interval: daily # base image needs to be bumped quickly (CPython)
22+
open-pull-requests-limit: 3
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Check Licenses
2+
on:
3+
pull_request:
4+
types:
5+
- opened
6+
- synchronize
7+
# Labels are needed to handle external contributors
8+
- labeled
9+
- unlabeled
10+
paths:
11+
- ".github/workflows/check-licenses.yaml"
12+
- "**/uv.lock"
13+
14+
jobs:
15+
default:
16+
permissions:
17+
contents: read
18+
pull-requests: write
19+
uses: saleor/saleor-internal-actions/.github/workflows/run-license-check.yaml@v1
20+
with:
21+
# List of ecosystems to scan.
22+
ecosystems: >-
23+
python
24+
javascript
25+
# Grant rules (https://github.com/anchore/grant/blob/4362dc22cf5ea9baeccfa59b2863879afe0c30d7/README.md#usage)
26+
rules: |
27+
# Explicitly allow LGPL as "*GPL*" rule will cause to reject them otherwise.
28+
- pattern: "*lgpl*"
29+
name: "allow-lgpl"
30+
mode: "allow"
31+
reason: "LGPL is allowed."
32+
- pattern: "*gpl*"
33+
name: "deny-gpl"
34+
mode: "deny"
35+
reason: "GPL licenses are not compatible with BSD-3-Clause"
36+
- pattern: "*proprietary*"
37+
name: "deny-proprietary"
38+
mode: "deny"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ["v*"]
7+
8+
permissions: {}
9+
10+
jobs:
11+
prepare:
12+
name: Prepare
13+
runs-on: ubuntu-24.04
14+
environment: publish-ghcr
15+
16+
permissions:
17+
contents: read
18+
19+
outputs:
20+
labels: ${{ steps.meta.outputs.labels }}
21+
tags: ${{ steps.metadata.outputs.tags }}
22+
version: ${{ steps.metadata.outputs.version }}
23+
steps:
24+
# Required for docker/metadata-action
25+
- name: Checkout
26+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
27+
28+
# Automatically creates release tags based on the event
29+
# e.g.:
30+
# - if pushed to a branch and branch is 'main', then the tag will be 'main'.
31+
# - if pushed a git tag, then the image tag will be the git tag value (e.g., v1.0.0)
32+
- name: Generate Metadata
33+
id: meta
34+
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
35+
with:
36+
images: ghcr.io/saleor/sarif-to-annotations
37+
38+
build-push:
39+
needs: prepare
40+
uses: saleor/saleor-internal-actions/.github/workflows/build-push-image-multi-platform.yaml@eb0c692da7bf13f5e1a82c17488b24c514dd10a1 # v1.10.0
41+
permissions:
42+
contents: read
43+
id-token: write # needed for AWS/ECR login (not used, but required permission)
44+
packages: write # needed for GHCR
45+
with:
46+
enable-ghcr: true
47+
oci-full-repository: ghcr.io/saleor/sarif-to-annotations
48+
tags: ${{ needs.prepare-variables.outputs.tags }}
49+
labels: ${{ needs.prepare-variables.outputs.labels }}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Publish to PyPI
2+
on:
3+
release:
4+
types:
5+
- released
6+
- prereleased
7+
8+
workflow_dispatch:
9+
inputs:
10+
pypi_repo:
11+
description: Which PyPI repository to use
12+
required: true
13+
type: choice
14+
options: ["pypi", "testpypi"]
15+
16+
permissions: {}
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-24.04
21+
22+
permissions:
23+
contents: read
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
28+
29+
- name: Setup Python
30+
id: setup-python
31+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
32+
with:
33+
python-version: "3.14"
34+
35+
- name: Install uv
36+
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
37+
with:
38+
python-version: ${{ steps.setup-python.outputs.python-version }}
39+
version: "0.10.7"
40+
# hash for uv-x86_64-unknown-linux-gnu.tar.gz.sha256
41+
# (from https://github.com/astral-sh/uv/releases)
42+
checksum: "9ac6cee4e379a5abfca06e78a777b26b7ba1f81cb7935b97054d80d85ac00774"
43+
44+
- name: Build source distributions and wheels
45+
run: uv build -o ./dist
46+
47+
- name: Upload Build
48+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
49+
with:
50+
name: "dist"
51+
path: "dist/"
52+
if-no-files-found: error
53+
retention-days: 5
54+
55+
publish:
56+
runs-on: ubuntu-24.04
57+
environment: publish-pypi
58+
59+
needs:
60+
- build
61+
62+
permissions:
63+
id-token: write # Required for PyPI trusted publishing
64+
65+
steps:
66+
- name: Download source distributions and wheels
67+
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
68+
with:
69+
name: dist
70+
path: dist
71+
72+
- name: Publish to PyPI
73+
if: >-
74+
github.event_name == 'release'
75+
|| (
76+
github.event_name == 'workflow_dispatch'
77+
&& github.event.inputs.pypi_repo == 'pypi'
78+
)
79+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
80+
81+
- name: Publish to TestPyPI
82+
if: >-
83+
github.event_name == 'workflow_dispatch'
84+
&& github.event.inputs.pypi_repo == 'testpypi'
85+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
86+
with:
87+
repository-url: https://test.pypi.org/legacy/

.github/workflows/test-build.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Test Build Image
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions: {}
9+
10+
jobs:
11+
test:
12+
permissions:
13+
contents: read
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
20+
21+
- name: Build Image
22+
run: docker build -t sarif-to-annotations .
23+
24+
- name: Run Simple Test
25+
run: |
26+
docker run \
27+
-v ./:/workdir \
28+
-w /workdir \
29+
--pull=never \
30+
sarif-to-annotations \
31+
./test/assets/semgrep-results-found.json

.github/workflows/test.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Run Tests
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions: {}
9+
10+
jobs:
11+
test:
12+
permissions:
13+
contents: read
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
20+
21+
- name: Setup Python
22+
id: setup-python
23+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
24+
with:
25+
python-version: "3.14"
26+
27+
- name: Install uv
28+
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
29+
with:
30+
version: "0.10.7"
31+
python-version: ${{ steps.setup-python.outputs.python-version }}
32+
activate-environment: true
33+
# hash for uv-x86_64-unknown-linux-gnu.tar.gz.sha256
34+
# (from https://github.com/astral-sh/uv/releases)
35+
checksum: "9ac6cee4e379a5abfca06e78a777b26b7ba1f81cb7935b97054d80d85ac00774"
36+
37+
- name: Install Dependencies
38+
run: |
39+
uv sync --locked --inexact --dev
40+
41+
- name: Run Pytest
42+
run: pytest

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.pyc
2+
__pycache__/
3+
.pytest_cache/
4+
.venv/
5+
dist/

Dockerfile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
FROM docker.io/python:3.14.3-slim-trixie@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456 AS deps
2+
3+
COPY \
4+
--from=ghcr.io/astral-sh/uv:0.10.8@sha256:88234bc9e09c2b2f6d176a3daf411419eb0370d450a08129257410de9cfafd2a \
5+
/uv /uvx /bin/
6+
7+
WORKDIR /app
8+
9+
ADD ./pyproject.toml ./README.md ./uv.lock /app/
10+
ADD ./src/ ./src/
11+
12+
# Keeps the settings in env var as otherwise 'uv run' will
13+
# try to reinstall the project
14+
ENV UV_LINK_MODE=copy \
15+
UV_COMPILE_BYTECODE=0 \
16+
UV_SYSTEM_PYTHON=1 \
17+
UV_PROJECT_ENVIRONMENT=/usr/local
18+
RUN --mount=type=cache,target=/root/.cache/uv \
19+
--mount=type=bind,source=uv.lock,target=uv.lock \
20+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
21+
uv sync --locked --no-editable
22+
23+
FROM docker.io/python:3.14.3-slim-trixie@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456 AS final
24+
25+
WORKDIR /config
26+
27+
COPY --from=deps \
28+
/usr/local/bin/sarif-to-annotations \
29+
/usr/local/bin/
30+
31+
COPY --from=deps \
32+
/usr/local/lib/python3.14/site-packages/ \
33+
/usr/local/lib/python3.14/site-packages/
34+
35+
FROM final AS dev
36+
37+
ENTRYPOINT [ "/usr/local/bin/sarif-to-annotations" ]

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# sarif-to-annotations
2+
3+
Python CLI that converts SARIF files into annotations that GitHub can understand.
4+
5+
## Installation
6+
7+
Either:
8+
9+
- Use the container image from GHCR (`ghcr.io/saleor/sarif-to-annotations`)
10+
<!-- - Use the action.yaml (later on) -->
11+
- Install from PyPI: `pip install sarif-to-annotations`
12+
13+
## Usage
14+
15+
```
16+
$ sarif-to-annotations <PATH_TO_SARIF>
17+
```
18+
19+
For example, if using GHCR, you can do the following:
20+
21+
```
22+
$ docker run \
23+
-v ./:/work:ro \
24+
-w /work \
25+
--rm \
26+
ghcr.io/saleor/sarif-to-annotations \
27+
./test/assets/semgrep-results-found.json
28+
```
29+
30+
Example output:
31+
32+
```
33+
::error file=.github/workflows/create-tag-with-release-pr.yml,line=83::Potential script injection through string interpolation%2C use an intermediate environment variable instead of ${{ ... }}.
34+
::error file=.github/workflows/publish-load-test.yml,line=17::Potential script injection through string interpolation%2C use an intermediate environment variable instead of ${{ ... }}.
35+
::error file=.github/workflows/publish-load-test.yml,line=43::Potential script injection through string interpolation%2C use an intermediate environment variable instead of ${{ ... }}.
36+
::error file=.github/workflows/test-env-deploy.yml,line=40::Potential script injection through string interpolation%2C use an intermediate environment variable instead of ${{ ... }}.
37+
::error file=.github/workflows/tests-and-linters.yml,line=80::Potential script injection through string interpolation%2C use an intermediate environment variable instead of ${{ ... }}.
38+
```

0 commit comments

Comments
 (0)