Skip to content

Security and Code Scanning

Tanimul Haque Khan edited this page Oct 7, 2026 · 1 revision

Security & Code Scanning

This page covers how SentinelGo's code is scanned, how findings get triaged and fixed, and how changes reach main. Read it before changing a workflow file, suppressing a finding, or merging a PR.

All findings land in Security → Code scanning.


What runs, and when

Scanner Where Trigger Blocks merge?
CodeQL (Go + Actions) codeql.yml push/PR to main, weekly (Mon 02:00 UTC) Yes, via the code_scanning ruleset rule
gosec ci.yml → Security Scan push/PR to main No. It's insight only and uploads SARIF
govulncheck ci.yml → Security Scan push/PR to main Yes, fails the job
Trivy ci.yml → Security Scan push/PR to main Yes, fails the job
OpenSSF Scorecard scorecard.yml push to main, weekly (Sat), branch-protection changes No. Feeds the README badge
SonarCloud external app + ci.yml → SonarQube push/PR to main No (not a required check), but keep the quality gate green
Semgrep, GitGuardian external apps PRs Yes. Both are required checks
Snyk external app PRs No

Required status checks on main

Build (darwin/amd64), Build (darwin/arm64), Build (linux/amd64), Build (windows/amd64), Test (ubuntu-latest), Test (macos-latest), Test (windows-latest), GitGuardian Security Checks, semgrep-cloud-platform/scan.

The ruleset uses a strict policy: a PR branch must be up to date with main before it can merge.


How changes reach main

  1. Commit work on dev. dev is protected only against deletion and creation, so pushing directly is fine.
  2. Open a dev → main PR.
  3. Enable auto-merge right away. Don't merge by hand:
    gh pr create --base main --head dev --title "..." --body-file pr.md
    gh pr merge <PR#> --auto --merge   # merge commits only; squash/rebase are disabled
  4. GitHub merges the PR once every required check passes. dev survives the merge because the ruleset protects it from deletion.
  5. Before the next PR, bring dev up to date: git fetch && git merge origin/main.

Dependabot PRs

Dependabot opens PRs straight against main for Go modules and GitHub Actions (weekly, Monday). The strict up-to-date rule means every merge to main puts the other open PRs behind. Handle them one at a time:

  1. Enable auto-merge on each: gh pr merge <PR#> --auto --merge.
  2. Comment @dependabot rebase on one PR and wait for it to merge.
  3. Repeat for the next. A PR can briefly show DIRTY (conflict) right after the rebase request. That's normal; give Dependabot a minute to rebase it.

Triage rules

Treat each alert as one of three cases:

  1. Real defect: fix it. Add a test that reproduces it. Close the alert by fixing the code, not by dismissing it.
  2. False positive: annotate it inline, with a reason (see below), so the reasoning lives next to the code.
  3. Process or repo-setting check (e.g. Scorecard BranchProtection, CodeReview, CIIBestPractices): dismiss it in the UI as won't fix, with a justification comment.

For larger work, open a GitHub issue per alert group, with the triage reasoning in the body. See #67–#70 for examples.

Suppressing gosec findings

Put a #nosec comment on the line above the flagged statement, naming the rule and giving a reason:

// #nosec G304 - path is an entry returned by os.ReadDir(efivarsDir)
data, rerr := os.ReadFile(filepath.Join(efivarsDir, e.Name()))
  • //nolint:gosec does not work for the SARIF scan. golangci-lint honours it, but the standalone gosec in CI ignores it. Use #nosec.
  • Always name the rule (G304, G302 G304, …). A bare #nosec hides future, unrelated findings on the same line.
  • scripts/ (release/CI tooling that takes paths from argv) is excluded from the gosec scan via -exclude-dir=scripts. It is never shipped in the agent binary.

Running gosec locally

gosec analyses the build for one OS at a time. CI runs on Linux, so run it on Linux to reproduce the CI results:

GOOS=linux  gosec -exclude-dir=scripts ./...
GOOS=darwin gosec -exclude-dir=scripts ./...   # for _darwin.go files

Running it on a Windows machine without GOOS set scans the Windows build and reports a different set of findings.


Workflow hardening rules

These rules close the Scorecard PinnedDependencies and TokenPermissions checks. Apply them to every new or edited workflow.

1. Pin every action to a full commit SHA

- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

A tag like @v7 or @v0.36.0 can be moved to point at different code, and the release job holds the signing key. Keep the # vX.Y.Z comment: Dependabot's github-actions config uses it to propose updates that bump the SHA and the comment together.

To resolve a tag to a SHA:

git ls-remote --tags https://github.com/<owner>/<action>.git | grep 'refs/tags/v7.0.0'
# For annotated tags, use the line ending in ^{} (the commit, not the tag object).

2. Least-privilege GITHUB_TOKEN

  • Top level of every workflow: permissions: contents: read (Scorecard uses read-all).
  • Grant write scopes only at job level, and only on the jobs that need them:
    • release.yml → release job: contents: write
    • codeql.yml → analyze job: security-events: write
    • ci.yml → Security Scan job: security-events: write (SARIF upload)
  • release-trigger.yml pushes its tag with the RELEASE_PAT secret (persisted by actions/checkout), so its GITHUB_TOKEN stays read-only. The push has to use a PAT: tags pushed with GITHUB_TOKEN don't trigger release.yml.

3. Never commit build or runtime output

.gitignore covers SQLite stores (sentinelgo_*.db, tasks.sqlite), coverage profiles (*.out) and stray /dev/ output. Watch for:

  • go build -o /dev/null on Windows creates a real file at ./dev/null. Use go vet or go build -o NUL instead.
  • Before committing, run git status and check for unexpected binaries.

Current status (October 2026)

Area State
gosec All alerts fixed or annotated (#70, #73)
Scorecard PinnedDependencies / TokenPermissions / BinaryArtifacts Fixed in #71
Scorecard Fuzzing Planned: Go native fuzzing of parsers that take untrusted input, see #72
Scorecard BranchProtection, CodeReview Accepted. The maintainer can bypass the main ruleset for small direct merges
Scorecard CIIBestPractices Not pursued (requires registering for the OpenSSF Best Practices badge)