GitRacks 28.1.0+gitracks.1
This commit is contained in:
commit
f73f768c5b
2 files changed
+255
No files matched your search
@@ -0,0 +1,240 @@
|
||||
# GitRacks security scanning: Trivy (dependencies, licenses, SBOM), Gitleaks (secrets) and Semgrep (SAST).
|
||||
# Each scanner's SARIF goes to GitRacks, which tracks the findings and sets the gitracks/security status (docs/fork/security.md).
|
||||
#
|
||||
# Release pins for runners without the tools on PATH: bump the versions with their sha256 from each release's checksums file.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
runs-on:
|
||||
description: Runner label
|
||||
type: string
|
||||
default: ubuntu-latest
|
||||
scanners:
|
||||
description: Comma-separated subset of trivy,gitleaks,semgrep
|
||||
type: string
|
||||
default: trivy,gitleaks,semgrep
|
||||
fail-on:
|
||||
description: Fail the gitracks/security status while findings this severe or worse are open (critical, high, medium, low, note, none)
|
||||
type: string
|
||||
default: high
|
||||
semgrep-config:
|
||||
description: Semgrep --config; empty uses the bundled rules, p/default needs network
|
||||
type: string
|
||||
default: ""
|
||||
sbom:
|
||||
description: Upload a CycloneDX SBOM from Trivy as the artifact sbom, in a separate job (needs actions/upload-artifact)
|
||||
type: boolean
|
||||
default: true
|
||||
offline:
|
||||
description: Use the Trivy DB already in TRIVY_CACHE_DIR instead of downloading it
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
TRIVY_VERSION: 0.76.0
|
||||
TRIVY_SHA256_X64: e24b1c51293c1b15e4ba8d45bbc7891ba053f90278ec2698f3562c993e145fe7
|
||||
TRIVY_SHA256_ARM64: aab21e677a7b7071fc79f1711480840754c18a1b9d9cefa7ccd06af63a0e8f4c
|
||||
GITLEAKS_VERSION: 8.30.1
|
||||
GITLEAKS_SHA256_X64: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
|
||||
GITLEAKS_SHA256_ARM64: e4a487ee7ccd7d3a7f7ec08657610aa3606637dab924210b3aee62570fb4b080
|
||||
SEMGREP_VERSION: 1.180.0
|
||||
SEMGREP_ENABLE_VERSION_CHECK: "0"
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
env:
|
||||
TOKEN: ${{ gitea.token }}
|
||||
SCANNERS: ",${{ inputs.scanners }},"
|
||||
steps:
|
||||
- name: Check out
|
||||
run: |
|
||||
git init -q .
|
||||
auth=$(printf 'x-access-token:%s' "$TOKEN" | base64 | tr -d '\n')
|
||||
git -c http.extraHeader="Authorization: Basic $auth" fetch -q --depth=1 "${{ gitea.server_url }}/${{ gitea.repository }}.git" "${{ gitea.sha }}"
|
||||
git checkout -q FETCH_HEAD
|
||||
out="${RUNNER_TEMP:-/tmp}/gitracks-security"
|
||||
mkdir -p "$out/bin"
|
||||
echo "OUT=$out" >> "$GITHUB_ENV"
|
||||
echo "$out/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install missing scanners
|
||||
run: |
|
||||
case "$(uname -m)" in
|
||||
x86_64) trivy_arch=Linux-64bit gitleaks_arch=linux_x64 trivy_sum=$TRIVY_SHA256_X64 gitleaks_sum=$GITLEAKS_SHA256_X64 ;;
|
||||
aarch64 | arm64) trivy_arch=Linux-ARM64 gitleaks_arch=linux_arm64 trivy_sum=$TRIVY_SHA256_ARM64 gitleaks_sum=$GITLEAKS_SHA256_ARM64 ;;
|
||||
esac
|
||||
fetch() { # url sha256 binary
|
||||
curl -sSfL "$1" -o "$OUT/download.tar.gz"
|
||||
echo "$2 $OUT/download.tar.gz" | sha256sum -c - > /dev/null
|
||||
tar -xzf "$OUT/download.tar.gz" -C "$OUT/bin" "$3"
|
||||
rm "$OUT/download.tar.gz"
|
||||
}
|
||||
if [[ $SCANNERS == *,trivy,* ]] && ! command -v trivy > /dev/null; then
|
||||
fetch "https://github.com/aquasecurity/trivy/releases/download/v$TRIVY_VERSION/trivy_${TRIVY_VERSION}_$trivy_arch.tar.gz" "$trivy_sum" trivy
|
||||
fi
|
||||
if [[ $SCANNERS == *,gitleaks,* ]] && ! command -v gitleaks > /dev/null; then
|
||||
fetch "https://github.com/gitleaks/gitleaks/releases/download/v$GITLEAKS_VERSION/gitleaks_${GITLEAKS_VERSION}_$gitleaks_arch.tar.gz" "$gitleaks_sum" gitleaks
|
||||
fi
|
||||
if [[ $SCANNERS == *,semgrep,* ]] && ! command -v semgrep > /dev/null; then
|
||||
python3 -m venv "$OUT/semgrep"
|
||||
"$OUT/semgrep/bin/pip" install -q "semgrep==$SEMGREP_VERSION"
|
||||
ln -s "$OUT/semgrep/bin/semgrep" "$OUT/bin/semgrep"
|
||||
fi
|
||||
|
||||
- name: Trivy
|
||||
if: always() && contains(env.SCANNERS, ',trivy,')
|
||||
run: |
|
||||
flags=()
|
||||
if [ "${{ inputs.offline }}" = true ]; then flags=(--skip-db-update --skip-java-db-update); fi
|
||||
# Trivy reads Go licenses from $GOPATH/pkg/mod; an empty one keeps results independent of other jobs on the host
|
||||
GOPATH="$OUT/gopath" trivy fs --quiet "${flags[@]}" --scanners vuln,license --format sarif -o "$OUT/trivy.sarif" .
|
||||
|
||||
- name: Gitleaks
|
||||
if: always() && contains(env.SCANNERS, ',gitleaks,')
|
||||
run: gitleaks dir . --no-banner --redact --report-format sarif --report-path "$OUT/gitleaks.sarif" --exit-code 0
|
||||
|
||||
- name: Semgrep
|
||||
if: always() && contains(env.SCANNERS, ',semgrep,')
|
||||
env:
|
||||
CONFIG: ${{ inputs.semgrep-config }}
|
||||
run: |
|
||||
config=$CONFIG
|
||||
if [ -z "$config" ]; then
|
||||
# in the working directory, so rule IDs carry no runner-specific path prefix
|
||||
config=.gitracks-semgrep.yml
|
||||
cat > "$config" << 'RULES'
|
||||
rules:
|
||||
- id: gitracks.python.eval
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
message: eval() and exec() run arbitrary code; parse the input instead, e.g. with ast.literal_eval.
|
||||
pattern-either:
|
||||
- pattern: eval($X)
|
||||
- pattern: exec($X)
|
||||
metadata: {category: security, cwe: "CWE-95"}
|
||||
- id: gitracks.python.deserialization
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
message: Deserializing untrusted data with this module can run code; use JSON for untrusted input.
|
||||
pattern-either:
|
||||
- pattern: pickle.loads(...)
|
||||
- pattern: pickle.load(...)
|
||||
- pattern: marshal.loads(...)
|
||||
metadata: {category: security, cwe: "CWE-502"}
|
||||
- id: gitracks.python.yaml-load
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
message: yaml.load without SafeLoader can build arbitrary objects; use yaml.safe_load.
|
||||
patterns:
|
||||
- pattern: yaml.load(...)
|
||||
- pattern-not: yaml.load(..., Loader=yaml.SafeLoader, ...)
|
||||
- pattern-not: yaml.load(..., Loader=SafeLoader, ...)
|
||||
metadata: {category: security, cwe: "CWE-502"}
|
||||
- id: gitracks.python.shell
|
||||
languages: [python]
|
||||
severity: WARNING
|
||||
message: Shell commands built from variables allow command injection; pass an argument list without shell=True.
|
||||
pattern-either:
|
||||
- pattern: subprocess.$F(..., shell=True, ...)
|
||||
- pattern: os.system($X)
|
||||
metadata: {category: security, cwe: "CWE-78"}
|
||||
- id: gitracks.python.sql-format
|
||||
languages: [python]
|
||||
severity: WARNING
|
||||
message: SQL built with string formatting allows SQL injection; use query parameters.
|
||||
pattern-either:
|
||||
- pattern: $C.execute(f"...")
|
||||
- pattern: $C.execute("..." % ...)
|
||||
- pattern: $C.execute("..." + ...)
|
||||
- pattern: $C.execute("...".format(...))
|
||||
metadata: {category: security, cwe: "CWE-89"}
|
||||
- id: gitracks.js.eval
|
||||
languages: [javascript, typescript]
|
||||
severity: ERROR
|
||||
message: Evaluating strings as code allows code injection.
|
||||
pattern-either:
|
||||
- pattern: eval($X)
|
||||
- pattern: new Function(...)
|
||||
metadata: {category: security, cwe: "CWE-95"}
|
||||
- id: gitracks.js.child-process
|
||||
languages: [javascript, typescript]
|
||||
severity: WARNING
|
||||
message: child_process.exec runs a shell; use execFile with an argument list.
|
||||
pattern: require('child_process').exec(...)
|
||||
metadata: {category: security, cwe: "CWE-78"}
|
||||
- id: gitracks.go.tls-skip-verify
|
||||
languages: [go]
|
||||
severity: ERROR
|
||||
message: InsecureSkipVerify disables TLS certificate checks.
|
||||
pattern: "tls.Config{..., InsecureSkipVerify: true, ...}"
|
||||
metadata: {category: security, cwe: "CWE-295"}
|
||||
- id: gitracks.go.shell
|
||||
languages: [go]
|
||||
severity: WARNING
|
||||
message: Running a command through a shell allows command injection; pass the arguments to exec.Command directly.
|
||||
pattern-either:
|
||||
- pattern: exec.Command("sh", "-c", ...)
|
||||
- pattern: exec.Command("bash", "-c", ...)
|
||||
metadata: {category: security, cwe: "CWE-78"}
|
||||
- id: gitracks.go.sql-format
|
||||
languages: [go]
|
||||
severity: WARNING
|
||||
message: SQL built with fmt.Sprintf allows SQL injection; use query parameters.
|
||||
pattern-either:
|
||||
- pattern: $DB.Query(fmt.Sprintf(...), ...)
|
||||
- pattern: $DB.Exec(fmt.Sprintf(...), ...)
|
||||
- pattern: $DB.QueryRow(fmt.Sprintf(...), ...)
|
||||
metadata: {category: security, cwe: "CWE-89"}
|
||||
RULES
|
||||
fi
|
||||
semgrep scan --quiet --metrics off --disable-version-check --config "$config" --sarif -o "$OUT/semgrep.sarif" .
|
||||
|
||||
- name: Upload findings
|
||||
if: always()
|
||||
env:
|
||||
URL: ${{ gitea.api_url }}/repos/${{ gitea.repository }}/actions/reports?format=sarif&fail_on=${{ inputs.fail-on }}
|
||||
run: |
|
||||
status=0
|
||||
for name in trivy gitleaks semgrep; do
|
||||
[[ $SCANNERS == *,$name,* ]] || continue
|
||||
if [ -s "$OUT/$name.sarif" ]; then
|
||||
curl -sSf -o /dev/null -H "Authorization: token $TOKEN" -H "Content-Type: application/octet-stream" --data-binary "@$OUT/$name.sarif" "$URL&name=$name" || status=1
|
||||
else
|
||||
echo "::warning::$name wrote no report"
|
||||
status=1
|
||||
fi
|
||||
done
|
||||
exit $status
|
||||
|
||||
# a job of its own: runners fetch every action of a job before it starts, which would break offline scans
|
||||
sbom:
|
||||
if: inputs.sbom
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
env:
|
||||
TOKEN: ${{ gitea.token }}
|
||||
steps:
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
git init -q .
|
||||
auth=$(printf 'x-access-token:%s' "$TOKEN" | base64 | tr -d '\n')
|
||||
git -c http.extraHeader="Authorization: Basic $auth" fetch -q --depth=1 "${{ gitea.server_url }}/${{ gitea.repository }}.git" "${{ gitea.sha }}"
|
||||
git checkout -q FETCH_HEAD
|
||||
out="${RUNNER_TEMP:-/tmp}/gitracks-sbom"
|
||||
mkdir -p "$out"
|
||||
if ! command -v trivy > /dev/null; then
|
||||
case "$(uname -m)" in
|
||||
x86_64) arch=Linux-64bit sum=$TRIVY_SHA256_X64 ;;
|
||||
aarch64 | arm64) arch=Linux-ARM64 sum=$TRIVY_SHA256_ARM64 ;;
|
||||
esac
|
||||
curl -sSfL "https://github.com/aquasecurity/trivy/releases/download/v$TRIVY_VERSION/trivy_${TRIVY_VERSION}_$arch.tar.gz" -o "$out/trivy.tar.gz"
|
||||
echo "$sum $out/trivy.tar.gz" | sha256sum -c - > /dev/null
|
||||
tar -xzf "$out/trivy.tar.gz" -C "$out" trivy
|
||||
export PATH="$out:$PATH"
|
||||
fi
|
||||
GOPATH="$out/gopath" trivy fs --quiet --format cyclonedx -o "$out/sbom.cdx.json" .
|
||||
echo "SBOM=$out/sbom.cdx.json" >> "$GITHUB_ENV"
|
||||
- uses: actions/upload-artifact@v3 # v4 refuses any server but github.com (GHESNotSupportedError)
|
||||
with:
|
||||
name: sbom
|
||||
path: ${{ env.SBOM }}
|
||||
@@ -0,0 +1,15 @@
|
||||
# GitRacks security actions
|
||||
|
||||
A reusable workflow that runs Trivy, Gitleaks and Semgrep and uploads their SARIF reports to GitRacks:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
security:
|
||||
uses: gitracks/security-actions/.gitea/workflows/security.yml@v1
|
||||
with:
|
||||
fail-on: high
|
||||
```
|
||||
|
||||
The NixOS module publishes this directory as the public repository `gitracks/security-actions` and moves its `v1` tag on upgrades (`services.gitracks.security.enable`). Elsewhere, push this directory to a public repository on your instance and tag it `v1`. Inputs and the severity gate are described in the GitRacks docs (`docs/fork/security.md`).
|
||||
|
||||
The bundled Semgrep rules are GitRacks' own (MIT).
|
||||
Reference in new issue
Block a user