Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc18da5f89
|
||
|
|
b7abc63460
|
||
|
|
0a7a8888f5
|
||
|
|
b15afa1238 | ||
|
|
e7fb3ac106 | ||
|
|
8f7d877f5e | ||
|
|
208f83e0d1 | ||
|
|
fc2b8e4d6a | ||
|
|
ac0a326166
|
||
|
|
4931584e62
|
No files matched your search
@@ -1,4 +1,4 @@
|
||||
name: Lint & Security
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -12,7 +12,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
precommit-and-security:
|
||||
lint-and-security:
|
||||
name: lint-and-security
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
@@ -70,10 +70,40 @@ jobs:
|
||||
| "\($d.name)@\($d.version) -> \(.id) [fix: \(if (.fix_versions | length) > 0 then (.fix_versions | join(", ")) else "none" end)]"
|
||||
' pip-audit.json
|
||||
|
||||
# Fail only on fixable findings (mirrors the Grype policy in security-scan.yml).
|
||||
# The type guard ensures a crashed/empty report fails rather than silently passing.
|
||||
# Fail only on fixable findings. The type guard ensures a crashed or empty
|
||||
# report fails rather than silently passing. Dependency security updates
|
||||
# are handled by the central Renovate job.
|
||||
jq -e '
|
||||
(.dependencies | type) == "array"
|
||||
and
|
||||
( [ .dependencies[] | (.vulns // [])[] | select((.fix_versions | length) > 0) ] | length == 0 )
|
||||
' pip-audit.json
|
||||
|
||||
tests:
|
||||
name: tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11", "3.13"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install Poetry
|
||||
run: pip install "poetry>=2,<3"
|
||||
|
||||
- name: Install project and dependencies
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Run tests
|
||||
run: poetry run pytest
|
||||
@@ -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/mirro",
|
||||
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:
|
||||
pull_request:
|
||||
@@ -12,7 +12,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
precommit-and-security:
|
||||
lint-and-security:
|
||||
name: lint-and-security
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
@@ -70,10 +70,40 @@ jobs:
|
||||
| "\($d.name)@\($d.version) -> \(.id) [fix: \(if (.fix_versions | length) > 0 then (.fix_versions | join(", ")) else "none" end)]"
|
||||
' pip-audit.json
|
||||
|
||||
# Fail only on fixable findings (mirrors the Grype policy in security-scan.yml).
|
||||
# The type guard ensures a crashed/empty report fails rather than silently passing.
|
||||
# Fail only on fixable findings. The type guard ensures a crashed or empty
|
||||
# report fails rather than silently passing. Dependency security updates
|
||||
# are handled by the central Renovate job.
|
||||
jq -e '
|
||||
(.dependencies | type) == "array"
|
||||
and
|
||||
( [ .dependencies[] | (.vulns // [])[] | select((.fix_versions | length) > 0) ] | length == 0 )
|
||||
' pip-audit.json
|
||||
|
||||
tests:
|
||||
name: tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11", "3.13"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install Poetry
|
||||
run: pip install "poetry>=2,<3"
|
||||
|
||||
- name: Install project and dependencies
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Run tests
|
||||
run: poetry run pytest
|
||||
+1
-14
@@ -63,7 +63,7 @@ poetry.lock locked dependency graph
|
||||
README.md user-facing documentation
|
||||
LICENCE GPL-3.0-or-later licence text
|
||||
.pre-commit-config.yaml Bandit, 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
|
||||
mirro.png README logo
|
||||
```
|
||||
@@ -390,19 +390,6 @@ Gitea pull request workflow:
|
||||
-> pip-audit dependency audit
|
||||
```
|
||||
|
||||
Scheduled/manual security workflow:
|
||||
|
||||
```text
|
||||
.gitea/workflows/security-scan.yml
|
||||
-> install Cosign
|
||||
-> verify and install Syft
|
||||
-> verify and install 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -169,7 +169,7 @@ Files with history in /foo/bar:
|
||||
|
||||
## Installation
|
||||
|
||||
### From GuardUtils package repo
|
||||
### From the SysMD package repository
|
||||
|
||||
This is the preferred method of installation.
|
||||
|
||||
@@ -179,7 +179,7 @@ This is the preferred method of installation.
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /usr/share/keyrings
|
||||
curl -fsSL https://repo.sysmd.uk/guardutils/guardutils.gpg | sudo gpg --dearmor -o /usr/share/keyrings/guardutils.gpg
|
||||
curl -fsSL https://repo.sysmd.uk/sysmd/sysmd.gpg | sudo gpg --dearmor -o /usr/share/keyrings/sysmd.gpg
|
||||
```
|
||||
|
||||
The GPG fingerprint is `0032C71FA6A11EF9567D4434C5C06BD4603C28B1`.
|
||||
@@ -187,7 +187,7 @@ The GPG fingerprint is `0032C71FA6A11EF9567D4434C5C06BD4603C28B1`.
|
||||
#### 2) Add the APT source
|
||||
|
||||
```bash
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/guardutils.gpg] https://repo.sysmd.uk/guardutils/debian stable main" | sudo tee /etc/apt/sources.list.d/guardutils.list
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/sysmd.gpg] https://repo.sysmd.uk/sysmd/debian stable main" | sudo tee /etc/apt/sources.list.d/sysmd.list
|
||||
```
|
||||
|
||||
#### 3) Update and install
|
||||
@@ -202,20 +202,20 @@ sudo apt install mirro
|
||||
#### 1) Import the GPG key
|
||||
|
||||
```
|
||||
sudo rpm --import https://repo.sysmd.uk/guardutils/guardutils.gpg
|
||||
sudo rpm --import https://repo.sysmd.uk/sysmd/sysmd.gpg
|
||||
```
|
||||
|
||||
#### 2) Add the repository configuration
|
||||
|
||||
```
|
||||
sudo tee /etc/yum.repos.d/guardutils.repo > /dev/null << 'EOF'
|
||||
[guardutils]
|
||||
name=GuardUtils Repository
|
||||
baseurl=https://repo.sysmd.uk/guardutils/rpm/$basearch
|
||||
sudo tee /etc/yum.repos.d/sysmd.repo > /dev/null << 'EOF'
|
||||
[sysmd]
|
||||
name=SysMD Repository
|
||||
baseurl=https://repo.sysmd.uk/sysmd/rpm/$basearch
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1
|
||||
gpgkey=https://repo.sysmd.uk/guardutils/guardutils.gpg
|
||||
gpgkey=https://repo.sysmd.uk/sysmd/sysmd.gpg
|
||||
EOF
|
||||
```
|
||||
|
||||
|
||||
Generated
+16
-16
@@ -1,15 +1,15 @@
|
||||
# This file is automatically @generated by Poetry 2.5.0 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.4.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "argcomplete"
|
||||
version = "3.6.3"
|
||||
version = "3.7.2"
|
||||
description = "Bash tab completion for argparse"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce"},
|
||||
{file = "argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c"},
|
||||
{file = "argcomplete-3.7.2-py3-none-any.whl", hash = "sha256:6029205678bdd9c1c728a155f5f9ecf5812393f969eef58807641a2bc2aa5b19"},
|
||||
{file = "argcomplete-3.7.2.tar.gz", hash = "sha256:aad8b69a0b9969edb62db0d1752354c0d50717b10e0cbb00e2a958381b9fc6b9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -255,14 +255,14 @@ testing = ["coverage", "pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "3.8.0"
|
||||
version = "4.6.2"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"},
|
||||
{file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"},
|
||||
{file = "pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e"},
|
||||
{file = "pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -289,14 +289,14 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
version = "9.1.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"},
|
||||
{file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"},
|
||||
{file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"},
|
||||
{file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -311,14 +311,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "7.0.0"
|
||||
version = "7.1.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"},
|
||||
{file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"},
|
||||
{file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"},
|
||||
{file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -449,4 +449,4 @@ python-discovery = ">=1.6"
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.11,<4.0"
|
||||
content-hash = "7b5985878a19a41fdd006a82ae10fa6ceff2e470d29db5e2ad1501997be053c1"
|
||||
content-hash = "44a3b9a81a4b2009158668ad96dce0e68d268170664ab81513cd4352f475f94a"
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mirro"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
description = "A safe editing wrapper: edits a temp copy, compares, and saves original backup if changed."
|
||||
authors = [{ name = "Marco D'Aleo", email = "marco@marcodaleo.com" }]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -23,7 +23,7 @@ packages = [{include = "mirro", from = "src"}]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^9.0.1"
|
||||
pytest-cov = "^7.0.0"
|
||||
pre-commit = "^3.8"
|
||||
pre-commit = "^4.0.0"
|
||||
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
|
||||
Reference in new issue
Block a user