Current Score: 6.6/10 → 7.5+/10 (in progress) Target Score: 9.5+/10 Last Updated: 2025-10-24
PR: #19 - Self-hosted runners + OSSF security stack
Commits: c0d536b, e481937
Added Tools:
- Semgrep - Multi-language SAST with 2000+ security rules
- Gitleaks 8.28.0 - Fast secret scanner
- TruffleHog 3.90.3 - Comprehensive secret scanner with verification
- Nancy - OSS Index dependency scanner
- go-licenses - License compliance analysis
- Syft 1.34.2 - SBOM generation (CycloneDX + SPDX)
- Grype 0.101.1 - Binary vulnerability scanner
- Cosign 3.0.2 - Container signing (Sigstore)
Migration to Native Binaries:
- All security tools now use native binaries (no Docker actions)
- Eliminates container overhead and permission issues
- Consistent tool versions across workflows
Expected Impact:
- ✅ SAST check: Enhanced (semgrep + gosec + CodeQL)
- ✅ Vulnerabilities check: Comprehensive (govulncheck + OSV + Nancy + Grype)
- ✅ Supply Chain: SBOM generation for all artifacts
- ✅ Security Policy: Enhanced with detailed tool documentation
Lint Workflow:
- Migrated all linters to native tools (golangci-lint, markdownlint, yamllint, hadolint)
- Removed Docker action dependencies
- Faster execution with local binaries
CI Workflow:
- Added staticcheck, errcheck, ineffassign
- Post-build SBOM generation
- Post-build binary scanning with Grype
- Enhanced code quality checks
Security Workflow:
- 4 new security jobs: semgrep, nancy, gitleaks, trufflehog
- License compliance checking
- SBOM with license information
- All tools produce SARIF for GitHub Security
Release Workflow:
- SBOM generation for all release artifacts
- Container image signing with Cosign v3 (keyless OIDC)
- SLSA provenance (existing)
Package Types:
- RPM: EL8/9/10 with mock builds
- DEB: Debian 12/13, Ubuntu 24.04
- FreeBSD: amd64/arm64
Security Features:
- SELinux support for RHEL/Oracle Linux
- Proper systemd hardening
- Unprivileged service user
- Secure file permissions
Paths Fixed:
- Binary:
/usr/sbin/ocserv-agent(was incorrectly in/etc/) - Config:
/etc/ocserv-agent/(read-only for service) - Logs:
/var/log/ocserv-agent/(writable)
New Documentation:
docs/SECURITY_TOOLS.md- Comprehensive security tools guidedocs/PACKAGING.md- Package building and installation guide
Updated Documentation:
- This file (OSSF_SCORECARD_IMPROVEMENTS.md)
| Standard | Before | After | Status |
|---|---|---|---|
| OSSF Scorecard | 4.9/10 | 6.6/10 | 🟡 In Progress |
| SLSA Build L3 | Partial | ✅ Full | ✅ Complete |
| OSPS Baseline L3 | Partial | ✅ Full | ✅ Complete |
| EU CRA | No SBOM | ✅ SBOM | ✅ Complete |
| NIST SSDF | Partial | ✅ Full | ✅ Complete |
- ✅ Binary-Artifacts: No binaries in repo
- ✅ Dangerous-Workflow: No dangerous GitHub Action patterns
- ✅ Dependency-Update-Tool: Dependabot configured
- ✅ License: Valid FSF/OSI license (MIT)
- ✅ SAST: CodeQL static analysis enabled
- ✅ Vulnerabilities: No known unfixed vulnerabilities
- 🔴 Code-Review (0/10): All 30 changesets lack review
- 🔴 Contributors (0/10): Single-organization project
- 🔴 Fuzzing (0/10): No fuzzing framework
- 🔴 Pinned-Dependencies (0/10): 49+ unpinned dependencies
- 🔴 Security-Policy (0/10):
Missing SECURITY.md✅ FIXED - 🔴 Token-Permissions (0/10): Excessive workflow permissions
⚠️ Branch-Protection (-1): Integration error⚠️ Signed-Releases (5/10): Only 1 of 2 artifacts signed⚠️ Maintained (0/10): Repository too young (<90 days)
- Create SECURITY.md
- Vulnerability disclosure policy
- Contact information
- Response timeline
Impact: +1.0 points (Security-Policy: 0 → 10)
Problem: All commits pushed directly to main without review
Solutions:
-
Enable Branch Protection Rules:
# Via GitHub UI: Settings → Branches → Branch protection rules # Or via gh CLI: gh api repos/dantte-lp/ocserv-agent/branches/main/protection \ --method PUT \ --field required_pull_request_reviews[required_approving_review_count]=1 \ --field required_pull_request_reviews[dismiss_stale_reviews]=true \ --field enforce_admins=true \ --field required_linear_history=true
-
Workflow Changes:
- All changes via Pull Requests
- Require 1 approval before merge
- Enable "Require review from Code Owners"
- Dismiss stale reviews when new commits pushed
-
Exception Handling:
- Emergency hotfixes: Create PR, get fast-track review
- Documentation updates: Still require PR (can be self-approved with justification)
Impact: +1.0 points (Code-Review: 0 → 10)
Implementation Steps:
# 1. Setup branch protection
gh repo edit dantte-lp/ocserv-agent --enable-issues=true --enable-projects=false
gh api -X PUT /repos/dantte-lp/ocserv-agent/branches/main/protection \
-f required_pull_request_reviews[required_approving_review_count]=1 \
-f required_status_checks[strict]=true \
-f required_status_checks[contexts][]=CI \
-f required_status_checks[contexts][]=Security \
-f required_status_checks[contexts][]=Lint \
-f enforce_admins=true
# 2. Create CODEOWNERS file
echo "* @dantte-lp" > .github/CODEOWNERSProblem: Workflows have excessive permissions
Current Issues:
release.yml:contents: write,packages: write,id-token: write- Some workflows don't specify permissions at all (default: all)
Solutions:
- CI Workflow (.github/workflows/ci.yml):
permissions:
contents: read
actions: read # For artifact downloads- Lint Workflow (.github/workflows/lint.yml):
permissions:
contents: read
pull-requests: read # Already correct- Security Workflow (.github/workflows/security.yml):
# Top-level (default for all jobs)
permissions:
contents: read
jobs:
gosec:
permissions:
contents: read
security-events: write # For SARIF upload
codeql:
permissions:
contents: read
security-events: write
trivy:
permissions:
contents: read
security-events: write
scorecard:
permissions:
contents: read
security-events: write
id-token: write # Required for OIDC
actions: read # Required for scorecard- Release Workflow (.github/workflows/release.yml):
permissions: {} # No default permissions
jobs:
build-artifacts:
permissions:
contents: read
build: # SLSA provenance
permissions:
id-token: write
contents: write # For uploading assets
actions: read
release:
permissions:
contents: write # For creating release
container:
permissions:
contents: read
packages: write # For GHCR pushImpact: +1.0 points (Token-Permissions: 0 → 10)
Problem: All GitHub Actions use version tags (@v4, @v5) instead of SHA hashes
Why Pin?
- Security: Prevents supply chain attacks via compromised action versions
- Reproducibility: Ensures builds are deterministic
- OSSF Requirement: Necessary for Scorecard score
Actions to Pin (22 unique actions):
| Action | Current | Target SHA (example) |
|---|---|---|
actions/checkout@v4 |
v4 | b4ffde65f46336ab88eb53be808477a3936bae11 (v4.1.1) |
actions/setup-go@v5 |
v5 | 0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 (v5.0.2) |
actions/upload-artifact@v4 |
v4 | 50769540e7f4bd5e21e526ee35c689e35e0d6874 (v4.4.3) |
actions/download-artifact@v4 |
v4 | fa0a91b85d4f404e444e00e005971372dc801d16 (v4.1.8) |
codecov/codecov-action@v4 |
v4 | 015f24e6818733317a2da2edd6290ab26238649a (v4.6.0) |
golangci/golangci-lint-action@v4 |
v4 | 971e284b6050e8a5849b72094c50ab08da042db8 (v4.0.1) |
github/codeql-action/init@v3 |
v3 | 4f3212b61783c3c68e8309a0f18a699764811cda (v3.27.1) |
github/codeql-action/autobuild@v3 |
v3 | 4f3212b61783c3c68e8309a0f18a699764811cda |
github/codeql-action/analyze@v3 |
v3 | 4f3212b61783c3c68e8309a0f18a699764811cda |
github/codeql-action/upload-sarif@v3 |
v3 | 4f3212b61783c3c68e8309a0f18a699764811cda |
securego/gosec@master |
master | |
aquasecurity/trivy-action@master |
master | |
ossf/scorecard-action@v2.3.1 |
v2.3.1 | 62b2cac7ed8198b15735ed49ab1e5cf35480ba46 (v2.3.1) |
softprops/action-gh-release@v1 |
v1 | c062e08bd532815e2082a85e87e3ef29c3e6d191 (v2.0.8) |
docker/setup-qemu-action@v3 |
v3 | 49b3bc8e6bdd4a60e6116a5414239cba5943d3cf (v3.2.0) |
docker/setup-buildx-action@v3 |
v3 | c47758b77c9736f4b2ef4073d4d51994fabfe349 (v3.7.1) |
docker/login-action@v3 |
v3 | 9780b0c442fbb1117ed29e0efdff1e18412f7567 (v3.3.0) |
docker/metadata-action@v5 |
v5 | 60a0d343a0d8a18aedee9d34e62251f752153bdb (v5.6.1) |
docker/build-push-action@v5 |
v5 | 4f58ea79222b3b9dc2c8bbdd6debcef730109a75 (v5.4.0) |
slsa-framework/slsa-github-generator@v2.0.0 |
v2.0.0 | Keep version tag (trusted) |
avto-dev/markdown-lint@v1 |
v1 | 04d43ee9191307b50935a753da3b775ab695eceb (v1.2.0) |
ibiqlik/action-yamllint@v3 |
v3 | 2576378a8e339169678f9939646ee3ee325e845c (v3.1.1) |
hadolint/hadolint-action@v3.1.0 |
v3.1.0 | 54c9adbab1582c2ef04b2016b760714a4bfde3cf (v3.1.0) |
Automated Pinning Script:
#!/bin/bash
# pin-actions.sh - Pin GitHub Actions to SHA hashes
declare -A SHAS=(
["actions/checkout@v4"]="b4ffde65f46336ab88eb53be808477a3936bae11"
["actions/setup-go@v5"]="0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32"
# ... add all mappings
)
for workflow in .github/workflows/*.yml; do
for action in "${!SHAS[@]}"; do
sha="${SHAS[$action]}"
# Replace with SHA and add comment
sed -i "s|uses: ${action}|uses: ${action%%@*}@${sha} # ${action}|g" "$workflow"
done
doneManual Process:
- For each action, find the latest release tag
- Get commit SHA for that tag:
gh api repos/{owner}/{repo}/git/ref/tags/{tag} - Replace
@v4with@<sha> # v4 - Add comment to indicate original version
Impact: +1.0 points (Pinned-Dependencies: 0 → 10)
Current Issues:
- Dockerfile uses
golang:1.25-alpinewithout digest
Solution:
# Before:
FROM golang:1.25-alpine AS builder
# After:
FROM golang:1.25-alpine@sha256:abcdef123... AS builderGet Digest:
docker pull golang:1.25-alpine
docker inspect --format='{{index .RepoDigests 0}}' golang:1.25-alpineAlso check:
deploy/compose/docker-compose.ymldeploy/compose/docker-compose.dev.ymldeploy/compose/docker-compose.test.yml
Problem: Only 1 of 2 artifacts signed
Current Status:
- SLSA provenance generated ✅
- Some artifacts not signed
⚠️
Solutions:
- GPG Sign All Release Artifacts:
# .github/workflows/release.yml
- name: Sign artifacts
run: |
echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --import
for file in dist/*.tar.gz; do
gpg --detach-sign --armor "$file"
done- Cosign for Container Images:
- name: Sign container image
run: |
cosign sign --key env://COSIGN_KEY ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
env:
COSIGN_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}Impact: +0.5 points (Signed-Releases: 5 → 10)
Problem: Commits not GPG signed (shows as "Unverified" on GitHub)
Solutions:
- Generate GPG Key:
gpg --full-generate-key
# Select: (1) RSA and RSA
# Key size: 4096
# Expiration: 2 years
# Name: dantte-lp
# Email: <your-github-email>- Configure Git:
gpg --list-secret-keys --keyid-format=long
# Copy key ID (after sec rsa4096/)
git config --global user.signingkey <KEY_ID>
git config --global commit.gpgsign true
git config --global tag.gpgsign true- Add to GitHub:
gpg --armor --export <KEY_ID>
# Copy output and add to GitHub Settings → SSH and GPG keys- Sign Previous Commits (if needed):
# Rebase and sign last N commits
git rebase --exec 'git commit --amend --no-edit --gpg-sign' -i HEAD~NImpact: Improves trust, required for Code-Review score
Options:
-
OSS-Fuzz (Recommended):
- Submit project to Google OSS-Fuzz
- Continuous fuzzing infrastructure
- Automatic bug reports
-
Go-fuzz:
// internal/config/config_fuzz_test.go
//go:build gofuzz
package config
func FuzzLoadConfig(f *testing.F) {
f.Add([]byte("valid config"))
f.Fuzz(func(t *testing.T, data []byte) {
// Fuzz config parser
})
}- GitHub Actions Fuzzing:
# .github/workflows/fuzz.yml
- name: Run fuzz tests
run: go test -fuzz=. -fuzztime=30s ./...Impact: +1.0 points (Fuzzing: 0 → 10)
Problem: Single organization contributor
Solutions:
- Encourage external contributions
- Good first issues for newcomers
- Clear contributing guidelines (✅ already have)
- Community engagement
Impact: +1.0 points (Contributors: 0 → 10) - Takes time
Steps:
- Complete self-certification: https://bestpractices.coreinfrastructure.org/
- Answer ~60 questions about project practices
- Achieve "passing" level (later: silver, gold)
Requirements:
- Public version control ✅
- Unique version numbers ✅
- Release notes ✅
- License ✅
- Documentation ✅
- Test suite ✅
- ...and more
Impact: +1.0 points (CII-Best-Practices: 0 → 10)
- Day 1: Create SECURITY.md ✅
- Day 2-3: Setup branch protection rules
- Day 4-5: Restrict token permissions in workflows
- Day 6-7: Setup GPG commit signing
Expected Score: 6.5/10
- Day 1-3: Pin all GitHub Actions to SHA hashes
- Day 4-5: Pin Docker base images with digests
- Day 6-7: Test and validate all workflows
Expected Score: 7.5/10
- Day 1-3: Implement GPG signing for all artifacts
- Day 4-5: Add Cosign for container images
- Day 6-7: Verify and document signing process
Expected Score: 8.0/10
- Fuzzing integration
- Community building for multi-contributor
- CII Best Practices certification
- SLSA Level 3 compliance
Expected Score: 9.0+/10
# Check current score
curl https://api.scorecard.dev/projects/github.com/dantte-lp/ocserv-agent
# Run scorecard locally
docker run -e GITHUB_AUTH_TOKEN=<token> gcr.io/openssf/scorecard:stable \
--repo=github.com/dantte-lp/ocserv-agent --show-details- OSSF Scorecard Documentation
- Pinning Dependencies Guide
- Token Permissions Guide
- SLSA Framework
- Sigstore Cosign
| Check | Current | Target | Status |
|---|---|---|---|
| Binary-Artifacts | 10 | 10 | ✅ Done |
| Branch-Protection | -1 | 10 | 🔄 Pending |
| CI-Tests | N/A | N/A | ✅ Done |
| CII-Best-Practices | 0 | 8 | ⏳ Future |
| Code-Review | 0 | 10 | 🔴 High Priority |
| Contributors | 0 | 5 | ⏳ Future |
| Dangerous-Workflow | 10 | 10 | ✅ Done |
| Dependency-Update-Tool | 10 | 10 | ✅ Done |
| Fuzzing | 0 | 8 | 🟡 Medium Priority |
| License | 10 | 10 | ✅ Done |
| Maintained | 0 | 10 | ⏳ Time-based |
| Packaging | -1 | 8 | ⏳ Future |
| Pinned-Dependencies | 0 | 10 | 🔴 High Priority |
| SAST | 10 | 10 | ✅ Done |
| Security-Policy | 10 | 10 | ✅ Done |
| Signed-Releases | 5 | 10 | 🟡 Medium Priority |
| Token-Permissions | 0 | 10 | 🔴 High Priority |
| Vulnerabilities | 10 | 10 | ✅ Done |
Current Score: 4.9/10 Projected Score (Phase 1): 6.5/10 (+1.6) Projected Score (Phase 2): 7.5/10 (+1.0) Projected Score (Phase 3): 8.0/10 (+0.5) Projected Score (Phase 4): 9.0+/10 (+1.0+)
Last Updated: 2025-10-23 Next Review: After Phase 1 completion