mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 22:31:36 +00:00
6b5af27546
Closes the 2026-04-25 audit's final-closure cluster. Score 51/55 -> 54/55
(98% closed); deferred 4/7 -> 7/7 (100%). All severity-graded findings now
closed except M-029 (frontend per-PR migration backlog, by design incremental).
L-004 (CWE-924) — dual-key API rotation overlap window:
internal/config/config.go::ParseNamedAPIKeys rewritten to allow same-name
duplicate entries iff admin flag matches. Mismatched-admin entries rejected
at startup (privilege escalation guard); exact (name,key) duplicates rejected
(typo guard — rotation requires DIFFERENT keys under the same name). Startup
INFO log per name with multiple entries surfaces the active rotation window.
NewAuthWithNamedKeys was already shaped correctly (constant-time hash compare
across all entries, same UserKey + AdminKey for either bearer); Bundle B's
M-025 per-user rate-limit bucket and audit-trail actor inherit consistency
across the rollover automatically. 8 new tests pin the contract end-to-end.
docs/security.md::API key rotation walks the 6-step zero-downtime rollover.
D-003 — Mutation testing wired:
security-deep-scan.yml gets a go-mutesting step covering ./internal/crypto/...,
./internal/pkcs7/..., ./internal/connector/issuer/local/... with per-package
summary lines extracted into go-mutesting.txt artefact.
D-007 — Frontend semgrep wired (recon found Bundle 7's wiring claim was false):
security-deep-scan.yml gets a 'semgrep p/react-security' step running
returntocorp/semgrep:latest --config=p/react-security against /src/web/src;
results uploaded as semgrep-react.json.
D-004 + D-005 — Operator runbook published:
docs/testing-strategy.md (NEW) consolidates per-tool local-run procedures,
acceptance thresholds, and triage paths for go-mutesting, ZAP baseline DAST,
testssl.sh, and semgrep p/react-security. Closes the 'wired CI-only, no
local-run validation' framing for D-004/D-005 by giving operators the same
commands the CI workflow runs.
Verification:
gofmt -l no diff
go vet ./internal/config/... ./internal/api/middleware/... clean
go test -short -count=1 ./internal/config/... ./internal/api/middleware/... PASS
python3 -c 'yaml.safe_load(...)' YAML OK
G-3 env-var docs guard no phantom env-vars
Audit deliverables:
audit-report.md: L-004 + D-003/4/5/7 boxes flipped [x]; score 51/55 -> 54/55
findings.yaml: 5 status flips; new bundle-G-final-closure closure_log entry
CHANGELOG.md: Bundle G entry under [unreleased]; supersedes Bundle E + F
L-004-deferred framing
195 lines
6.9 KiB
YAML
195 lines
6.9 KiB
YAML
name: security-deep-scan
|
|
|
|
# Bundle-7 / Audit D-001..D-007:
|
|
# Slow / containerized scans on a daily schedule + manual dispatch.
|
|
# Per-PR fast gates live in ci.yml; this workflow runs the heavyweight
|
|
# tools that need docker, network egress to scanner registries, or
|
|
# longer wall-clock budgets than a per-PR check tolerates.
|
|
#
|
|
# Scope:
|
|
# trivy image container CVE + secret scan
|
|
# syft SBOM CycloneDX SBOM artefact upload
|
|
# ZAP baseline DAST baseline against a live deploy_test stack (D-004)
|
|
# nuclei template-based vuln scan against the same stack
|
|
# schemathesis OpenAPI fuzz against the running server
|
|
# testssl.sh TLS configuration audit (D-005)
|
|
# race detector x10 full -count=10 race run on the entire test suite (D-002)
|
|
# gosec Go security static analysis (slow first run)
|
|
# go-mutesting mutation testing on crypto cluster (D-003)
|
|
# semgrep p/react-security frontend XSS / dangerouslySetInnerHTML / target=_blank ruleset (D-007)
|
|
#
|
|
# Each step is best-effort — failures are uploaded as artefacts but do
|
|
# NOT block the workflow. Triage happens via the Bundle-7 receipt
|
|
# directory under cowork/comprehensive-audit-2026-04-25/tool-output/.
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 6 * * *' # daily 06:00 UTC
|
|
workflow_dispatch: {}
|
|
|
|
permissions:
|
|
contents: read
|
|
security-events: write # SARIF upload to GitHub code scanning
|
|
|
|
jobs:
|
|
deep-scan:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.25'
|
|
|
|
- name: Install Go-based tools
|
|
run: bash scripts/install-security-tools.sh
|
|
continue-on-error: true
|
|
|
|
# --- Static analysis (slow paths) ---
|
|
|
|
- name: gosec
|
|
run: |
|
|
$(go env GOPATH)/bin/gosec -fmt sarif -out gosec.sarif ./... || true
|
|
continue-on-error: true
|
|
|
|
- name: osv-scanner (multi-ecosystem CVE)
|
|
run: |
|
|
$(go env GOPATH)/bin/osv-scanner -r --format json --output osv-scanner.json . || true
|
|
continue-on-error: true
|
|
|
|
# --- Race detector at -count=10 (D-002) ---
|
|
|
|
- name: go test -race -count=10 (full suite)
|
|
run: |
|
|
go test -race -count=10 -short ./... 2>&1 | tee go-test-race.txt
|
|
continue-on-error: true
|
|
|
|
# --- Coverage receipts for crypto cluster (H-005) ---
|
|
|
|
- name: go test -cover (crypto cluster)
|
|
run: |
|
|
go test -cover -covermode=atomic \
|
|
./internal/crypto/... \
|
|
./internal/pkcs7/... \
|
|
./internal/connector/issuer/local/... \
|
|
2>&1 | tee go-test-cover.txt
|
|
|
|
# --- Mutation testing on crypto cluster (D-003) ---
|
|
#
|
|
# Operator runbook: docs/testing-strategy.md::Mutation testing.
|
|
# Tool: go-mutesting (https://github.com/zimmski/go-mutesting). Each
|
|
# package is mutated independently; the per-package summary line
|
|
# (`The mutation score is X.YZ`) is grep-extracted into the receipt.
|
|
# Acceptance threshold: ≥80% kill ratio per package; surviving
|
|
# mutants get triaged in cowork/comprehensive-audit-2026-04-25/
|
|
# d003-mutation-results.md (per-mutant action item or
|
|
# equivalent-mutation justification).
|
|
|
|
- name: Install go-mutesting
|
|
run: go install github.com/zimmski/go-mutesting/cmd/go-mutesting@latest
|
|
continue-on-error: true
|
|
|
|
- name: go-mutesting (crypto cluster)
|
|
run: |
|
|
: > go-mutesting.txt
|
|
for pkg in ./internal/crypto/... ./internal/pkcs7/... ./internal/connector/issuer/local/...; do
|
|
echo "=== $pkg ===" | tee -a go-mutesting.txt
|
|
$(go env GOPATH)/bin/go-mutesting "$pkg" 2>&1 | tee -a go-mutesting.txt || true
|
|
done
|
|
continue-on-error: true
|
|
|
|
# --- Container + supply chain (D-001 partial, D-006 partial) ---
|
|
|
|
- name: Build certctl image
|
|
run: docker build -t certctl:deep-scan .
|
|
continue-on-error: true
|
|
|
|
- name: trivy image scan
|
|
run: |
|
|
docker run --rm -v "$PWD":/src aquasec/trivy:latest image \
|
|
--format json --output /src/trivy.json certctl:deep-scan || true
|
|
continue-on-error: true
|
|
|
|
- name: syft SBOM
|
|
run: |
|
|
docker run --rm -v "$PWD":/src anchore/syft:latest dir:/src \
|
|
-o cyclonedx-json > syft.cyclonedx.json || true
|
|
continue-on-error: true
|
|
|
|
# --- DAST against a live stack (D-004) ---
|
|
|
|
- name: docker compose up (test stack)
|
|
run: |
|
|
docker compose -f deploy/docker-compose.yml up -d
|
|
sleep 20
|
|
continue-on-error: true
|
|
|
|
- name: ZAP baseline
|
|
uses: zaproxy/action-baseline@v0.10.0
|
|
with:
|
|
target: 'https://localhost:8443'
|
|
continue-on-error: true
|
|
|
|
- name: schemathesis (OpenAPI fuzz)
|
|
run: |
|
|
pip install schemathesis
|
|
schemathesis run --base-url https://localhost:8443 \
|
|
--hypothesis-max-examples=50 api/openapi.yaml || true
|
|
continue-on-error: true
|
|
|
|
- name: nuclei
|
|
run: |
|
|
docker run --rm --network host projectdiscovery/nuclei:latest \
|
|
-u https://localhost:8443 -j -o nuclei.json || true
|
|
continue-on-error: true
|
|
|
|
# --- TLS audit (D-005) ---
|
|
|
|
- name: testssl.sh
|
|
run: |
|
|
docker run --rm -v "$PWD":/data drwetter/testssl.sh:latest \
|
|
--jsonfile /data/testssl.json https://localhost:8443 || true
|
|
continue-on-error: true
|
|
|
|
- name: docker compose down
|
|
run: docker compose -f deploy/docker-compose.yml down || true
|
|
if: always()
|
|
|
|
# --- Frontend XSS / unsafe-link ruleset (D-007) ---
|
|
#
|
|
# Operator runbook: docs/testing-strategy.md::Frontend semgrep.
|
|
# Bundle 8 already verified `dangerouslySetInnerHTML` count at
|
|
# zero and the `target="_blank"` rel-noopener pin via grep
|
|
# guards in ci.yml — semgrep p/react-security adds defence in
|
|
# depth (it catches escape patterns the grep guards don't see,
|
|
# e.g., href={user_input}, eval, document.write).
|
|
|
|
- name: semgrep p/react-security (frontend)
|
|
run: |
|
|
docker run --rm -v "$PWD":/src returntocorp/semgrep:latest \
|
|
semgrep --config=p/react-security --json /src/web/src \
|
|
> semgrep-react.json 2>semgrep-react.stderr || true
|
|
continue-on-error: true
|
|
|
|
# --- Upload everything as artefacts ---
|
|
|
|
- name: Upload deep-scan receipts
|
|
uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: security-deep-scan-${{ github.run_id }}
|
|
path: |
|
|
gosec.sarif
|
|
osv-scanner.json
|
|
go-test-race.txt
|
|
go-test-cover.txt
|
|
go-mutesting.txt
|
|
trivy.json
|
|
syft.cyclonedx.json
|
|
nuclei.json
|
|
testssl.json
|
|
semgrep-react.json
|
|
semgrep-react.stderr
|
|
retention-days: 30
|