Skip to content

fix(db): populate book.Author in List and Get responses (closes #882) #1350

fix(db): populate book.Author in List and Get responses (closes #882)

fix(db): populate book.Author in List and Get responses (closes #882) #1350

Workflow file for this run

name: Security
on:
push:
branches: [main, development]
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'CHANGELOG*'
- 'CONTRIBUTING*'
- 'README*'
- 'LICENSE*'
- 'CODE_OF_CONDUCT*'
- '.github/ISSUE_TEMPLATE/**'
pull_request:
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'CHANGELOG*'
- 'CONTRIBUTING*'
- 'README*'
- 'LICENSE*'
- 'CODE_OF_CONDUCT*'
- '.github/ISSUE_TEMPLATE/**'
schedule:
# Weekly Monday 06:00 UTC
- cron: '0 6 * * 1'
permissions:
contents: read
actions: read
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Go SAST: gosec only — golangci-lint and govulncheck run in ci.yml lint ──
sast-go:
name: SAST – Go
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.25.10"
cache: true
- name: gosec (SARIF)
uses: securego/gosec@5f4eec95fa28ce5dc6cf555de8c242cb57545f01 # master
with:
# G402 (TLS/cookie insecure) excluded: cookie Secure flag is dynamic (auto-detected
# from r.TLS and X-Forwarded-Proto, overridable via BINDERY_COOKIE_SECURE) and gosec
# only accepts literal `true`. Safety is enforced by cookie config logic + docs.
args: '-no-fail -fmt sarif -out gosec.sarif -exclude=G101,G402 ./...'
- name: Upload gosec SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always()
with:
sarif_file: gosec.sarif
category: gosec
# ── Frontend SAST: Semgrep + npm audit + ESLint security ───────────────────
sast-frontend:
name: SAST – Frontend
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "22"
cache: npm
cache-dependency-path: web/package-lock.json
- name: Install dependencies
run: npm ci --prefix web
- name: npm audit
run: npm audit --audit-level=high --prefix web
- name: Install ESLint security tooling
run: |
npm install --no-save \
eslint-plugin-security \
@microsoft/eslint-formatter-sarif \
--prefix web
- name: ESLint with security plugin
run: |
cat > /tmp/eslint-security.config.mjs << 'EOF'
import security from 'eslint-plugin-security';
export default [
security.configs.recommended,
{ files: ['src/**/*.{ts,tsx,js,jsx}'] }
];
EOF
cd web && npx eslint \
-c /tmp/eslint-security.config.mjs \
--format @microsoft/eslint-formatter-sarif \
--output-file eslint-security.sarif \
src/ || true
- name: Upload ESLint SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always() && hashFiles('web/eslint-security.sarif') != ''
with:
sarif_file: web/eslint-security.sarif
category: eslint-security
- name: Semgrep
run: |
python3 -m pip install --user semgrep
semgrep scan \
--config p/react --config p/typescript --config p/xss \
--sarif --output semgrep.sarif . || true
- name: Upload Semgrep SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always() && hashFiles('semgrep.sarif') != ''
with:
sarif_file: semgrep.sarif
category: semgrep-frontend
# ── Secrets scan: Gitleaks ─────────────────────────────────────────────────
secrets-scan:
name: Secrets Scan
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Gitleaks
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_CONFIG: .gitleaks.toml
GITLEAKS_ENABLE_SARIF: true
- name: Upload Gitleaks SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always()
with:
sarif_file: results.sarif
category: gitleaks
# ── IaC scan: Hadolint + Helm lint + Checkov ──────────────────────────────
iac-scan:
name: IaC Scan
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Hadolint (Dockerfile)
uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
with:
dockerfile: Dockerfile
format: sarif
output-file: hadolint.sarif
no-fail: true
- name: Upload Hadolint SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always()
with:
sarif_file: hadolint.sarif
category: hadolint
- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
with:
version: latest
- name: Helm lint
run: helm lint --strict charts/bindery
- name: Checkov
uses: bridgecrewio/checkov-action@de2bfaecd21d58ef232e0d2a3391c33c32c460d7 # master
with:
directory: .
file: Dockerfile
framework: dockerfile,helm,kubernetes
output_format: sarif
output_file_path: checkov.sarif
soft_fail: true
- name: Upload Checkov SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always()
with:
sarif_file: checkov.sarif
category: checkov
# ── Container scan: Trivy + Grype + Syft SBOM ────────────────────────────
container-scan:
name: Container Scan
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build image (with layer cache)
run: |
docker buildx build \
--load \
--cache-from type=gha \
--cache-to type=gha,mode=max \
-t bindery:scan \
.
- name: Trivy vulnerability scan (SARIF)
uses: aquasecurity/trivy-action@1994662b5555670344cd84d29ed3cad4bd26f31c # master
with:
image-ref: bindery:scan
format: sarif
output: trivy.sarif
severity: CRITICAL,HIGH
exit-code: '1'
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always()
with:
sarif_file: trivy.sarif
category: trivy
- name: Grype vulnerability scan (SARIF)
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
id: grype
with:
image: bindery:scan
output-format: sarif
output-file: grype.sarif
fail-build: true
- name: Upload Grype SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
if: always() && hashFiles('grype.sarif') != ''
with:
sarif_file: grype.sarif
category: grype
- name: Syft SBOM – SPDX
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
image: bindery:scan
format: spdx-json
output-file: sbom.spdx.json
artifact-name: sbom.spdx.json
upload-artifact: true
upload-artifact-retention: 90
- name: Syft SBOM – CycloneDX
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
image: bindery:scan
format: cyclonedx-json
output-file: sbom.cyclonedx.json
artifact-name: sbom.cyclonedx.json
upload-artifact: true
upload-artifact-retention: 90
# ── DAST: ZAP baseline (passive) ──────────────────────────────────────────
dast-api:
name: DAST – ZAP Baseline
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.25.10"
cache: true
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "22"
cache: npm
cache-dependency-path: web/package-lock.json
- name: Build frontend
run: npm ci --prefix web && npm run build --prefix web
- name: Build Bindery binary
run: go build -o bindery-dast ./cmd/bindery
- name: Start Bindery
run: |
BINDERY_DB_PATH=/tmp/bindery-dast.db \
BINDERY_API_KEY=zap-test-key \
./bindery-dast &
# Wait for the service to be ready
for i in $(seq 1 30); do
if curl -sf http://localhost:8787/api/v1/health; then
echo "Bindery is up"
break
fi
sleep 2
done
- name: ZAP Baseline Scan
run: |
mkdir -p zap-out
docker run --rm --network=host \
-v "$PWD/zap-out:/zap/wrk" \
zaproxy/zap-stable \
zap-baseline.py \
-t http://localhost:8787 \
-J zap-report.json \
-r zap-report.html \
-I || true
- name: Upload ZAP report artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: zap-baseline-report
path: zap-out/
retention-days: 30
# ── Helm unit tests ────────────────────────────────────────────────────────
# Stream C will add the test fixtures. continue-on-error until they land.
policy-test:
name: Helm Policy Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: helm-unittest (docker)
run: |
docker run --rm -v "$PWD:/apps" \
helmunittest/helm-unittest:latest \
charts/bindery
continue-on-error: true
# ── Aggregate severity summary ─────────────────────────────────────────────
summary:
name: Security Summary
runs-on: ubuntu-latest
needs:
- sast-go
- sast-frontend
- secrets-scan
- iac-scan
- container-scan
- dast-api
- policy-test
if: always()
steps:
- name: Write severity summary
run: |
cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
## Security Scan Summary
| Job | Status |
|-----|--------|
| SAST – Go (gosec) | ${{ needs.sast-go.result }} |
| SAST – Frontend (Semgrep, npm audit, ESLint) | ${{ needs.sast-frontend.result }} |
| Secrets Scan (Gitleaks) | ${{ needs.secrets-scan.result }} |
| IaC Scan (Hadolint, Helm lint, Checkov) | ${{ needs.iac-scan.result }} |
| Container Scan (Trivy, Grype, Syft SBOM) | ${{ needs.container-scan.result }} |
| DAST – ZAP Baseline | ${{ needs.dast-api.result }} |
| Helm Policy Tests | ${{ needs.policy-test.result }} |
> Full findings are visible in the **Security** tab → Code scanning alerts.
> SBOMs are attached as workflow artifacts (90-day retention).
EOF