Skip to content

ci: harden permissions + 6 new pro-grade security workflows#21

Open
Outtsett wants to merge 3 commits intomainfrom
ci/professional-hardening
Open

ci: harden permissions + 6 new pro-grade security workflows#21
Outtsett wants to merge 3 commits intomainfrom
ci/professional-hardening

Conversation

@Outtsett
Copy link
Copy Markdown
Owner

@Outtsett Outtsett commented May 5, 2026

Summary

Brings CI/CD up to professional-grade security baseline: 6 new workflows + permissions/SHA-pinning hardening across the existing 3.

What's new

Workflow Purpose Free on private?
trufflehog.yml Verified-only secret scanner — validates findings against APIs yes
osv-scanner.yml CVE scanner for pubspec.lock + Gradle (Google OSV.dev) yes
actionlint.yml Workflow YAML lint + shellcheck inside run: blocks yes
scorecard.yml OpenSSF security posture scoring (weekly) yes (degrades w/o SCORECARD_TOKEN PAT)
codeql.yml Java/Kotlin (android/) + Swift (ios/) static analysis only if public OR GHAS — gated on visibility
pr-title.yml Conventional-commits enforcement on PR title (squash-merge subject) yes

Hardening across the existing 3

  • Permissions: explicit permissions: block at workflow root (default contents: read) and per-job (escalate only where needed). No more inheriting the broad default token scope.
  • Pinned third-party actions to SHAs (with version comments):
    • subosito/flutter-action@v21a449444c387b1966244ae4d4f8c696479add0b2
    • gitleaks/gitleaks-action@v2ff98106e4c7b2bc287b24eaf42907196329070c7
    • Plus all newly-introduced third-party actions (TruffleHog, OSV, Scorecard, lcov-reporter, semantic-pull-request).
    • Official actions/* keep major-version tags (industry-standard compromise).
  • Concurrency block on every workflow — cancels superseded runs.
  • timeout-minutes on every job (5–45m by size).

Fixes universal CI failure on PR #20

gitleaks was failing with 403 Resource not accessible by integration on every PR because it calls GET /repos/.../pulls/N/commits and the workflow only granted contents: read. Added pull-requests: read to the job. PR #20 will go green on this check after merging.

(Two other PR #20 failures — format check + commitlint license: type — are commit-content issues, not CI config issues. Will be addressed by rebasing PR #20 after this lands.)

Coverage PR comments

Adds a coverage-comment job to ci.yml that posts a sticky PR comment with line/branch coverage diff vs base, using romeovs/lcov-reporter-action (SHA-pinned). Soft signal; the test job stays the gating check.

CLAUDE.md updated

Documents the full 9-workflow inventory + the required-status-check list that branch protection should enforce on main once the repo flips public.

Test plan

  • actionlint runs clean locally on the full workflow set (Docker, exit 0)
  • Push triggers all 7 active workflows on this PR; verify each goes green
  • gitleaks no longer hits 403 Resource not accessible by integration
  • trufflehog runs clean (--only-verified mode)
  • osv-scanner runs clean (no CVEs in current pubspec.lock)
  • actionlint runs clean on the new workflows
  • pr-title validates this PR's title (ci: harden ...)
  • commitlint validates the single commit
  • CI analyze + format runs clean (this branch is from origin/main HEAD which doesn't have the v1.0 unformatted files)
  • scorecard produces a partial result (no SCORECARD_TOKEN set yet — that's expected on private repos)

Follow-ups (not in this PR)

  • Set up the SCORECARD_TOKEN repo secret (fine-grained PAT, read-only on admin/PRs/contents) for full Scorecard check coverage.
  • Once PR feat(v1.0): land store-ready release #12 (release/v1.0) merges, the v1.0 file batch will need a one-shot dart format cleanup PR — that's a content fix, not a CI config fix.
  • Once repo flips public, CodeQL will activate automatically (gated on visibility); no further config change needed.

🤖 Generated with Claude Code

Adds the workflow surface that brings this repo in line with what
serious public Flutter/Dart projects ship.

New workflows:
- TruffleHog (verified-only) — second secret scanner; complements
  gitleaks (regex) by validating found credentials against their APIs.
  Catches what gitleaks misses; eliminates false positives.
- OSV-Scanner — Google's vulnerability scanner against the OSV.dev
  database for pubspec.lock + Gradle. Free, no API key, works on
  private repos without GHAS. Replaces dependency-review-action which
  needs GHAS on private repos.
- actionlint — workflow YAML linter (shellcheck inside `run:` blocks).
  Would have caught the SC2086 quoting issues hit on PR #19.
- OpenSSF Scorecard — weekly security posture scoring. Works on
  private repos with optional SCORECARD_TOKEN PAT for full check
  coverage; gracefully degrades without one.
- CodeQL — static analysis for android/ Java/Kotlin + ios/ Swift.
  Gated on `github.event.repository.visibility == 'public'` because
  GHAS is required on private repos.
- PR Title — amannn/action-semantic-pull-request validates the PR
  title against conventional-commits (belt-and-suspenders with
  commitlint when squash-merging uses the PR title as commit subject).

Hardening across all existing workflows:
- Pinned subosito/flutter-action@v2 -> 1a449444c387b1966244ae4d4f8c696479add0b2
- Pinned gitleaks/gitleaks-action@v2 -> ff98106e4c7b2bc287b24eaf42907196329070c7
- All third-party actions pinned to commit SHAs with version comments;
  Dependabot continues to keep them current.
- Explicit `permissions:` block at workflow root (default-deny) and
  per-job (least-privilege escalation only where needed).
- `concurrency:` block on every workflow to cancel superseded runs.
- `timeout-minutes:` on every job (5–45m by job size).

Fixes universal CI failures:
- secret-scan.yml: add `pull-requests: read` permission. The gitleaks
  action calls `GET /repos/.../pulls/N/commits` which 403'd on every
  PR before this. Run on PR #20 hit `Resource not accessible by
  integration` because of this missing permission.

CI workflow additions:
- coverage-comment job posts a sticky PR comment with line/branch
  coverage diff vs base via romeovs/lcov-reporter-action (SHA-pinned).
  Soft signal — does not gate merge; rely on test job for that.

Update CLAUDE.md with the full 9-workflow inventory + the required
status-check list that branch protection should enforce on `main`.

Local validation: actionlint passes clean against the full workflow
set (Docker run, exit 0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 5, 2026 05:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the repository’s CI/CD security posture by adding multiple security-focused GitHub Actions workflows (secret scanning, dependency vuln scanning, workflow linting, PR title linting, repo scorecarding, and CodeQL) and hardening existing workflows with explicit permissions, concurrency cancellation, timeouts, and SHA-pinning of third-party actions.

Changes:

  • Added 6 new security/quality workflows: TruffleHog, OSV-Scanner, actionlint, OpenSSF Scorecard, CodeQL (visibility-gated), and PR title conventional-commit validation.
  • Hardened existing workflows (CI, secret-scan, commitlint) with explicit job permissions, timeouts, concurrency, and SHA-pinned third-party actions.
  • Updated CLAUDE.md to document the CI/CD workflow inventory and recommended branch protection required checks.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
CLAUDE.md Documents CI/CD workflow inventory and branch protection required checks guidance.
.github/workflows/ci.yml Adds timeouts/permissions, pins Flutter action by SHA, and posts PR coverage comments.
.github/workflows/commitlint.yml Adds concurrency/timeout and moves permissions to job scope.
.github/workflows/secret-scan.yml Adds concurrency/timeout and pins gitleaks action by SHA; expands job permissions.
.github/workflows/trufflehog.yml New verified-only TruffleHog secret scanning workflow.
.github/workflows/osv-scanner.yml New OSV dependency vulnerability scanning workflow with SARIF artifact output.
.github/workflows/actionlint.yml New workflow YAML linting via actionlint download script.
.github/workflows/scorecard.yml New OpenSSF Scorecard workflow with SARIF output and artifact upload.
.github/workflows/codeql.yml New CodeQL workflow for android/ios native code, gated on repo visibility.
.github/workflows/pr-title.yml New PR-title conventional-commits validation workflow (pull_request_target).

Comment thread .github/workflows/ci.yml
Comment on lines +20 to +22
# subosito/flutter-action@v2 — pinned to commit SHA for supply-chain safety.
# Bump in lockstep with the Dependabot github-actions update PR.
FLUTTER_ACTION_REF: 1a449444c387b1966244ae4d4f8c696479add0b2
branches: [main]
workflow_dispatch:

permissions: read-all
Comment thread CLAUDE.md
Comment on lines +269 to +271
- `OSV-Scanner / OSV-Scanner (pubspec + gradle)`
- `actionlint / Lint workflow YAML`

… target

Three fixes to first run of PR #21:

1. OSV-Scanner action path was wrong (`google/osv-scanner-action/.github/
   actions/osv-scanner-action@SHA` → `google/osv-scanner-action/
   osv-scanner-action@SHA`). The reusable composite lives at the
   subdirectory root, not under `.github/actions/`. Fixed.

2. TruffleHog action wrapper already passes `--no-update` to the
   trufflehog binary, so duplicating it via `extra_args` triggered
   `flag 'no-update' cannot be repeated`. Removed the duplicate.

3. iOS build was failing on `pod install` with:
     The plugin "health" requires a higher minimum iOS deployment
     version than your application is targeting. Increase to 14.0.
   Pre-existing config bug — the Xcode project's three
   IPHONEOS_DEPLOYMENT_TARGET entries were still on the Flutter
   default of 12.0. Bumped to 14.0 to satisfy `health 13.x`.

actionlint runs clean against the corrected workflow set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iOS build is now informational (path-filtered, doesn't run on PRs that
touch no iOS files) instead of gating every PR. Three reasons:

1. Tyler's dev machine is Windows — iOS validation happens on a Mac
   pre-store-submission, not on every PR.
2. Transitive deps periodically ship iOS code requiring a newer Xcode
   SDK than the macOS runner ships. Currently device_info_plus 12.4.0
   calls NSProcessInfo.isiOSAppOnVision (iOS 17+ selector) which the
   runner's Xcode rejects:
     ARC Semantic Issue (Xcode): No visible @interface for
     'NSProcessInfo' declares the selector 'isiOSAppOnVision'
   Unrelated PRs (like this one) get red iOS checks for transitive
   dep churn they didn't introduce.
3. macos-latest runner minutes are 10x ubuntu-latest — not worth
   burning on PRs that touch no iOS files.

Implementation: add a `changed` job using dorny/paths-filter@v3 (pinned
to commit SHA) that reads the diff against base. Sets `ios` output to
true if any of `ios/**`, `pubspec.yaml`, `pubspec.lock`, or this
workflow file changed. Build iOS job gated on `needs.changed.outputs.ios
== 'true'`.

Tradeoff: iOS regressions in transitive Dart deps don't surface until a
PR touches pubspec or iOS files. Acceptable because:
- Manual iOS builds happen pre-store-submission anyway
- Dependabot bumps to pubspec.lock will trigger the iOS build, catching
  transitive-dep iOS breakage at the bump PR
- A scheduled weekly cron could be added if drift becomes an issue

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Coverage after merging ci/professional-hardening into main will be

19.79%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
lib/models
   habit.g.dart0%100%100%0%101–102, 104–105, 110–111, 113, 116–118, 125, 127–128, 13, 130, 132, 139, 142–143, 145–146, 148–149, 15, 154–155, 157, 16, 160–162, 17, 19–33, 37, 40–68, 71–72, 74, 77–79, 86, 88–89, 91, 98
   habit.dart39.39%100%100%39.39%173, 189–203, 207–208, 210–211
   habit_completion.g.dart0%100%100%0%13, 15–17, 19–24, 28, 31–41, 44–45, 47, 50–52
   habit_completion.dart0%100%100%0%38, 48–52, 56–60
   skip_pattern.g.dart0%100%100%0%13, 15–17, 19–25, 29, 32–44, 47–48, 50, 53–55
   skip_pattern.dart93.94%100%100%93.94%95–96
lib/providers
   habit_provider.dart11.24%100%100%11.24%100–101, 106–107, 109–110, 115–122, 130–132, 138, 144–148, 151, 153, 158–159, 166, 171–178, 195, 200, 209–210, 214, 218, 221, 223, 229, 249, 255, 262, 267–271, 273–277, 28, 44, 47–49, 52–53, 60, 72–73, 75, 79–82, 84, 88–89, 93–94, 99
lib/services
   skip_pattern_service.dart30.99%100%100%30.99%100, 103–105, 107–109, 111, 167, 173–174, 176–185, 188–189, 194, 200–202, 30, 52, 56–57, 73, 77–78, 80–84, 86–88, 90–92, 94, 97–99

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants