Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c598887ff | ||
|
|
1cdd09ecfd | ||
|
|
807130ffdb
|
||
|
|
40840b5250
|
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
name: Lint & Security
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -12,7 +12,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
precommit-and-security:
|
lint-and-security:
|
||||||
name: lint-and-security
|
name: lint-and-security
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
@@ -70,8 +70,9 @@ jobs:
|
|||||||
| "\($d.name)@\($d.version) -> \(.id) [fix: \(if (.fix_versions | length) > 0 then (.fix_versions | join(", ")) else "none" end)]"
|
| "\($d.name)@\($d.version) -> \(.id) [fix: \(if (.fix_versions | length) > 0 then (.fix_versions | join(", ")) else "none" end)]"
|
||||||
' pip-audit.json
|
' pip-audit.json
|
||||||
|
|
||||||
# Fail only on fixable findings (mirrors the Grype policy in security-scan.yml).
|
# Fail only on fixable findings. The type guard ensures a crashed or empty
|
||||||
# The type guard ensures a crashed/empty report fails rather than silently passing.
|
# report fails rather than silently passing. Dependency security updates
|
||||||
|
# are handled by the central Renovate job.
|
||||||
jq -e '
|
jq -e '
|
||||||
(.dependencies | type) == "array"
|
(.dependencies | type) == "array"
|
||||||
and
|
and
|
||||||
@@ -82,6 +83,10 @@ jobs:
|
|||||||
name: tests
|
name: tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.11", "3.13"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -92,7 +97,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Install Poetry
|
- name: Install Poetry
|
||||||
run: pip install "poetry>=2,<3"
|
run: pip install "poetry>=2,<3"
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
name: Security Scan
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: 27 8 * * *
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
security-scan:
|
|
||||||
runs-on: running-man
|
|
||||||
|
|
||||||
env:
|
|
||||||
TARGET_DIR: .
|
|
||||||
COSIGN_VERSION: v3.0.5
|
|
||||||
SYFT_VERSION: v1.42.3
|
|
||||||
GRYPE_VERSION: v0.110.0
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Cosign (bootstrap)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
FILE="cosign-linux-amd64"
|
|
||||||
|
|
||||||
curl -fLO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/${FILE}
|
|
||||||
|
|
||||||
chmod +x ${FILE}
|
|
||||||
mv ${FILE} /usr/local/bin/cosign
|
|
||||||
|
|
||||||
cosign version
|
|
||||||
|
|
||||||
- name: Install Syft (verified)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION_NO_V="${SYFT_VERSION#v}"
|
|
||||||
FILE="syft_${VERSION_NO_V}_linux_amd64.tar.gz"
|
|
||||||
BASE_URL="https://github.com/anchore/syft/releases/download/${SYFT_VERSION}"
|
|
||||||
|
|
||||||
curl -fLO ${BASE_URL}/${FILE}
|
|
||||||
curl -fLO ${BASE_URL}/syft_${VERSION_NO_V}_checksums.txt
|
|
||||||
curl -fLO ${BASE_URL}/syft_${VERSION_NO_V}_checksums.txt.sig
|
|
||||||
curl -fLO ${BASE_URL}/syft_${VERSION_NO_V}_checksums.txt.pem
|
|
||||||
|
|
||||||
cosign verify-blob \
|
|
||||||
--signature syft_${VERSION_NO_V}_checksums.txt.sig \
|
|
||||||
--certificate syft_${VERSION_NO_V}_checksums.txt.pem \
|
|
||||||
--certificate-identity-regexp "https://github.com/anchore/syft" \
|
|
||||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
|
||||||
syft_${VERSION_NO_V}_checksums.txt
|
|
||||||
|
|
||||||
CHECKSUM_LINE=$(grep " ${FILE}$" syft_${VERSION_NO_V}_checksums.txt)
|
|
||||||
if [ -z "$CHECKSUM_LINE" ]; then
|
|
||||||
echo "Missing checksum entry for ${FILE}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$CHECKSUM_LINE" | sha256sum -c -
|
|
||||||
|
|
||||||
tar -xzf ${FILE}
|
|
||||||
mv syft /usr/local/bin/
|
|
||||||
|
|
||||||
syft version
|
|
||||||
|
|
||||||
- name: Install Grype (verified)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION_NO_V="${GRYPE_VERSION#v}"
|
|
||||||
FILE="grype_${VERSION_NO_V}_linux_amd64.tar.gz"
|
|
||||||
BASE_URL="https://github.com/anchore/grype/releases/download/${GRYPE_VERSION}"
|
|
||||||
|
|
||||||
curl -fLO ${BASE_URL}/${FILE}
|
|
||||||
curl -fLO ${BASE_URL}/grype_${VERSION_NO_V}_checksums.txt
|
|
||||||
curl -fLO ${BASE_URL}/grype_${VERSION_NO_V}_checksums.txt.sig
|
|
||||||
curl -fLO ${BASE_URL}/grype_${VERSION_NO_V}_checksums.txt.pem
|
|
||||||
|
|
||||||
cosign verify-blob \
|
|
||||||
--signature grype_${VERSION_NO_V}_checksums.txt.sig \
|
|
||||||
--certificate grype_${VERSION_NO_V}_checksums.txt.pem \
|
|
||||||
--certificate-identity-regexp "https://github.com/anchore/grype" \
|
|
||||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
|
||||||
grype_${VERSION_NO_V}_checksums.txt
|
|
||||||
|
|
||||||
CHECKSUM_LINE=$(grep " ${FILE}$" grype_${VERSION_NO_V}_checksums.txt)
|
|
||||||
if [ -z "$CHECKSUM_LINE" ]; then
|
|
||||||
echo "Missing checksum entry for ${FILE}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$CHECKSUM_LINE" | sha256sum -c -
|
|
||||||
|
|
||||||
tar -xzf ${FILE}
|
|
||||||
mv grype /usr/local/bin/
|
|
||||||
|
|
||||||
grype version
|
|
||||||
|
|
||||||
- name: Generate SBOM
|
|
||||||
working-directory: ${{ env.TARGET_DIR }}
|
|
||||||
run: |
|
|
||||||
syft dir:. -o json > sbom.json
|
|
||||||
|
|
||||||
- name: Show SBOM contents
|
|
||||||
working-directory: ${{ env.TARGET_DIR }}
|
|
||||||
run: |
|
|
||||||
echo "Packages discovered by Syft:"
|
|
||||||
jq -r '.artifacts[] | "\(.name)@\(.version) [\(.type)]"' sbom.json | sort
|
|
||||||
|
|
||||||
- name: Run Grype scan (JSON)
|
|
||||||
id: audit
|
|
||||||
continue-on-error: true
|
|
||||||
working-directory: ${{ env.TARGET_DIR }}
|
|
||||||
run: |
|
|
||||||
grype sbom:sbom.json -o json > grype.json
|
|
||||||
|
|
||||||
echo "Vulnerabilities (fixable only):"
|
|
||||||
jq -r '
|
|
||||||
.matches[]
|
|
||||||
| select((.vulnerability.fix.versions | length) > 0)
|
|
||||||
| "\(.artifact.name)@\(.artifact.version) -> \(.vulnerability.id) [\(.vulnerability.severity)] | fixed: \(.vulnerability.fix.versions[0])"
|
|
||||||
' grype.json
|
|
||||||
|
|
||||||
# Fail only on fixable MEDIUM/HIGH/CRITICAL
|
|
||||||
jq -e '
|
|
||||||
[
|
|
||||||
.matches[]?
|
|
||||||
| select(
|
|
||||||
(
|
|
||||||
.vulnerability.severity == "Medium" or
|
|
||||||
.vulnerability.severity == "High" or
|
|
||||||
.vulnerability.severity == "Critical"
|
|
||||||
)
|
|
||||||
and
|
|
||||||
(
|
|
||||||
(.vulnerability.fix.versions | length) > 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
| length == 0
|
|
||||||
' grype.json
|
|
||||||
|
|
||||||
- name: Show full Grype table
|
|
||||||
working-directory: ${{ env.TARGET_DIR }}
|
|
||||||
run: |
|
|
||||||
echo "Full Grype report:"
|
|
||||||
grype sbom:sbom.json -o table
|
|
||||||
|
|
||||||
- name: Notify Node-RED on vulnerabilities
|
|
||||||
if: steps.audit.outcome == 'failure'
|
|
||||||
working-directory: ${{ env.TARGET_DIR }}
|
|
||||||
run: |
|
|
||||||
jq '
|
|
||||||
{
|
|
||||||
repo: "guardutils/chguard",
|
|
||||||
summary: (
|
|
||||||
"Total: " +
|
|
||||||
(
|
|
||||||
[
|
|
||||||
.matches[]
|
|
||||||
| select((.vulnerability.fix.versions | length) > 0)
|
|
||||||
] | length | tostring
|
|
||||||
)
|
|
||||||
),
|
|
||||||
vulnerabilities: [
|
|
||||||
.matches[]
|
|
||||||
| select((.vulnerability.fix.versions | length) > 0)
|
|
||||||
| {
|
|
||||||
library: .artifact.name,
|
|
||||||
cve: .vulnerability.id,
|
|
||||||
severity: .vulnerability.severity,
|
|
||||||
installed: .artifact.version,
|
|
||||||
fixed: (.vulnerability.fix.versions[0]),
|
|
||||||
title: .vulnerability.description,
|
|
||||||
url: .vulnerability.dataSource
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
' grype.json \
|
|
||||||
| curl -s -X POST https://nodered.sysmd.uk/vulns-alert \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
--data-binary @-
|
|
||||||
|
|
||||||
- name: Fail workflow if vulnerabilities found
|
|
||||||
if: steps.audit.outcome == 'failure'
|
|
||||||
run: exit 1
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Lint & Security
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -12,7 +12,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
precommit-and-security:
|
lint-and-security:
|
||||||
name: lint-and-security
|
name: lint-and-security
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
@@ -70,8 +70,9 @@ jobs:
|
|||||||
| "\($d.name)@\($d.version) -> \(.id) [fix: \(if (.fix_versions | length) > 0 then (.fix_versions | join(", ")) else "none" end)]"
|
| "\($d.name)@\($d.version) -> \(.id) [fix: \(if (.fix_versions | length) > 0 then (.fix_versions | join(", ")) else "none" end)]"
|
||||||
' pip-audit.json
|
' pip-audit.json
|
||||||
|
|
||||||
# Fail only on fixable findings (mirrors the Grype policy in security-scan.yml).
|
# Fail only on fixable findings. The type guard ensures a crashed or empty
|
||||||
# The type guard ensures a crashed/empty report fails rather than silently passing.
|
# report fails rather than silently passing. Dependency security updates
|
||||||
|
# are handled by the central Renovate job.
|
||||||
jq -e '
|
jq -e '
|
||||||
(.dependencies | type) == "array"
|
(.dependencies | type) == "array"
|
||||||
and
|
and
|
||||||
@@ -82,6 +83,10 @@ jobs:
|
|||||||
name: tests
|
name: tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.11", "3.13"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -92,7 +97,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Install Poetry
|
- name: Install Poetry
|
||||||
run: pip install "poetry>=2,<3"
|
run: pip install "poetry>=2,<3"
|
||||||
+1
-12
@@ -69,7 +69,7 @@ pyproject.toml Poetry package metadata and console script
|
|||||||
poetry.lock locked dependency graph
|
poetry.lock locked dependency graph
|
||||||
README.md user-facing documentation
|
README.md user-facing documentation
|
||||||
.pre-commit-config.yaml Black and generic pre-commit hooks
|
.pre-commit-config.yaml Black and generic pre-commit hooks
|
||||||
.gitea/workflows/ lint, dependency audit, SBOM and Grype workflows
|
.gitea/workflows/ lint and dependency-audit workflows
|
||||||
dist/ built release artifacts, not source
|
dist/ built release artifacts, not source
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -595,17 +595,6 @@ Gitea pull request workflow:
|
|||||||
-> pip-audit dependency audit
|
-> pip-audit dependency audit
|
||||||
```
|
```
|
||||||
|
|
||||||
Scheduled/manual security workflow:
|
|
||||||
|
|
||||||
```text
|
|
||||||
.gitea/workflows/security-scan.yml
|
|
||||||
-> install verified Cosign, Syft, and Grype
|
|
||||||
-> generate SBOM
|
|
||||||
-> scan for vulnerabilities
|
|
||||||
-> notify Node-RED on fixable Medium/High/Critical vulnerabilities
|
|
||||||
-> fail workflow on those vulnerabilities
|
|
||||||
```
|
|
||||||
|
|
||||||
Pre-commit currently includes Bandit, Black, trailing whitespace, EOF, YAML, and TOML checks.
|
Pre-commit currently includes Bandit, Black, trailing whitespace, EOF, YAML, and TOML checks.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Generated
+4
-4
@@ -54,14 +54,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "4.1.0"
|
version = "4.2.4"
|
||||||
description = "A platform independent file lock."
|
description = "A platform independent file lock."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.11"
|
||||||
groups = ["main", "dev"]
|
groups = ["main", "dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "filelock-4.1.0-py3-none-any.whl", hash = "sha256:2ce9818e3e2d8f284c1a964414447ef148d42a5fd5e2a477a7118e574b293ec1"},
|
{file = "filelock-4.2.4-py3-none-any.whl", hash = "sha256:301241438c57936ef93f4243a94f2559022268bfcf9b375a9a72eb0f8db8358e"},
|
||||||
{file = "filelock-4.1.0.tar.gz", hash = "sha256:ad7f724afef953e731b1cc39bcd3a09166d72ed7fcdf29e6e88b1c3235c6715d"},
|
{file = "filelock-4.2.4.tar.gz", hash = "sha256:c6d8663535e1650ee430507b4ffbac7b464dd64b7647ea0112a9f406499d163d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in new issue
Block a user