Documentation
¶
Index ¶
- Constants
- func AssignFingerprints(findings []Finding)
- func CollectInlineIgnores(content []byte) map[int]inlineDirective
- func ConfigFileNames() []string
- func FixBytes(content []byte, findings []Finding) ([]byte, int, error)
- func FixFile(filePath string, findings []Finding) (int, error)
- func FixFindings(targetPath string, findings []Finding) (int, error)
- func IsGitLabCIPath(p string) bool
- func RuleByID() map[RuleID]RuleSpec
- type ActionRef
- type ActionsPermissions
- type Advisory
- type AttackStage
- type BranchProtection
- type ComboComponent
- type ComboScope
- type ComboSeverity
- type Confidence
- type Config
- type EnabledFlag
- type FeatureStatus
- type Finding
- func ApplyRepoConfig(findings []Finding, cfg *RepoConfig) []Finding
- func AuditActionPins(ctx context.Context, refs []ActionRef, auditor PinAuditor) []Finding
- func CheckCachePoisoningRelease(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckCheckoutPersistCredentials(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckContinueOnErrorJob(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckDebugLoggingEnabled(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckDownloadWithoutChecksum(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckForbiddenUses(refs []ActionRef, policy *ForbiddenUses) []Finding
- func CheckGitHubEnvInjection(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckHardcodedSecrets(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckLongLivedPAT(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckMissingConcurrency(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckMissingTimeout(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckObfuscatedExpression(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckOverprovisionedSecrets(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckPBAC(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckPPE(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckPPELaundered(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckPipeToShell(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckRepoDispatchUnfiltered(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSLSACachePoisoning(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSLSAOIDCTokenScope(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSLSAPermsOverlyBroad(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSLSAProvenance(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSLSAProvenanceIsolated(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSLSAVerifyStep(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSecretInRunOutput(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSecretsInheritPRTarget(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSelfHostedRunners(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckSpoofableActorCondition(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckUnpinnedActions(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckUnpinnedImages(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckUnsoundCondition(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckUnsoundContains(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckUntrustedPullRequestTarget(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckUseTrustedPublishing(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func CheckWorkflowRunArtifactPoisoning(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
- func FilterByConfidence(findings []Finding, min Confidence) []Finding
- func FilterByEnabledRules(findings []Finding, disabled map[RuleID]bool) []Finding
- func FilterByPersona(findings []Finding, persona Persona) []Finding
- func FilterFindings(findings []Finding, ruleset string) []Finding
- func ScanBytes(name string, content []byte) ([]Finding, error)
- func ScanDir(dirPath string) ([]Finding, error)
- func ScanFile(filePath string) ([]Finding, error)
- func ScanGitLabProjectSettings(ctx GitLabProjectContext) []Finding
- func ScanRepositorySettings(ctx RepositoryContext) []Finding
- func StampConfidence(findings []Finding) []Finding
- type ForbiddenUses
- type GitHubPinAuditor
- func (g *GitHubPinAuditor) Advisories(ctx context.Context, owner, repo string) ([]Advisory, error)
- func (g *GitHubPinAuditor) RefKinds(ctx context.Context, owner, repo, ref string) (bool, bool, error)
- func (g *GitHubPinAuditor) RepoArchived(ctx context.Context, owner, repo string) (bool, bool, error)
- func (g *GitHubPinAuditor) ResolveRef(ctx context.Context, owner, repo, ref string) (string, bool, error)
- func (g *GitHubPinAuditor) TagSHAs(ctx context.Context, owner, repo string) ([]string, error)
- type GitLabProjectContext
- type IgnoreEntry
- type JobNode
- type JobNodeWithID
- type Persona
- type PinAuditor
- type Platform
- type RepoConfig
- type RepoInfo
- type RepositoryContext
- type RequiredPullRequestReviews
- type RequiredStatusChecks
- type RuleID
- type RuleOverride
- type RuleSpec
- type RuleSurface
- type SecurityAndAnalysis
- type Severity
- type StepNode
- type ToxicCombo
- type WorkflowNode
- type WorkflowPermissions
Constants ¶
const ( FrameworkOWASP = "owasp" FrameworkSLSABuildL1 = "slsa-build-l1" FrameworkSLSABuildL2 = "slsa-build-l2" FrameworkSLSABuildL3 = "slsa-build-l3" FrameworkSLSASourceL1 = "slsa-source-l1" // version controlled (trivially satisfied for any GitHub repo) FrameworkSLSASourceL2 = "slsa-source-l2" // preserve change history (immutable refs, tag protection) FrameworkSLSASourceL3 = "slsa-source-l3" // continuous technical controls (branch protection, status checks) FrameworkSLSASourceL4 = "slsa-source-l4" // two-party review )
Framework labels grouping rules into compliance/security frameworks. A rule can belong to multiple frameworks at once (e.g. action-pinning is both an OWASP check and a SLSA Build L3 prerequisite). The CLI's --ruleset flag and the web app's ruleset selector accept these as filter values directly, plus the umbrella "slsa" alias that matches any "slsa-*" entry.
SLSA labels track the SLSA v1.2 specification (https://slsa.dev/spec/v1.2/). The Build track levels (L1/L2/L3) are unchanged from v1.0; the Source track (L1 Version Controlled → L2 History & Provenance → L3 Continuous Technical Controls → L4 Two-Party Review) is new/promoted-from-draft in v1.2.
const SettingsFile = "<repository settings>"
SettingsFile is the synthetic Finding.File value used for all repository configuration findings (those that come from the GitHub API, not from a workflow YAML). Reporters and the web SPA recognize this exact value and render the findings apart from per-file findings.
Variables ¶
This section is empty.
Functions ¶
func AssignFingerprints ¶
func AssignFingerprints(findings []Finding)
AssignFingerprints fills each finding's Fingerprint with a stable identity for cross-scan tracking. The hash covers what the finding *is* — rule, file, title, and the description (which names the job/step/action involved) — and deliberately excludes line/column, so unrelated edits that shift a finding down a file don't change its identity.
Two genuinely identical findings in one file (same rule, same description — e.g. copy-pasted steps) get a stable "#n" occurrence suffix, assigned in line order so the earliest occurrence keeps the bare hash across scans.
func CollectInlineIgnores ¶
CollectInlineIgnores parses the raw file content and returns a directive per line that carries a `# pipefort: ignore` comment (1-based line numbers).
func ConfigFileNames ¶
func ConfigFileNames() []string
ConfigFileNames returns the candidate config filenames in precedence order, so the web API can fetch them over the provider's contents API.
func FixBytes ¶
FixBytes applies the auto-fix dispatcher to a CI YAML's content in-memory and returns the mutated bytes plus a count of fixes applied. Returns (nil, 0, nil) when no fix matched — callers can use this to skip opening an empty PR/MR or writing an unchanged file.
Findings are split by platform via the RuleID suffix (`-gl-` → GitLab, otherwise GitHub). Mixed batches are rare (findings come from one file), so we dispatch the whole batch to whichever fixer matches the first platform-tagged finding; the other side's findings fall through unhandled.
func FixFile ¶
FixFile parses a single YAML file, applies AST modifications based on findings, and saves in-place. Thin wrapper around FixBytes — exists so the CLI's --fix path keeps its current behaviour while the web app uses the in-memory primitive directly.
func FixFindings ¶
FixFindings groups findings by file, applies fixes to each file's YAML AST, and writes it back.
func IsGitLabCIPath ¶
IsGitLabCIPath reports whether a path looks like a GitLab CI config file: .gitlab-ci.yml / .gitlab-ci.yaml at the repo root, or any *.yml/.yaml under .gitlab-ci/.
Types ¶
type ActionRef ¶
type ActionRef struct {
File string
Line int
Column int
Owner string
Repo string
Ref string // tag, branch, or 40-hex SHA after '@'
Raw string // full "owner/repo@ref" as written
VersionComment string // trailing "# v3" comment text, if any
}
ActionRef is a single third-party `uses: owner/repo[/path]@ref` reference, with the trailing line comment captured for ref-version-mismatch.
func CollectActionRefs ¶
func CollectActionRefs(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []ActionRef
CollectActionRefs extracts every third-party action reference from a parsed workflow. Local (`./`, `.github/`) and `docker://` references are skipped — the former isn't third-party, the latter is handled by CheckUnpinnedImages.
func CollectActionRefsFromBytes ¶
CollectActionRefsFromBytes parses one GitHub workflow file's bytes and returns its action references. GitLab files have no equivalent `uses:` surface and yield nothing. Used by the web scan pipeline, which holds file content in memory.
func CollectActionRefsFromDir ¶
CollectActionRefsFromDir walks a repo's .github/workflows directory and returns every action reference across its workflow files. Used by the CLI's --audit-pins pass.
type ActionsPermissions ¶
type ActionsPermissions struct {
Enabled bool `json:"enabled"`
AllowedActions string `json:"allowed_actions"` // "all" | "local_only" | "selected"
}
ActionsPermissions mirrors GET /repos/.../actions/permissions.
type Advisory ¶
type Advisory struct {
GHSAID string
Summary string
VulnerableRange string // e.g. "< 1.2.3" or ">= 1.0.0, < 1.2.3"
FirstPatched string // e.g. "1.2.3" ("" if none)
}
Advisory is a minimal view of a GitHub security advisory affecting an action.
type AttackStage ¶
type AttackStage struct {
Order int `json:"order"`
Title string `json:"title"`
Description string `json:"description"`
// RuleID links a stage to the component finding responsible for it. Empty
// for the synthetic terminal "impact" stage.
RuleID RuleID `json:"rule_id"`
}
AttackStage is one ordered node in the attack chain drawn for a combo.
type BranchProtection ¶
type BranchProtection struct {
EnforceAdmins *EnabledFlag `json:"enforce_admins,omitempty"`
RequiredPullRequestReviews *RequiredPullRequestReviews `json:"required_pull_request_reviews,omitempty"`
RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks,omitempty"`
RequiredSignatures *EnabledFlag `json:"required_signatures,omitempty"`
AllowForcePushes *EnabledFlag `json:"allow_force_pushes,omitempty"`
AllowDeletions *EnabledFlag `json:"allow_deletions,omitempty"`
}
BranchProtection is the subset of GET /repos/.../branches/{branch}/protection the rules need. Nested pointer fields follow GitHub's convention of omitting the sub-object entirely when the policy is not in force (e.g. RequiredPullRequestReviews == nil means "PR reviews are not required").
type ComboComponent ¶
ComboComponent ties a matched requirement back to the concrete finding (file:line) that satisfied it.
type ComboScope ¶
type ComboScope string
ComboScope describes how a combo instance is keyed: "file" combos require their anchor ingredient inside one workflow file and emit once per such file; "repo" combos correlate repo-wide and emit at most once per scan.
const ( ScopeFile ComboScope = "file" ScopeRepo ComboScope = "repo" )
type ComboSeverity ¶
type ComboSeverity string
ComboSeverity rates a toxic combination. It is deliberately a separate type from Finding.Severity: combos can be CRITICAL (above the per-finding scale) and must never leak into shouldFail / countBySeverity / CLI exit codes.
const ( ComboCritical ComboSeverity = "CRITICAL" ComboHigh ComboSeverity = "HIGH" )
type Confidence ¶
type Confidence string
Confidence expresses how certain a rule is that a finding is real (as opposed to how bad it would be — that's Severity). Deterministic checks (e.g. "no permissions block") are HIGH; pattern/heuristic checks that can misfire (e.g. secret-name matching, typosquat edit distance) are MEDIUM or LOW. Every finding gets a confidence: checks may stamp one per finding, and StampConfidence backfills the rule's default for the rest.
const ( ConfidenceHigh Confidence = "HIGH" ConfidenceMedium Confidence = "MEDIUM" ConfidenceLow Confidence = "LOW" )
type Config ¶
type Config struct {
Path string `json:"path"`
FailOnSeverity Severity `json:"fail_on_severity"`
Ruleset string `json:"ruleset"` // "all", "owasp", "slsa", "slsa-build-l1|2|3"
}
Config represents options for scanning.
type EnabledFlag ¶
type EnabledFlag struct {
Enabled bool `json:"enabled"`
}
EnabledFlag is GitHub's recurring `{"enabled": bool}` envelope.
type FeatureStatus ¶
type FeatureStatus struct {
Status string `json:"status"`
}
FeatureStatus is GitHub's `{"status": "enabled" | "disabled"}` shape.
type Finding ¶
type Finding struct {
File string `json:"file"`
Line int `json:"line"`
Column int `json:"column"`
Severity Severity `json:"severity"`
Category string `json:"category"` // OWASP Category (e.g. CICD-SEC-04)
RuleID RuleID `json:"rule_id"` // Per-check identifier (matches RuleSpec.ID). Empty for SYSTEM findings.
Title string `json:"title"` // Short description
Description string `json:"description"` // Detailed description of the risk
Recommendation string `json:"recommendation"` // Actionable steps to fix
// Confidence is how certain the check is that this finding is real.
// Backfilled from the rule's DefaultConfidence by StampConfidence; SYSTEM
// findings (empty RuleID) default to HIGH.
Confidence Confidence `json:"confidence,omitempty"`
// Fingerprint is a stable identity for tracking the same finding across
// scans (independent of line/column shifts). Populated by
// AssignFingerprints; empty until then. Feeds the web app's new-finding
// diffing and SARIF partialFingerprints.
Fingerprint string `json:"fingerprint,omitempty"`
}
Finding represents a single security vulnerability/risk found in the pipeline.
func ApplyRepoConfig ¶
func ApplyRepoConfig(findings []Finding, cfg *RepoConfig) []Finding
ApplyRepoConfig applies a repo config to a finished finding list as a pure transform: drop disabled rules, drop file/line-matched ignores, and rewrite severities. SYSTEM findings (empty RuleID) always pass. A nil config is a no-op. This runs after the file paths are normalized to repo-relative so the glob matching in IgnoreEntry.File works.
func AuditActionPins ¶
func AuditActionPins(ctx context.Context, refs []ActionRef, auditor PinAuditor) []Finding
AuditActionPins runs the four online supply-chain audits over the collected references. Network results are memoized per repo/ref so a heavily-reused action (e.g. actions/checkout) is looked up once. Lookup errors are swallowed per-ref so a transient failure can't sink the whole pass; the affected audit simply produces no finding.
func CheckCachePoisoningRelease ¶
func CheckCachePoisoningRelease(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckCachePoisoningRelease flags dependency caching inside a workflow that also publishes a release-shaped artifact. A cache entry poisoned by a lower-trust workflow (e.g. one triggered by a fork PR) can be restored here and flow into the published output.
func CheckCheckoutPersistCredentials ¶
func CheckCheckoutPersistCredentials(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckCheckoutPersistCredentials flags actions/checkout steps inside a privileged-trigger workflow that don't disable persist-credentials, leaving the job token in .git/config for later untrusted steps (CICD-SEC-1).
func CheckContinueOnErrorJob ¶
func CheckContinueOnErrorJob(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckContinueOnErrorJob flags jobs that declare continue-on-error: true at the job level. A job-level `continue-on-error: true` causes the job's conclusion to be reported as success even when its steps fail, so required- check gates, branch-protection enforcement, and audit dashboards lose visibility into the failure.
func CheckDebugLoggingEnabled ¶
func CheckDebugLoggingEnabled(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckDebugLoggingEnabled flags workflow/job/step env blocks that turn on the GitHub Actions debug-logging knobs.
func CheckDownloadWithoutChecksum ¶
func CheckDownloadWithoutChecksum(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckDownloadWithoutChecksum flags inline scripts that download a binary or archive without a paired integrity-verification command in the same step.
func CheckForbiddenUses ¶
func CheckForbiddenUses(refs []ActionRef, policy *ForbiddenUses) []Finding
CheckForbiddenUses enforces a repository's action allow/deny policy from its .pipefort.yml `forbidden-uses` block. It matches each action reference's `owner/repo` (ignoring the @ref) against the policy patterns (globs, e.g. `someorg/*` or `actions/checkout`). Allow and Deny are mutually exclusive; Allow wins when both are set. Returns nil when no policy is configured, so the rule is silent by default.
func CheckGitHubEnvInjection ¶
func CheckGitHubEnvInjection(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckGitHubEnvInjection flags writes to $GITHUB_ENV / $GITHUB_PATH whose value derives from untrusted input — either interpolated directly, or laundered through a tainted env: var. Poisoning these files injects environment variables / PATH entries into every subsequent step (CICD-SEC-4).
func CheckHardcodedSecrets ¶
func CheckHardcodedSecrets(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckHardcodedSecrets checks for Insufficient Credential Hygiene (CICD-SEC-6) specifically finding hardcoded passwords, tokens, API keys.
func CheckLongLivedPAT ¶
func CheckLongLivedPAT(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckLongLivedPAT flags secrets-context references whose name strongly implies a long-lived personal access token. We walk env blocks (workflow, job, step) and `with:` inputs on steps; the literal GITHUB_TOKEN is excluded.
func CheckMissingConcurrency ¶
func CheckMissingConcurrency(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckMissingConcurrency flags deploy/release-shaped workflows that lack any concurrency: guard (workflow-level or on every job). Overlapping runs of such a workflow race on shared caches, artifacts, and deploy targets.
func CheckMissingTimeout ¶
func CheckMissingTimeout(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckMissingTimeout checks if jobs have defined timeout-minutes to prevent resource exhaustion.
func CheckObfuscatedExpression ¶
func CheckObfuscatedExpression(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckObfuscatedExpression flags obfuscation that hides untrusted-input flow from human review: index-notation context access and base64-decode-and-run chains in run scripts.
func CheckOverprovisionedSecrets ¶
func CheckOverprovisionedSecrets(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckOverprovisionedSecrets flags two over-exposure patterns: toJSON(secrets) (dumps every secret at once) and workflow-level env: mapping of secrets.* (leaks the secret into every step of every job).
func CheckPBAC ¶
func CheckPBAC(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckPBAC checks for Pipeline-Based Access Controls (CICD-SEC-5) specifically missing top-level and job-level permissions blocks.
func CheckPPE ¶
func CheckPPE(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckPPE checks for Poisoned Pipeline Execution (CICD-SEC-4) — untrusted github context interpolated into code. Two injection surfaces are covered: inline run: scripts, and action with: inputs that are evaluated as code (most notably actions/github-script's `script:` input).
func CheckPPELaundered ¶
func CheckPPELaundered(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckPPELaundered extends PPE detection (same rule as CheckPPE) to untrusted data laundered through a tainted env: var and re-interpolated as `${{ env.X }}` in a run script or action input — a bypass of the direct github.event.* match.
func CheckPipeToShell ¶
func CheckPipeToShell(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckPipeToShell checks if steps run curl/wget and pipe it directly to bash/sh.
func CheckRepoDispatchUnfiltered ¶
func CheckRepoDispatchUnfiltered(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckRepoDispatchUnfiltered flags workflows triggered by repository_dispatch without an explicit types: allowlist. Without it, any caller with a repo-scoped token can fire the workflow with arbitrary event_type and client_payload — effectively granting third-party services an unbounded trigger surface.
func CheckSLSACachePoisoning ¶
func CheckSLSACachePoisoning(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSLSACachePoisoning flags actions/cache (or actions/cache/restore) keys derived from PR-controlled inputs in workflows that run with elevated pull_request_target privileges. An attacker controls the cache namespace and can plant payloads consumed by the trusted base branch.
func CheckSLSAOIDCTokenScope ¶
func CheckSLSAOIDCTokenScope(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSLSAOIDCTokenScope flags jobs that use attest/cosign/slsa-generator signing tooling but don't declare id-token: write at the job level (and the workflow-level permissions don't already grant it).
func CheckSLSAPermsOverlyBroad ¶
func CheckSLSAPermsOverlyBroad(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSLSAPermsOverlyBroad flags an explicit permissions block that grants write-all (the antipattern that's strictly worse than no block: it makes the maintainer think they applied least privilege when they actually granted everything).
func CheckSLSAProvenance ¶
func CheckSLSAProvenance(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSLSAProvenance flags workflows that publish a release-shaped artifact without any provenance/signing step. Heuristic: presence of any "publishes" action or run-line, absence of every provenance/signing action *and* the SLSA reusable workflow.
func CheckSLSAProvenanceIsolated ¶
func CheckSLSAProvenanceIsolated(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSLSAProvenanceIsolated flags workflows that have in-job attestation steps but no slsa-github-generator reusable workflow call. The in-job form is L2 only — L3 requires the signing context the user's run-steps can't touch, which the reusable workflow provides.
func CheckSLSAVerifyStep ¶
func CheckSLSAVerifyStep(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSLSAVerifyStep emits an INFO finding when the workflow consumes artifacts (downloads or pulls images) but no verification step is present. This is a recommendation, not a security defect — INFO severity keeps it out of failure thresholds by default.
func CheckSecretInRunOutput ¶
func CheckSecretInRunOutput(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSecretInRunOutput flags inline scripts that print a secret to logs or persist it to step output/env, both of which defeat GitHub's log masking.
func CheckSecretsInheritPRTarget ¶
func CheckSecretsInheritPRTarget(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSecretsInheritPRTarget flags jobs that call a reusable workflow with secrets: inherit inside a privileged-trigger workflow, handing every repository secret to a workflow running in an attacker-influenced context (CICD-SEC-4).
func CheckSelfHostedRunners ¶
func CheckSelfHostedRunners(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSelfHostedRunners checks if jobs run on self-hosted runners which may expose internal environments.
func CheckSpoofableActorCondition ¶
func CheckSpoofableActorCondition(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckSpoofableActorCondition flags an `if:` gate that compares github.actor or github.triggering_actor to a bot login as a security control. Those fields are spoofable in several trigger contexts, so they must not be the sole guard on privileged logic (CICD-SEC-1).
func CheckUnpinnedActions ¶
func CheckUnpinnedActions(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckUnpinnedActions checks for Dependency Chain Abuse (CICD-SEC-3) specifically referencing third-party actions by tag/branch instead of full commit SHA.
func CheckUnpinnedImages ¶
func CheckUnpinnedImages(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckUnpinnedImages checks for Dependency Chain Abuse (CICD-SEC-3) via container images referenced by a mutable tag instead of an immutable digest. Three surfaces are covered, all of which pull an OCI image at run time:
- job-level `container:` (scalar `image:tag` or a mapping with `image:`)
- `services.<name>.image`
- step `uses: docker://image:tag`
A tag like `:latest` or `:18` is mutable — the registry can repoint it to a different (possibly malicious) image — so the build is only reproducible when the image is pinned by `@sha256:` digest. Pure-expression images (`${{ ... }}`) are skipped: their value isn't knowable statically.
func CheckUnsoundCondition ¶
func CheckUnsoundCondition(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckUnsoundCondition flags job/step `if:` values that GitHub evaluates to a constant-truthy string rather than a boolean. The canonical bug is mixing literal text with an expression — either multiple ${{ }} blocks joined by literal operators, or literal text outside a single ${{ }} block. GitHub then treats the whole value as a non-empty string, which is always truthy, so the guard never actually gates.
func CheckUnsoundContains ¶
func CheckUnsoundContains(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckUnsoundContains flags contains('literal haystack', <context>) where the needle is attacker-influenceable (a ref, label, title, …). A crafted value that is a substring of the haystack satisfies the check and passes the gate.
func CheckUntrustedPullRequestTarget ¶
func CheckUntrustedPullRequestTarget(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckUntrustedPullRequestTarget checks for Insufficient Flow Control (CICD-SEC-1): a privileged trigger (pull_request_target or workflow_run) that checks out the untrusted head ref and then runs tests/builds with secrets in scope.
func CheckUseTrustedPublishing ¶
func CheckUseTrustedPublishing(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckUseTrustedPublishing nudges publishing steps that use a long-lived registry token toward OIDC trusted publishing (short-lived, per-run credentials). Currently targets PyPI (the most mature trusted-publishing ecosystem) plus token-based npm/cargo/rubygems publishes.
func CheckWorkflowRunArtifactPoisoning ¶
func CheckWorkflowRunArtifactPoisoning(file string, workflow *WorkflowNode, jobs []JobNodeWithID) []Finding
CheckWorkflowRunArtifactPoisoning flags workflow_run workflows that download artifacts produced by the (untrusted) triggering run. A fork PR can upload a poisoned artifact the privileged workflow_run then trusts (CICD-SEC-1).
func FilterByConfidence ¶
func FilterByConfidence(findings []Finding, min Confidence) []Finding
FilterByConfidence keeps findings at or above the given minimum confidence. An empty minimum (or LOW) keeps everything. SYSTEM findings always pass.
func FilterByEnabledRules ¶
FilterByEnabledRules drops findings whose RuleID is explicitly disabled. Findings with an empty RuleID (synthetic SYSTEM/INFO notices like parse errors or settings-audit failures) are never toggleable — they always pass. A nil/empty disabled map is a no-op; callers can pass the result of EffectiveDisabledRules unconditionally.
func FilterByPersona ¶
FilterByPersona keeps findings whose rule belongs to the selected persona tier or below: `regular` keeps only regular rules, `pedantic` adds pedantic ones, `auditor` keeps everything. SYSTEM findings and unknown rules always pass.
func FilterFindings ¶
FilterFindings filters findings based on the ruleset. Supported values:
- "", "all" — every finding (no filter).
- "owasp" — rules tagged with FrameworkOWASP.
- "slsa" — rules tagged with any SLSA v1.2 framework (Build or Source track).
- "slsa-build-l1|l2|l3" — rules for that specific SLSA v1.2 Build level.
- "slsa-source-l1|l2|l3|l4" — rules for that specific SLSA v1.2 Source level.
SYSTEM findings (no RuleID — parse errors, settings-audit notices) always pass. Rules with no framework tags only appear under "all".
func ScanBytes ¶
ScanBytes scans raw CI YAML held in memory. It dispatches on the file path: .gitlab-ci.yml / .gitlab-ci/*.yml is routed through the GitLab scanner; everything else is treated as a GitHub Actions workflow. The name argument is used both for dispatch and as the Finding.File label so callers need not write content to disk before scanning.
func ScanDir ¶
ScanDir walks a directory looking for CI/CD configs to scan:
- .github/workflows/*.yml / *.yaml (GitHub Actions)
- .gitlab-ci.yml at the repo root and .gitlab-ci/**/*.yml (GitLab CI)
Both layouts are walked when present; findings from each are merged in path order. When neither is present we fall back to the legacy "walk everything" behaviour for ad-hoc YAML collections.
func ScanGitLabProjectSettings ¶
func ScanGitLabProjectSettings(ctx GitLabProjectContext) []Finding
ScanGitLabProjectSettings runs the GitLab project-configuration checks and returns the findings. Each finding carries the synthetic SettingsFile path and Platform-gitlab rule ids so the UI groups them like the GitHub settings audit. Mirrors ScanRepositorySettings's hardcoded-sequence style.
func ScanRepositorySettings ¶
func ScanRepositorySettings(ctx RepositoryContext) []Finding
ScanRepositorySettings runs every repository-settings audit rule against the fetched context and returns the resulting findings. It mirrors ScanBytes's hardcoded-sequence style at scanner.go so a new rule means "add a line here", with no registry to update.
func StampConfidence ¶
StampConfidence backfills Finding.Confidence from the rule's DefaultConfidence for findings that don't carry one. SYSTEM findings (empty RuleID) and findings for unknown rules default to HIGH. Mutates in place and returns the slice for call-site convenience.
type ForbiddenUses ¶
ForbiddenUses is an allow/deny policy for action references. Exactly one of Allow (only these are permitted) or Deny (these are forbidden) should be set; when both are present, Allow takes precedence.
type GitHubPinAuditor ¶
GitHubPinAuditor implements PinAuditor against api.github.com. A token is optional but strongly recommended — anonymous requests share a 60/hour limit. It reuses the package's githubAPIBaseURL var so tests can point it at an httptest server.
func NewGitHubPinAuditor ¶
func NewGitHubPinAuditor(token string) *GitHubPinAuditor
NewGitHubPinAuditor builds an auditor with a 10s-per-request HTTP client.
func (*GitHubPinAuditor) Advisories ¶
Advisories returns GHSA entries affecting an action via the global advisories API, filtered to the actions ecosystem and this package.
func (*GitHubPinAuditor) RefKinds ¶
func (g *GitHubPinAuditor) RefKinds(ctx context.Context, owner, repo, ref string) (bool, bool, error)
RefKinds reports whether ref exists as a branch and/or a tag via the git refs API. A 404 on a kind means that kind doesn't exist.
func (*GitHubPinAuditor) RepoArchived ¶
func (g *GitHubPinAuditor) RepoArchived(ctx context.Context, owner, repo string) (bool, bool, error)
RepoArchived reports whether owner/repo is archived via the repo API.
func (*GitHubPinAuditor) ResolveRef ¶
func (g *GitHubPinAuditor) ResolveRef(ctx context.Context, owner, repo, ref string) (string, bool, error)
ResolveRef resolves a tag/branch/SHA to its commit SHA via the commits API. A 404 means the ref/SHA isn't present in the repo (found=false).
type GitLabProjectContext ¶
type GitLabProjectContext struct {
ProjectPath string
WebURL string
DefaultBranch string
// OnlyAllowMergeIfPipelineSucceeds mirrors the project setting of the same
// name; nil when the project payload didn't include it.
OnlyAllowMergeIfPipelineSucceeds *bool
// PublicBuilds is the "public pipelines" project setting.
PublicBuilds *bool
// ApprovalsBeforeMerge is the required approval count; nil when the
// approvals API is unavailable (Free tier returns 404).
ApprovalsBeforeMerge *int
// DefaultBranchProtected is true when the default branch has a Protected
// Branch entry. DefaultBranchAllowsForcePush reflects that entry's
// allow_force_push. Both are meaningful only when protected-branch data was
// fetched (BranchProtectionFetched).
BranchProtectionFetched bool
DefaultBranchProtected bool
DefaultBranchAllowsForcePush bool
}
GitLabProjectContext is the GitLab project configuration the settings audit inspects. It is the GitLab analog of RepositoryContext (which is GitHub-shaped); the API layer fetches it and hands it to ScanGitLabProjectSettings. Pointer fields distinguish "not fetched / not available on this tier" (nil) from a real value.
type IgnoreEntry ¶
IgnoreEntry suppresses a rule at a file (glob, repo-relative) and, optionally, specific 1-based line numbers. An empty Lines slice suppresses the whole file.
type JobNode ¶
type JobNode struct {
Name yaml.Node `yaml:"name"`
RunsOn yaml.Node `yaml:"runs-on"`
Permissions yaml.Node `yaml:"permissions"`
Env yaml.Node `yaml:"env"`
Concurrency yaml.Node `yaml:"concurrency"`
Steps yaml.Node `yaml:"steps"`
If yaml.Node `yaml:"if"`
TimeoutMinutes yaml.Node `yaml:"timeout-minutes"`
}
JobNode represents a single job in a workflow.
type JobNodeWithID ¶
JobNodeWithID wraps a JobNode with its mapping key (ID).
type Persona ¶
type Persona string
Persona groups rules by signal-to-noise, zizmor-style. The CLI's --persona flag admits rules at or below the selected tier: `regular` (the default) keeps only high-signal security checks, `pedantic` adds hygiene/best-practice nits, and `auditor` adds everything — including checks that surface things worth a look but rarely actionable on their own.
type PinAuditor ¶
type PinAuditor interface {
// ResolveRef returns the commit SHA a ref (tag/branch/sha) resolves to in
// owner/repo, and whether it exists there at all (found=false on 404).
ResolveRef(ctx context.Context, owner, repo, ref string) (sha string, found bool, err error)
// Advisories returns published advisories affecting the owner/repo action.
Advisories(ctx context.Context, owner, repo string) ([]Advisory, error)
// RepoArchived reports whether owner/repo is archived (read-only) upstream.
// ok=false when the repo can't be read (404/error) so the caller skips it.
RepoArchived(ctx context.Context, owner, repo string) (archived bool, ok bool, err error)
// RefKinds reports whether a ref exists upstream as a branch and/or a tag.
RefKinds(ctx context.Context, owner, repo, ref string) (isBranch, isTag bool, err error)
// TagSHAs returns the commit SHAs that owner/repo's tags point at.
TagSHAs(ctx context.Context, owner, repo string) ([]string, error)
}
PinAuditor performs the network lookups the online audits need. It is an interface so the audits can be unit-tested with a fake (no network).
type Platform ¶
type Platform string
Platform names the CI/CD platform a rule targets. Empty defaults to GitHub for back-compat with the existing rule catalog — every pre-multi-provider rule was implicitly GitHub Actions. New GitLab-specific rules carry PlatformGitLab. Portable rules (e.g. best-prac-1-pipe-to-shell, cicd-sec-9-download-without-checksum) carry PlatformAny.
type RepoConfig ¶
type RepoConfig struct {
// Ruleset / MinConfidence / Persona are defaults for the corresponding CLI
// flags; an explicit flag always wins (see LoadRepoConfig callers).
Ruleset string `yaml:"ruleset"`
MinConfidence string `yaml:"min-confidence"`
Persona string `yaml:"persona"`
// Rules holds per-rule overrides keyed by RuleID.
Rules map[string]RuleOverride `yaml:"rules"`
// ForbiddenUses drives the cicd-sec-5-forbidden-uses rule (allow XOR deny
// list of action references). Consumed by that check; nil = rule silent.
ForbiddenUses *ForbiddenUses `yaml:"forbidden-uses"`
}
RepoConfig is an in-repo `.pipefort.yml` — the CLI-friendly counterpart to the web app's DB-backed rule settings. It lets a repository ship its own scan preferences and per-rule suppressions alongside its code, so a `pipefort` run needs no external state. zizmor's `zizmor.yml` is the direct analog.
Every field is optional. The zero value applies nothing.
func LoadRepoConfig ¶
func LoadRepoConfig(dir string) (*RepoConfig, string, error)
LoadRepoConfig looks for a config file under dir (in repoConfigNames order) and parses it. Returns (nil, "", nil) when none is present. The returned string is the relative path found, for user-facing messaging.
func ParseRepoConfig ¶
func ParseRepoConfig(data []byte) (*RepoConfig, error)
ParseRepoConfig unmarshals config bytes and validates enum-ish fields so a typo (e.g. persona: auditer) is surfaced instead of silently ignored.
func (*RepoConfig) DisabledRuleIDs ¶
func (c *RepoConfig) DisabledRuleIDs() map[RuleID]bool
DisabledRuleIDs returns the set of rules the config turns off. Used by the web layer to union config-disabled rules into the DB-backed disabled set (a repo config may further-restrict, never re-enable).
type RepoInfo ¶
type RepoInfo struct {
Private bool `json:"private"`
Visibility string `json:"visibility"` // "public" | "private" | "internal"
DefaultBranch string `json:"default_branch"`
SecurityAndAnalysis *SecurityAndAnalysis `json:"security_and_analysis,omitempty"`
}
RepoInfo is the subset of GET /repos/{owner}/{repo} the settings audit cares about. SecurityAndAnalysis is only present for repos where GitHub has decided to surface the feature toggles (always for public, conditionally for private).
type RepositoryContext ¶
type RepositoryContext struct {
Owner string
Repo string
DefaultBranch string
HTMLURL string // e.g. https://github.com/{owner}/{repo} — used to build remediation links
Repository *RepoInfo
BranchProtection *BranchProtection
WorkflowPerms *WorkflowPermissions
ActionsPolicy *ActionsPermissions
DependabotAlerts bool // true == GitHub returned 204 ("enabled")
HasCodeowners bool // true when any of CODEOWNERS / .github/CODEOWNERS / docs/CODEOWNERS exists
// protection because the repository's plan doesn't support protected
// branches (private repo on a free plan), as opposed to it simply being
// unconfigured (BranchProtection == nil with this false). The audit reports
// this as an INFO note instead of a HIGH "no protection" finding the owner
// can't act on without upgrading.
BranchProtectionUnavailable bool
}
RepositoryContext is the bundle of GitHub-side configuration the settings-audit rules inspect. It mirrors what FetchRepositorySettings returns from pkg/api/github.go. Pointer fields are nil when the corresponding endpoint returns 404 (or is otherwise unavailable); callers must treat nil as "not configured" rather than panicking.
type RequiredPullRequestReviews ¶
type RequiredPullRequestReviews struct {
DismissStaleReviews bool `json:"dismiss_stale_reviews"`
RequireCodeOwnerReviews bool `json:"require_code_owner_reviews"`
RequiredApprovingReviewCount int `json:"required_approving_review_count"`
}
RequiredPullRequestReviews mirrors the branch-protection sub-object.
type RequiredStatusChecks ¶
type RequiredStatusChecks struct {
Strict bool `json:"strict"`
}
RequiredStatusChecks mirrors the branch-protection sub-object.
type RuleID ¶
type RuleID string
RuleID is the stable, per-check identifier (e.g. "best-prac-2-missing-timeout"). Finding.Category is the coarser OWASP/best-prac group ("CICD-SEC-1", "BEST-PRAC-2") that the existing "owasp" ruleset filter and the auto-fixer dispatch on. We keep both: Category preserves backward compatibility with persisted findings and the fixer; RuleID gives users a per-check toggle that doesn't accidentally mute nine sibling branch-protection rules just because they all share CICD-SEC-1.
const ( RulePPECheckout RuleID = "cicd-sec-1-ppe-checkout" RuleLongLivedPAT RuleID = "cicd-sec-2-long-lived-pat" RuleUnpinnedAction RuleID = "cicd-sec-3-unpinned-action" RuleUnpinnedImage RuleID = "cicd-sec-3-unpinned-image" // Online pinned-action audits (supplychain_online_rules.go). Opt-in: emitted // only by AuditActionPins (CLI --audit-pins / the web scan pipeline), never by // the offline ScanBytes pass. RuleKnownVulnerableAction RuleID = "cicd-sec-3-known-vulnerable-action" RuleImpostorCommit RuleID = "cicd-sec-3-impostor-commit" RuleRefVersionMismatch RuleID = "cicd-sec-3-ref-version-mismatch" RuleTyposquatAction RuleID = "cicd-sec-3-typosquat-action" RuleArchivedAction RuleID = "cicd-sec-3-archived-action" RuleStaleActionRef RuleID = "cicd-sec-3-stale-action-ref" RuleRefConfusion RuleID = "cicd-sec-3-ref-confusion" RulePPEShellInjection RuleID = "cicd-sec-4-ppe-shell-injection" RuleMissingPermissions RuleID = "cicd-sec-5-missing-permissions" RuleHardcodedSecrets RuleID = "cicd-sec-6-hardcoded-secrets" RuleDebugLoggingEnabled RuleID = "cicd-sec-7-debug-logging-enabled" RuleRepoDispatchUnfilt RuleID = "cicd-sec-8-repository-dispatch-unfiltered" RuleDownloadNoChecksum RuleID = "cicd-sec-9-download-without-checksum" RuleContinueOnErrorJob RuleID = "cicd-sec-10-continue-on-error-job" RulePipeToShell RuleID = "best-prac-1-pipe-to-shell" RuleMissingTimeout RuleID = "best-prac-2-missing-timeout" RuleSelfHostedRunners RuleID = "best-prac-3-self-hosted-runners" // Additional workflow checks (rules.go / owasp_extended_rules.go). RuleWorkflowRunArtifactPoisoning RuleID = "cicd-sec-1-workflow-run-artifact-poisoning" RuleCheckoutPersistCreds RuleID = "cicd-sec-1-checkout-persist-credentials" RuleSecretsInheritPRTarget RuleID = "cicd-sec-4-secrets-inherit-pr-target" RuleSecretInRunOutput RuleID = "cicd-sec-6-secret-in-run-output" // Injection-depth checks (injection_rules.go). RuleGitHubEnvInjection RuleID = "cicd-sec-4-github-env-injection" RuleSpoofableActorCondition RuleID = "cicd-sec-1-spoofable-actor-condition" // Offline rule-parity batch (condition_rules.go / obfuscation_rules.go / // cache_rules.go / publishing_rules.go / concurrency_rules.go / // owasp_extended_rules.go). RuleUnsoundCondition RuleID = "cicd-sec-1-unsound-condition" RuleUnsoundContains RuleID = "cicd-sec-1-unsound-contains" RuleObfuscatedExpr RuleID = "cicd-sec-4-obfuscated-expression" RuleCachePoisonRelease RuleID = "cicd-sec-4-cache-poisoning-release" RuleOverprovSecrets RuleID = "cicd-sec-6-overprovisioned-secrets" RuleUseTrustedPublish RuleID = "cicd-sec-2-use-trusted-publishing" RuleMissingConcurrency RuleID = "best-prac-4-missing-concurrency" // Config-driven action allow/deny policy (forbidden_uses.go). Silent unless // a .pipefort.yml forbidden-uses block is present. RuleForbiddenUses RuleID = "cicd-sec-5-forbidden-uses" // SLSA Build-track workflow checks (slsa_rules.go) ------------------------ RuleSLSAProvenance RuleID = "slsa-build-l2-provenance" RuleSLSAProvenanceIsolated RuleID = "slsa-build-l3-provenance-isolated" RuleSLSAOIDCTokenScope RuleID = "slsa-build-l2-oidc-token-scope" RuleSLSAPermsOverlyBroad RuleID = "slsa-build-l2-perms-overly-broad" RuleSLSACachePoisoning RuleID = "slsa-build-l3-cache-poisoning" RuleSLSAVerifyStep RuleID = "slsa-build-l2-verify-step" // GitLab CI workflow checks (gitlab_rules.go) ----------------------------- // One rule per OWASP CI/CD Top 10 risk that has a portable GitLab analog. // IDs follow the `cicd-sec-N-gl-*` convention so the GitHub IDs stay // stable. The portable rules (best-prac-1, cicd-sec-9) reuse their // existing IDs across both platforms — there is no `-gl-` parallel. RuleGitLabMRTarget RuleID = "cicd-sec-1-gl-mr-target" RuleGitLabPATSecret RuleID = "cicd-sec-2-gl-pat-secret" RuleGitLabUnpinnedInclude RuleID = "cicd-sec-3-gl-unpinned-include" RuleGitLabShellInjection RuleID = "cicd-sec-4-gl-shell-injection" RuleGitLabHardcodedSecrets RuleID = "cicd-sec-6-gl-hardcoded-secrets" RuleGitLabDebugTrace RuleID = "cicd-sec-7-gl-debug-trace" RuleGitLabTriggerUnfiltered RuleID = "cicd-sec-8-gl-trigger-unfiltered" RuleGitLabAllowFailure RuleID = "cicd-sec-10-gl-allow-failure" RuleGitLabMissingTimeout RuleID = "best-prac-2-gl-missing-timeout" RuleGitLabSelfHostedTags RuleID = "best-prac-3-gl-self-hosted-tags" RuleGitLabMissingResGroup RuleID = "best-prac-4-gl-missing-resource-group" // GitLab project-settings checks (settings_gitlab.go, Surface repo-settings). RuleGitLabBPMissing RuleID = "cicd-sec-1-gl-bp-missing" RuleGitLabBPForcePush RuleID = "cicd-sec-1-gl-bp-force-push" RuleGitLabMergeNoPipeline RuleID = "cicd-sec-1-gl-merge-without-pipeline" RuleGitLabNoApprovals RuleID = "cicd-sec-1-gl-no-approvals" RuleGitLabPublicPipelines RuleID = "cicd-sec-4-gl-public-pipelines" )
Workflow checks (ScanBytes) ------------------------------------------------- Each ID maps to one of the Check* functions in rules.go / slsa_rules.go.
const ( RuleBPMissing RuleID = "cicd-sec-1-bp-missing" RuleBPForcePush RuleID = "cicd-sec-1-bp-force-push" RuleBPDeletion RuleID = "cicd-sec-1-bp-deletion" RuleBPNoReview RuleID = "cicd-sec-1-bp-no-review" RuleBPFewReviewers RuleID = "cicd-sec-1-bp-few-reviewers" RuleBPStaleReviews RuleID = "cicd-sec-1-bp-stale-reviews" RuleBPNoStatusChecks RuleID = "cicd-sec-1-bp-no-status-checks" RuleBPAdminBypass RuleID = "cicd-sec-1-bp-admin-bypass" RuleBPNoCodeownersReview RuleID = "cicd-sec-1-bp-no-codeowners-review" RuleBPNoSignedCommits RuleID = "cicd-sec-1-bp-no-signed-commits" RuleWPermWrite RuleID = "cicd-sec-4-wperm-write" RuleWPermPRApprove RuleID = "cicd-sec-4-wperm-pr-approve" RuleActionsAllAllowed RuleID = "cicd-sec-5-actions-all-allowed" RuleDependabotAlertsOff RuleID = "cicd-sec-3-dependabot-alerts-off" RuleDependabotFixesOff RuleID = "cicd-sec-3-dependabot-fixes-off" RuleSecretScanningOff RuleID = "cicd-sec-6-secret-scanning-off" RuleSecretPushProtectionOff RuleID = "cicd-sec-6-secret-push-protection-off" )
Repository-settings checks (ScanRepositorySettings) ------------------------ Each ID maps to one of the helper functions in settings.go. The slugs match the existing docs/rules/<slug>.mdx pages so doc URLs are derivable.
type RuleOverride ¶
type RuleOverride struct {
// Enabled, when non-nil and false, disables the rule entirely. A repo
// config can only *disable* a rule, never re-enable one an org policy
// turned off (enforced by the web layer, not here).
Enabled *bool `yaml:"enabled"`
// Severity, when set, rewrites the finding severity (HIGH/MEDIUM/LOW/INFO).
Severity string `yaml:"severity"`
// Ignore suppresses findings of this rule at specific files/lines.
Ignore []IgnoreEntry `yaml:"ignore"`
}
RuleOverride is a single rule's configuration.
type RuleSpec ¶
type RuleSpec struct {
ID RuleID `json:"id"`
Category string `json:"category"` // "CICD-SEC-1", "BEST-PRAC-2", ...
Title string `json:"title"`
DefaultSeverity Severity `json:"default_severity"`
Surface RuleSurface `json:"surface"`
Platform Platform `json:"platform,omitempty"` // empty = github (legacy default)
Description string `json:"description"`
DocURL string `json:"doc_url"` // "/rules/<id>"
// Frameworks the rule serves. A rule with no frameworks is unfiltered (it
// only shows up under the "all" ruleset). Use the Framework* constants.
Frameworks []string `json:"frameworks"`
// DefaultConfidence is stamped onto findings that don't carry their own.
// Left empty in the catalog literal it normalizes to ConfidenceHigh —
// only heuristic rules need an explicit entry.
DefaultConfidence Confidence `json:"default_confidence"`
// Persona tiers the rule for noise filtering. Empty normalizes to
// PersonaRegular — only pedantic/auditor rules need an explicit entry.
Persona Persona `json:"persona"`
}
RuleSpec describes a single user-toggleable check. The web app reads this catalog over GET /api/rules to render the Rule Settings page; the Go API uses it on PUT to validate that a rule_id submitted by the client is real.
func Rules ¶
func Rules() []RuleSpec
Rules returns the canonical catalog in the order pages should render. New scanner checks must add an entry here (and a docs/rules/<id>.mdx page — see CLAUDE.md convention 3) so users can toggle them.
Entries may leave DefaultConfidence/Persona empty; Rules() normalizes those to ConfidenceHigh/PersonaRegular so consumers never see a zero value.
type RuleSurface ¶
type RuleSurface string
RuleSurface tells the UI which scan path emits a rule, so the Rule Settings page can group "what I check in my workflow YAMLs" apart from "what I check in GitHub's repository configuration".
const ( SurfaceWorkflow RuleSurface = "workflow" // produced by ScanBytes SurfaceRepoSettings RuleSurface = "repo-settings" // produced by ScanRepositorySettings )
type SecurityAndAnalysis ¶
type SecurityAndAnalysis struct {
SecretScanning *FeatureStatus `json:"secret_scanning,omitempty"`
SecretScanningPushProtection *FeatureStatus `json:"secret_scanning_push_protection,omitempty"`
DependabotSecurityUpdates *FeatureStatus `json:"dependabot_security_updates,omitempty"`
}
SecurityAndAnalysis mirrors the nested object GitHub returns when the repository has any of the related features available. Each sub-field is itself nullable — secret scanning, for example, is only reported on repos where GitHub Advanced Security applies.
type StepNode ¶
type StepNode struct {
Name yaml.Node `yaml:"name"`
Uses yaml.Node `yaml:"uses"`
Run yaml.Node `yaml:"run"`
If yaml.Node `yaml:"if"`
Env yaml.Node `yaml:"env"`
With yaml.Node `yaml:"with"`
}
StepNode represents a single step in a job.
type ToxicCombo ¶
type ToxicCombo struct {
ID string `json:"id"`
Title string `json:"title"`
Severity ComboSeverity `json:"severity"`
Scope ComboScope `json:"scope"`
File string `json:"file"` // set for file-scoped instances; "" repo-wide
Impact string `json:"impact"`
BreakChain string `json:"break_chain"`
BreakChainRule RuleID `json:"break_chain_rule"`
Stages []AttackStage `json:"stages"`
Components []ComboComponent `json:"components"`
}
ToxicCombo is one detected toxic combination instance.
func DetectToxicCombinations ¶
func DetectToxicCombinations(findings []Finding) []ToxicCombo
DetectToxicCombinations correlates the given findings into toxic combinations. Callers must pass findings AFTER rule/ruleset filtering so a disabled rule can never form a combo. The result is deterministically ordered (severity, then id, then file) and is nil for empty input.
type WorkflowNode ¶
type WorkflowNode struct {
Name yaml.Node `yaml:"name"`
On yaml.Node `yaml:"on"`
Permissions yaml.Node `yaml:"permissions"`
Env yaml.Node `yaml:"env"`
Concurrency yaml.Node `yaml:"concurrency"`
Jobs yaml.Node `yaml:"jobs"`
}
WorkflowNode is a wrapper around the yaml.Node to parse a GitHub Actions workflow.
type WorkflowPermissions ¶
type WorkflowPermissions struct {
DefaultWorkflowPermissions string `json:"default_workflow_permissions"` // "read" | "write"
CanApprovePullRequestReviews bool `json:"can_approve_pull_request_reviews"` // self-approval
}
WorkflowPermissions mirrors GET /repos/.../actions/permissions/workflow.
Source Files
¶
- blanklines.go
- catalog.go
- combos.go
- filters.go
- fingerprint.go
- fixer.go
- forbidden_uses.go
- gitlab_fixer.go
- gitlab_rules.go
- injection_rules.go
- inline_ignores.go
- owasp_extended_rules.go
- parity_rules.go
- repoconfig.go
- rules.go
- scanner.go
- settings.go
- settings_gitlab.go
- slsa_rules.go
- supplychain_github.go
- supplychain_online_rules.go
- supplychain_rules.go
- supplychain_typosquat.go
- types.go