Skip to content

feat(security): add fleet-side suppression/waiver for the --strict gate #179

Description

@rshade

Overview

Part of the security epic #36 — continues #38 (added --strict, closed by PR #161). The opt-in --strict gate promotes HIGH Layer 1 findings to blocking, but there is no way to waive a finding an operator cannot fix. This adds a declarative, fleet-side suppression mechanism so operators can enable --strict without being permanently blocked by findings in artifacts they don't author.

Problem Statement

Surfaced while manually testing the v0.2.5 release (PR #164): deploy --strict rshade/gh-aw-fleet (dry-run) blocks with 21 HIGH findings, all actionlint:* on gh-aw compiler-generated *.lock.yml files (# This file was automatically generated by gh-aw … DO NOT EDIT). These appear on every compiled workflow in every fleet, regardless of source. The only carve-out today is the hardcoded promptinj: prefix in BlockingSecurityFindings (internal/fleet/security_gate.go:68). Net result: --strict ships correct but un-enableable on a real agentics-based fleet.

Proposed Solution

A declarative security.suppress block in fleet.json (overlay-able via fleet.local.json), matched by rule + file glob, applied at the blocking computation only — suppressed findings still surface as advisory, preserving the audit trail.

Config shape (pkg/fleet/config.go)

{
  "version": 1,
  "security": {
    "suppress": [
      {
        "rule": "actionlint:syntax-check",
        "file": ".github/workflows/*.lock.yml",
        "reason": "gh-aw 'queue' concurrency extension; actionlint schema false positive"
      }
    ]
  }
}

New wire-contract types: SecurityConfig + SuppressionRule{Rule, File, Reason string}, hung off a new optional Config.Security *SecurityConfig (json:"security,omitempty") — mirrors the RepoSpec.CompileStrict *bool optional-pointer precedent.

Matching

  • rule matches Finding.RuleID exactly or with a trailing * (e.g. actionlint:*) via stdlib path/filepath.Matchno new dependency (constitution: stdlib-first).
  • file matches Finding.File (slash form, e.g. .github/workflows/ci-doctor.lock.yml) via filepath.Match; single-level * covers the .lock.yml case (avoids a **/doublestar dep).
  • Empty rule or file = wildcard for that dimension; a finding is suppressed iff both dimensions match.

Gate integration

Add a third continue guard inside BlockingSecurityFindings (internal/fleet/security_gate.go:68), mirroring the existing promptinj: carve-out. That function takes only []Finding today, so thread the compiled rules in via a new parameter or a Suppressions field on fleet.SecurityOpts, populated from cfg.Security at the internal/fleet/deploy.go:276 / :375 call sites (the config is already in scope there). Suppression affects only the blocking set; emitSecurityFindingWarnings and appendFindingDiagnostics still print every finding.

Files likely affected

  • pkg/fleet/config.goSecurityConfig/SuppressionRule types + Config.Security field.
  • internal/fleet/load.gomergeConfigs (~line 184) clause so security overlays from fleet.local.json.
  • internal/fleet/security_gate.go — suppression guard; thread through EvaluateStrictSecurityGate.
  • cmd/deploy.go / cmd/sync.go / cmd/upgrade.go — pass cfg.Security into the gate.
  • docs + godoc (additive — no cmd/fleet.SchemaVersion bump).

Acceptance Criteria

  • security.suppress parses from fleet.json and overlays from fleet.local.json.
  • A HIGH finding matching a rule (by rule + file glob) is excluded from the --strict blocking set; deploy --strict on the otherwise-blocked repo exits 0.
  • A HIGH finding matching no rule still blocks.
  • Suppressed findings still print on stderr and in JSON warnings[] (advisory, not hidden).
  • Glob matching uses stdlib only (no new dependency).
  • No cmd.SchemaVersion / fleet.SchemaVersion bump.
  • Per-invocation --strict / --yes remain un-persisted to fleet.json.
  • Table-driven gate tests (mirror internal/fleet/security_gate_test.go) + a load/merge test.
  • Tests pass with adequate coverage.
  • Documentation updated (docs-site security section + README).

Out of Scope

  • Per-repo (RepoSpec.Security) overrides — follow-on.
  • A suppressed: true / reason annotation on the JSON envelope — deferred (see Resolved decisions).
  • Demoting actionlint severity — that is the companion "option 3" issue.
  • Any change to gh aw compile --strict.

Resolved decisions

  • Suppressed findings stay visible as advisory (printed on stderr + JSON warnings[]); suppression removes them from the --strict blocking set only. A suppressed: true / reason envelope annotation is deferred (later enhancement), keeping advisory output byte-identical for now.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort/mediumMedium effort - half day to 1 dayenhancementNew feature or requestroadmap/currentActive development - Immediate FocussecuritySecurity-related issue or feature

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions