Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "security"
groups:
efcore:
patterns:
- "Microsoft.EntityFrameworkCore*"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "ci"
51 changes: 51 additions & 0 deletions .github/workflows/ContinuousIntegration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: ContinuousIntegration

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository source
uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"

- name: Restore NuGet packages
run: dotnet restore

- name: Build application
run: dotnet build --no-restore --configuration Release

- name: Run automated tests
run: dotnet test --no-build --configuration Release --logger "trx;LogFileName=test-results.trx"

- name: Publish test result artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: "**/test-results.trx"

# Defense-in-depth alongside Dependabot: fail the build if a known
# critical/high vulnerable NuGet package is referenced (direct or transitive).
- name: Scan for vulnerable NuGet packages
run: |
set -euo pipefail
echo "Scanning for known-vulnerable packages..."
report=$(dotnet list package --vulnerable --include-transitive 2>&1) || true
echo "$report"
if echo "$report" | grep -qi "has the following vulnerable packages"; then
echo "::error::Vulnerable NuGet packages detected. See job log above."
exit 1
fi
77 changes: 77 additions & 0 deletions .github/workflows/DependencyVulnerabilityScanning.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: DependencyVulnerabilityScanning

# Enforces a remediation SLA policy (documented in docs/SecurityHardening.md)
# on top of Dependabot itself: Critical/High severity alerts must be
# remediated within 7 days, Medium within 30. This does not replace
# Dependabot (which finds the CVEs and opens PRs) -- it just makes the SLA
# visible and failing in CI instead of only living in a doc nobody re-reads.

on:
schedule:
- cron: "0 8 * * *"
workflow_dispatch: {}

permissions:
contents: read
security-events: read

env:
CRITICAL_HIGH_SLA_DAYS: 7
MEDIUM_SLA_DAYS: 30

jobs:
check-sla:
runs-on: ubuntu-latest
steps:
- name: Check Dependabot alerts against remediation SLA
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail

alerts=$(gh api "repos/$REPO/dependabot/alerts?state=open&per_page=100" --paginate 2>/dev/null || echo "[]")

echo "## Dependabot SLA check for $REPO" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"

count=$(echo "$alerts" | jq 'length')
if [ "$count" -eq 0 ]; then
echo "No open Dependabot alerts. Nothing to check." >> "$GITHUB_STEP_SUMMARY"
echo "No open Dependabot alerts."
exit 0
fi

now_epoch=$(date -u +%s)
breached=0

echo "| Severity | Package | Opened | Age (days) | SLA (days) | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|---|---|---|---|---|---|" >> "$GITHUB_STEP_SUMMARY"

while IFS=$'\t' read -r severity package created_at html_url; do
created_epoch=$(date -u -d "$created_at" +%s 2>/dev/null || echo "$now_epoch")
age_days=$(( (now_epoch - created_epoch) / 86400 ))

case "$severity" in
critical|high) sla=$CRITICAL_HIGH_SLA_DAYS ;;
medium) sla=$MEDIUM_SLA_DAYS ;;
*) sla="" ;;
esac

if [ -z "$sla" ]; then
status="ℹ️ no SLA (low)"
elif [ "$age_days" -gt "$sla" ]; then
status="❌ BREACHED"
breached=1
else
status="✅ within SLA"
fi

echo "| $severity | [$package]($html_url) | $created_at | $age_days | ${sla:-n/a} | $status |" >> "$GITHUB_STEP_SUMMARY"
done < <(echo "$alerts" | jq -r '.[] | [.security_advisory.severity, .dependency.package.name, .created_at, .html_url] | @tsv')

if [ "$breached" -eq 1 ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "::error::One or more Critical/High Dependabot alerts have exceeded the ${CRITICAL_HIGH_SLA_DAYS}-day remediation SLA. See job summary for details."
exit 1
fi
63 changes: 63 additions & 0 deletions .github/workflows/EnforceRequiredReviewer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: EnforceRequiredReviewer

on:
workflow_dispatch:
push:
branches:
- main
- develop
paths:
- .github/workflows/EnforceRequiredReviewer.yml

permissions:
contents: read

jobs:
enforce-required-reviewer:
runs-on: ubuntu-latest
steps:
- name: Enforce required reviewer protection on protected branches
env:
GH_TOKEN: ${{ secrets.GH_ADMIN_TOKEN || secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail

for branch in main develop; do
echo "Applying branch protection to $branch"

if ! gh api "repos/$REPO/branches/$branch" >/dev/null 2>&1; then
echo "Branch '$branch' was not found or is not accessible; skipping."
continue
fi

body='{
"required_status_checks": null,
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": false,
"require_last_push_approval": false
},
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false,
"block_creations": false,
"required_linear_history": false,
"allow_fork_syncing": false,
"required_conversation_resolution": false,
"lock_branch": false
}'

if ! printf '%s\n' "$body" | gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
"/repos/$REPO/branches/$branch/protection" \
--input -; then
echo "::error::Unable to update branch protection for '$branch'. The token likely lacks admin access to repository branch protection APIs. Configure a PAT or GitHub App token with repo admin permissions and set it as GH_ADMIN_TOKEN."
exit 1
fi

echo "Branch protection updated successfully for $branch"
done
60 changes: 60 additions & 0 deletions .github/workflows/SecretScanning.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: SecretScanning

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Weekly full-history scan to catch anything that slipped past PR scanning
# (e.g. force-pushed history, commits made outside the protected branch).
- cron: "0 6 * * 1"
workflow_dispatch: {}

permissions:
contents: read

jobs:
diff-scan:
if: github.event_name == 'push' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repository with history
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Run TruffleHog filesystem scan
run: |
docker run --rm -v "$PWD:/repo" -w /repo trufflesecurity/trufflehog:latest \
filesystem /repo --fail --only-verified --json > trufflehog-diff-report.json

full-history-scan:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout full repository history
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Scan full git history for committed secrets
run: |
docker run --rm -v "$PWD:/repo" -w /repo trufflesecurity/trufflehog:latest \
git file:///repo --fail --only-verified --json > trufflehog-history-report.json

- name: Upload TruffleHog history report
if: always()
uses: actions/upload-artifact@v4
with:
name: trufflehog-history-report
path: trufflehog-history-report.json

- name: Report remediation guidance on failure
if: failure()
run: |
echo "::error::TruffleHog found credentials in git history. Do NOT just delete the commit."
echo "1. Rotate/revoke the exposed credential immediately at the issuing provider."
echo "2. Purge it from history with git filter-repo or BFG Repo-Cleaner."
echo "3. Force-push the rewritten history and have all collaborators re-clone."
echo "See trufflehog-history-report.json (uploaded as a workflow artifact) for exact commits/files."
85 changes: 85 additions & 0 deletions .github/workflows/SecurityComplianceAudit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: SecurityComplianceAudit

on:
schedule:
- cron: "0 7 * * 1"
workflow_dispatch: {}

permissions:
contents: read

jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Checkout repository source
uses: actions/checkout@v4

- name: Audit repository security hardening settings
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set +e
fail=0
echo "## Security compliance audit for $REPO" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"

check() {
local name="$1" ok="$2" detail="$3"
if [ "$ok" = "true" ]; then
echo "- ✅ $name" >> "$GITHUB_STEP_SUMMARY"
else
echo "- ❌ $name — $detail" >> "$GITHUB_STEP_SUMMARY"
fail=1
fi
}

protection=$(gh api "repos/$REPO/branches/main/protection" 2>/dev/null || true)
if [ -z "$protection" ]; then
check "Branch protection enabled on main" false "no protection rule found (or token lacks admin:repo)"
else
check "Branch protection enabled on main" true ""

reviewers=$(echo "$protection" | jq -r '.required_pull_request_reviews.required_approving_review_count // 0' 2>/dev/null || echo 0)
if [ "$reviewers" -ge 1 ]; then
check "Required PR reviewers >= 1" true ""
else
check "Required PR reviewers >= 1" false "currently $reviewers"
fi

enforce_admins=$(echo "$protection" | jq -r '.enforce_admins.enabled // false' 2>/dev/null || echo false)
check "Branch protection enforced for admins" "$enforce_admins" "enforce_admins is false"
fi

signatures=$(gh api "repos/$REPO/branches/main/protection/required_signatures" 2>/dev/null | jq -r '.enabled // false' 2>/dev/null || echo false)
check "Required signed commits on main" "$signatures" "required_signatures.enabled is false or unset"

alerts_status=$(gh api "repos/$REPO/vulnerability-alerts" -i 2>/dev/null | head -1 | grep -o '204' || true)
if [ "$alerts_status" = "204" ]; then
check "Dependabot vulnerability alerts enabled" true ""
else
check "Dependabot vulnerability alerts enabled" false "vulnerability-alerts endpoint did not return 204"
fi

fixes_status=$(gh api "repos/$REPO/automated-security-fixes" -i 2>/dev/null | head -1 | grep -o '200' || true)
if [ -n "$fixes_status" ]; then
check "Dependabot automated security fixes enabled" true ""
else
check "Dependabot automated security fixes enabled" false "automated-security-fixes endpoint did not return 200"
fi

{
echo ""
echo "### Not checkable from a repo-scoped workflow token"
echo "- ⚠️ **SSO enforcement** — GitHub Enterprise org-level setting. Verify manually: Organization settings → Authentication security → \"Require SAML SSO\"."
echo "- ⚠️ **Audit log retention** — GitHub Enterprise Cloud org/enterprise setting. Verify manually: Enterprise settings → Audit log → retention policy."
} >> "$GITHUB_STEP_SUMMARY"

if [ "$fail" -ne 0 ]; then
echo "::warning::Security audit found one or more missing or non-compliant settings. Review the summary above."
else
echo "::notice::Security audit completed successfully."
fi

exit 0
Loading
Loading