Documentation
¶
Overview ¶
Package ir is the provider-neutral intermediate representation of a pipeline: jobs, images, includes, branches, variables and settings in the one shape every Rego policy reads, built by the gitlab and github packages.
Package ir defines the provider-agnostic intermediate representation of a CI/CD pipeline. Provider collectors produce an *ir.NormalizedPipeline that the OPA rule engine consumes regardless of the source.
Index ¶
- Constants
- Variables
- func ClassifyGates(p *NormalizedPipeline)
- func IsPRHeadRef(ref string) bool
- func IsPRHeadShaRef(ref string) bool
- func MarkInert(p *NormalizedPipeline)
- func NormalizeVisibility(raw string) string
- func OverrideFingerprint(jobs []OverriddenJob) string
- func SplitDefinedIn(s string) (repo, ref, file string, line int, ok bool)
- func ViaText(via []string) string
- func WorkflowDefinedIn(origin string, line int) string
- func WorkflowStateKey(originFile string) string
- type Action
- type ActionMetadata
- type AnalyzedWorkflow
- type ArtifactRef
- type Branch
- type CacheModeDecl
- type CacheRef
- type DependabotConfig
- type Dockerfile
- type DockerfileBase
- type Environment
- type Function
- type Gate
- type GateClass
- type Image
- type Include
- type Job
- type MRApprovalRule
- type MRApprovalSettings
- type MRSettings
- type MutableRemoteExec
- type NormalizedPipeline
- type OverriddenJob
- type Provider
- type ReusableCaller
- type SecurityPolicyProjectState
- type SettingsVariable
- type UnexpandedAction
Constants ¶
const ( // UnexpandedBudget: past MaxNestedContainers, the run is degraded (R3). UnexpandedBudget = "budget" // UnexpandedFetch: the definition could not be read, the run warns. UnexpandedFetch = "fetch" // UnexpandedExpression: `uses: ${{ ... }}`, never guessed (spec 3.3). UnexpandedExpression = "expression" // UnexpandedAPIDisabled: no API at all, the run warns and scores (R6). UnexpandedAPIDisabled = "api_disabled" )
The reasons a reference stays unexpanded (spec section 7).
const ( MRApprovalBehaviorKeepApprovals = "keep_approvals" MRApprovalBehaviorRemoveCodeOwnerApprovals = "remove_approvals_by_code_owners" MRApprovalBehaviorRemoveAllApprovals = "remove_all_approvals" )
BehaviorWhenCommitIsAdded values, ordered by strictness: what happens to existing approvals when a new commit lands on an open merge request. ISSUE-503 treats the configured expectation as a minimum on this ladder (keep < remove code-owner approvals < remove all), matching the legacy platform's ordinal comparison.
const ( VisibilityPublic = "public" VisibilityPrivate = "private" VisibilityUnknown = "unknown" )
Repository exposure values carried by NormalizedPipeline.Visibility.
Variables ¶
var PrivilegedTriggers = []string{
"pull_request_target",
"workflow_run",
"issue_comment",
"pull_request_review",
"pull_request_review_comment",
"discussion_comment",
"discussion",
"gollum",
"fork",
}
PrivilegedTriggers are the events whose run holds the base repository's secrets and token while someone outside the team can start it: the triggers that, alone, make a run privileged. It is the one list: policies/situation/situation.rego's privileged_triggers is the same set (TestPrivilegedTriggersMatchTheSituation keeps the two equal).
Functions ¶
func ClassifyGates ¶ added in v0.7.1
func ClassifyGates(p *NormalizedPipeline)
ClassifyGates fills Job.Gate on every GitHub job carrying a privileged trigger: ClassifyGate per trigger it carries (classifiedTriggers, the privileged ones and every one an outsider fires), with its types: filter and the job's deployment environment looked up among p.Environments, keeping the weakest result (weakerGate). A job of a reusable workflow (workflow_call) runs under its callers' events instead, so it also takes the weakest gate among its calls (callGate). A no-op on GitLab, whose jobs carry no trigger.
func IsPRHeadRef ¶ added in v0.7.1
IsPRHeadRef reports whether a checkout ref (wrapped in ${{ }} or not) checks out the code of a pull request or of another workflow's run.
func IsPRHeadShaRef ¶ added in v0.7.1
IsPRHeadShaRef reports whether a checkout ref is the head commit SHA of the pull request or of the workflow run: what runs is the commit the event names, nothing pushed after it.
func MarkInert ¶ added in v0.7.1
func MarkInert(p *NormalizedPipeline)
MarkInert fills Job.Inert on every job of p that cannot run at all: its workflow is disabled, its runner label is retired, it has no trigger, it is a reusable workflow nobody calls, or it is pinned to another repository. An inert job starts no attack path and is walked by none (see every_dead in policies/situation/situation.rego, which also reads this field, and liveSituation in control/paths.go, which reads it the same way it already reads Job.Dead).
A no-op on GitLab: every reason below reads a GitHub-only signal (workflow state, runs-on, workflow_call, the triggers a workflow declares). Gating the whole function on the pipeline's provider, rather than on WorkflowHasTriggers alone, keeps a GitLab job's always- empty Triggers from misreading as "no trigger": a GitLab job simply never carries this field at all, by design (see Job.Triggers).
func NormalizeVisibility ¶ added in v0.5.18
NormalizeVisibility maps a provider's visibility string to the three values the situation facts understand. GitLab's "internal" is private: an outsider cannot read it.
func OverrideFingerprint ¶ added in v0.5.0
func OverrideFingerprint(jobs []OverriddenJob) string
OverrideFingerprint is the canonical digest of an include's override content: the overridden jobs sorted by name, each carrying the WHOLE local job block as the user's own configuration declares it. encoding/json sorts map keys, so the bytes are stable across runs and machines; the digest is the first 16 hex characters of SHA-256, the width every other CLI fingerprint uses. Empty when nothing is overridden.
Canonical form: the jobs sorted by name, each marshalled as {"name": <name>, "content": <the local job block>} with every map's keys sorted, the whole array marshalled as JSON with no indentation, sha256 of those bytes, first 16 hex characters.
The digest covers the block as a whole rather than a projection of the keys the override regex matched: ANY change to the overriding job moves it, nested or not, forbidden keyword or not (a keyword that only ever appears under another key, an unmatched key, a value several levels down). A projection of the matched keys would hash a null for a keyword the job never declares at its top level and let its content change unnoticed. The order in which the collector discovered the jobs does not move the digest.
func SplitDefinedIn ¶ added in v0.7.2
SplitDefinedIn reads a DefinedIn locator back: "owner/repo@ref:path:line" (repo "owner/repo"), or "./path:line" for a composite of the scanned repository (repo ""). ok is false for anything else. A git ref never holds a colon, so the first colon after the @ ends the ref.
func ViaText ¶ added in v0.7.2
ViaText renders a chain the way every output prints it (spec 6): outermost first, " > " separated.
func WorkflowDefinedIn ¶ added in v0.7.2
WorkflowDefinedIn is the locator of a line of a workflow called in another repository, from its qualified origin "owner/repo/.github/workflows/x.yml@ref" (spec 3.2). "" for any other origin, a workflow of the scanned repository included.
func WorkflowStateKey ¶ added in v0.7.1
WorkflowStateKey normalizes originFile to the repo-relative form WorkflowStates is keyed by: the remote scan already sets Job.OriginFile to that form, so it passes through unchanged, but the local scan sets it to the absolute filesystem path (the workflows directory joined with the file name), which never matches the map directly. Slashes are normalized to "/" first, since filepath.Join on the local scan uses the OS separator. originFile is returned unchanged when the marker is not found at all (an unexpected shape, safer than guessing).
Types ¶
type Action ¶
type Action struct {
Uses string `json:"uses"`
With map[string]any `json:"with,omitempty"`
// Name is the step's `name:` from the workflow YAML, when the author
// provided one. It is the only stable discriminator between two steps in
// the same job that reference the same action: the line number tells them
// apart within a scan but moves whenever unrelated code above them is
// edited, so the name is what a run-over-run identifier can rely on.
Name string `json:"name,omitempty"`
// Line is the 1-based line number of the `uses:` directive in the
// source workflow file. Populated by the provider collector so
// action-level findings can point the reviewer at the exact step
// instead of the surrounding job header. Zero when unknown.
Line int `json:"line,omitempty"`
// If is the step-level `if:` expression guarding this action step
// (GitHub Actions), verbatim from the workflow YAML, "" when the
// step is unconditional. Policies that reason per trigger can use
// it to exclude the step from events its condition rules out.
If string `json:"if,omitempty"`
Metadata *ActionMetadata `json:"metadata,omitempty"`
Comment string `json:"comment,omitempty"`
// Nested is true when the step was not written in the scanned workflow but
// pulled in by a composite action or a called workflow (spec 2026-10-16, R1).
Nested bool `json:"nested,omitempty"`
// Via is the chain of references the step was reached through, outermost
// first, as written in the file that pulled each one in:
// ["thomasboni/nested-actions-b@main", "thomasboni/nested-actions-d@main"].
Via []string `json:"via,omitempty"`
// DefinedIn locates the uses: line inside the container that holds it,
// "owner/repo@ref:path/action.yml:9" (a local composite: "./.github/actions/x/action.yml:4").
DefinedIn string `json:"definedIn,omitempty"`
}
Action is a single invocation of a reusable third-party action. Uses is the full ref ("owner/repo@v4" or "owner/repo@<sha>"), With is the raw `with:` map (scope values may be strings, bools or numbers, hence `any`).
Metadata, when present, carries facts resolved against the GitHub API (archived repo, ref kind, tag SHA). Zero-valued Metadata — or a nil pointer — means the collector did not look it up (no token, offline run, non-GitHub action). Policies that key on API evidence should treat absence as "unknown" and stay silent to avoid false positives.
Comment holds the trailing `# comment` that workflow authors commonly add behind a hash-pinned reference to document the human-readable version (e.g. `@abc123… # v4.1.0`). Kept as the raw string after stripping leading whitespace + `#`.
type ActionMetadata ¶
type ActionMetadata struct {
RepoArchived bool `json:"repoArchived,omitempty"`
RefExists bool `json:"refExists,omitempty"`
// RefKnownAbsent is true ONLY when the upstream API definitively
// answered that the ref does not exist (a 404 / 422 on a readable
// repo). It stays false when the ref could not be verified (private
// repo, rate limit, network error) so impostor-commit (ISSUE-707)
// never flags a valid SHA it merely failed to reach.
RefKnownAbsent bool `json:"refKnownAbsent,omitempty"`
RefKind string `json:"refKind,omitempty"`
// RefCommitSha is set only when the pinned ref is the SHA of an
// annotated tag OBJECT: it carries the commit that tag object
// dereferences to. Empty for every other ref shape, including a
// plain commit pin (there the ref already IS the commit).
RefCommitSha string `json:"refCommitSha,omitempty"`
TagSha string `json:"tagSha,omitempty"`
LatestTag string `json:"latestTag,omitempty"`
LatestReleaseSha string `json:"latestReleaseSha,omitempty"`
CommentVersion string `json:"commentVersion,omitempty"`
CommentTagSha string `json:"commentTagSha,omitempty"`
RefIsAmbiguous bool `json:"refIsAmbiguous,omitempty"`
Advisories []string `json:"advisories,omitempty"`
// AdvisorySeverities maps a GHSA id of Advisories to the severity the
// GitHub Advisory Database rates it with, lower-cased (low, medium,
// high, critical); an id it did not rate is absent. The path assembly
// caps an ISSUE-703 path by the worst of them.
AdvisorySeverities map[string]string `json:"advisorySeverities,omitempty"`
// StargazersCount is the action repository's star count, resolved
// from the GitHub API at collect time. Consumed by the authorized-
// sources control (ISSUE-713) to enforce a minimum-stars trust
// threshold. Zero when enrichment did not run or the repo was
// unreachable — policies must abstain rather than flag on absence.
StargazersCount int `json:"stargazersCount,omitempty"`
// MutableRemoteExec, when set, records that the action's own source
// fetches code or a download-steering manifest from a MOVING ref
// (main/master/HEAD/…) at runtime — so pinning this action by SHA
// does not make its execution immutable. Nil when the source was not
// analysed (offline, non-github.com host, trusted owner) or nothing
// mutable was found. Consumed by the mutable-remote-exec control.
MutableRemoteExec *MutableRemoteExec `json:"mutableRemoteExec,omitempty"`
// RedirectedOwner is the owner the action's repository currently
// reports as its full_name (the metadata lane's own `GET
// repos/{owner}/{repo}` call), when it differs from the `uses`
// owner, case insensitive. GitHub keeps the old owner/repo path
// resolving after a rename or an organisation transfer, so this is
// who can actually change the code, not who the workflow still
// names. Empty when the names agree or the repository could not be
// read.
RedirectedOwner string `json:"redirectedOwner,omitempty"`
}
ActionMetadata mirrors the collector-side GitHubMetadata shape. Kept in internal/ir so policies can consume it via the serialised JSON input without internal/ir importing the collector package.
type AnalyzedWorkflow ¶ added in v0.4.52
AnalyzedWorkflow is one GitHub workflow file a scan read, retained for the JSON report so a consumer knows which files produced the findings (#443).
type ArtifactRef ¶ added in v0.5.18
type ArtifactRef struct {
Name string `json:"name,omitempty"`
Pattern string `json:"pattern,omitempty"`
Paths []string `json:"paths,omitempty"`
Mode string `json:"mode"` // "produce" or "consume"
// CrossRun is true on a consume entry that reads another workflow
// run's artifacts (GitHub download-artifact with a run-id input), the
// one way an artifact crosses workflow files.
CrossRun bool `json:"crossRun,omitempty"`
}
ArtifactRef is one artifact a job produces or consumes. An empty Name and an empty Pattern on a consume entry mean "every artifact of the workflow". Pattern is a consume entry's glob over artifact names (GitHub download-artifact's `pattern:` input).
type Branch ¶
type Branch struct {
Name string `json:"name"`
Protected bool `json:"protected"`
ProtectionPattern string `json:"protectionPattern,omitempty"`
AllowForcePush bool `json:"allowForcePush,omitempty"`
CodeOwnerApprovalRequired bool `json:"codeOwnerApprovalRequired,omitempty"`
MinPushAccessLevel int `json:"minPushAccessLevel,omitempty"`
MinMergeAccessLevel int `json:"minMergeAccessLevel,omitempty"`
ProtectionDetailsKnown bool `json:"protectionDetailsKnown,omitempty"`
}
Branch is a branch of the source repository as seen by the collector. Fields beyond Name/Protected are populated from the provider's branch protection API (GitLab /api/v4/projects/:id/protected_branches, the GitHub equivalent, …). They stay zero-valued when the collector did not fetch protection settings.
ProtectionDetailsKnown distinguishes "we have authoritative data and the rule values just happen to be zero" from "we don't have the data at all" (e.g. GitHub's /branches/{name}/protection endpoint is admin- only and returned 403 on a content-only token). Without this flag, the rego rule that checks codeOwnerApprovalRequired would false- positive every branch on a read-only GitHub token, because the field would default to false even though we never observed the truth. Rules that depend on the detail fields must guard on this.
type CacheModeDecl ¶ added in v0.7.0
type CacheModeDecl struct {
Value string `json:"value"`
Effective string `json:"effective,omitempty"`
Line int `json:"line"`
Level string `json:"level"`
}
CacheModeDecl is a GitHub `cache-mode:` declaration. Value is the text as written. Effective is the access it grants (read, write, write-only or none), the same on every run: GitHub accepts an expression there only when it reads no context. Effective is empty when the value grants nothing GitHub would run (an unknown word, or an expression reading a context, which makes the workflow file invalid). Level says whether the job declares it or inherits its workflow's; Line is the declaration's.
type CacheRef ¶ added in v0.5.18
type CacheRef struct {
// Key is what a restore and a save must share for one to read the
// other: the actions/cache key as written, or the key a caching action
// builds (its discriminators included: a dependency path, a buildx
// scope, a job id). Family is the name a reader recognizes, the key
// without the part that tells two caches of one kind apart; empty when
// it is the key itself.
Key string `json:"key,omitempty"`
Family string `json:"family,omitempty"`
Paths []string `json:"paths,omitempty"`
Mode string `json:"mode"` // "restore", "save" or "both"
Prefix bool `json:"prefix,omitempty"`
Uses string `json:"uses,omitempty"`
Line int `json:"line,omitempty"`
// Parts is the discriminators of the real key a caching action builds
// itself, resolved to a concrete value, by name: "os", "arch"
// (read off the job's own runs-on, every caching action), "version"
// and "manager" (setup-node, setup-python, setup-go; the input that
// gives it, when it is a literal, never a `.nvmrc`/a `*-version-file`
// or an expression), "job" and "key" (Swatinem/rust-cache, the job id
// and the key/shared-key input when literal). A discriminator this
// program cannot resolve is absent here, never guessed; the situation
// layer's key-template match (control/cachekeys.go) then leaves it an
// open placeholder.
Parts map[string]string `json:"parts,omitempty"`
}
CacheRef is one cache a job restores, saves or both. A Prefix entry is a key family: restored, it takes the most recent cache whose key starts with Key, not only the one equal to it (GitHub actions/cache restore-keys, or a caching action that keys on a fixed prefix followed by a hash only known at run time); saved, its key is Key followed by such a part. Uses and Line are the step that declares it (GitHub), so a finding on that step can find its caches.
type DependabotConfig ¶
type DependabotConfig struct {
Path string `json:"path"`
InsecureExecEcosystems []string `json:"insecureExecEcosystems,omitempty"`
// MissingCooldownEcosystems lists ecosystems in the config that
// have no `cooldown:` block. A cooldown window gives security
// advisory pipelines time to flag a bad release before
// Dependabot's PR automation picks it up.
MissingCooldownEcosystems []string `json:"missingCooldownEcosystems,omitempty"`
}
DependabotConfig captures the bits of .github/dependabot.yml that feed the dependabot-* policies. Populated by the GitHub collector when the file exists; nil otherwise.
type Dockerfile ¶
type Dockerfile struct {
Path string `json:"path"`
Bases []DockerfileBase `json:"bases,omitempty"`
}
Dockerfile captures the result of parsing a single Dockerfile's FROM directives for supply-chain auditing.
type DockerfileBase ¶
type DockerfileBase struct {
Image string `json:"image"`
Line int `json:"line"`
PinnedByDigest bool `json:"pinnedByDigest,omitempty"`
}
DockerfileBase is one FROM line. PinnedByDigest is true when the base image is referenced via `image@sha256:...` (immutable); otherwise the tag (or default-tag) form lets the registry serve a different layer for the same name.
type Environment ¶ added in v0.7.1
type Environment struct {
Name string `json:"name"`
Known bool `json:"known,omitempty"`
RequiredReviewers bool `json:"requiredReviewers,omitempty"`
Reviewers int `json:"reviewers,omitempty"`
PreventSelfReview bool `json:"preventSelfReview,omitempty"`
WaitTimer int `json:"waitTimer,omitempty"`
BranchPolicy bool `json:"branchPolicy,omitempty"`
}
Environment is a GitHub Actions deployment environment's protection rules, as the provider collector reads them from the repository (not from the workflow file itself, which only names the environment). Known is false when the collector could not read the rule set (no access, environment not found, provider error), in which case the other fields carry no information and a reader must not treat their zero values as "no protection rule".
type Function ¶ added in v0.6.1
type Function struct {
Name string `json:"name,omitempty"`
Ref string `json:"ref"`
Kind string `json:"kind"`
// Deprecated is true when the step used the legacy `step:` key
// (renamed to `func:`) or Kind is "git" (deprecated git-repository
// loading, superseded by OCI registry refs).
Deprecated bool `json:"deprecated,omitempty"`
}
Function references a GitLab CI/CD Function (docs.gitlab.com/ci/functions) — a job's `run:` step's `func:` (or deprecated `step:`) reference. Kind classifies the reference form: "oci" (registry/path:tag or @sha256: digest — the supported form), "local" (relative/absolute filesystem path — same-repo, no supply-chain concern), "builtin" (builtin://..., shipped inside the runner — no supply-chain concern either), "unknown" (a structured value the collector could not read), or "git" (the deprecated git-repository loading form, host/path@ref with no OCI tag).
type Gate ¶ added in v0.7.1
type Gate struct {
Class GateClass `json:"class"`
Reason string `json:"reason,omitempty"`
PinnedSha bool `json:"pinnedSha,omitempty"`
// Unknown lists the references ClassifyGate could not resolve when
// Class is GateUnknown.
Unknown []string `json:"unknown,omitempty"`
}
Gate is the result of classifying who can trigger a job, given the facts an outsider alone controls.
func ClassifyGate ¶ added in v0.7.1
func ClassifyGate(job Job, byName map[string]Job, trigger string, types []string, env Environment, known bool, base string) Gate
ClassifyGate decides who can make job run: an outsider on their own, a maintainer who approved it (a label, the author's identity, a merge, or a protected environment), only code already in the repository, or unknown.
trigger is the job's GitHub Actions event name and types is that event's `types:` filter (empty means every type fires). env and known carry the job's deployment environment protection rules, known being whether the provider could actually read them; when known is false the environment contributes nothing to the classification, it is neither evidence of a gate nor of its absence. base is the branch a pull request targets in the model, the repository's default branch ("" reads main): what github.ref, github.ref_name and github.base_ref name on a pull_request_target run.
type GateClass ¶ added in v0.7.1
type GateClass string
GateClass says who can make a privileged job run.
const ( // GateOutsider means an outsider, acting alone, can make the job run // with its event action of choice: the condition is true for at // least one type an outsider can produce. GateOutsider GateClass = "outsider" // GateMaintainerApproved means the job only runs after some // maintainer action: a label, the author's identity, a merge, or a // protected environment's required reviewers. GateMaintainerApproved GateClass = "maintainer_approved" // GateSameRepo means the job only runs for code already in the // repository itself (no fork head), never for an outsider's pull // request. GateSameRepo GateClass = "same_repo" // GateNoOutsider means the privileged run never executes the job at // all: its condition names other events (github.event_name, or an // event action the trigger never produces), so it only runs on the // repository's own events, never on one an outsider starts. GateNoOutsider GateClass = "no_outsider" // GateUnknown means the facts available do not determine whether an // outsider can make the job run. GateUnknown GateClass = "unknown" // GateInsider means the party who can change what runs is a first // party (the scanned repository's own owner or domain) or a sibling // organisation's redirected repository: not an outsider, and not // gated behind a maintainer action either, but close enough to home // that a path through it holds at the Medium ceiling rather than // Critical (control/trust.go's actorFor). GateInsider GateClass = "insider" )
Gate classes.
type Image ¶
type Image struct {
Name string `json:"name"`
Tag string `json:"tag,omitempty"`
Digest string `json:"digest,omitempty"`
Registry string `json:"registry,omitempty"`
CredentialsPassword string `json:"credentialsPassword,omitempty"`
// Unresolved reports that the reference still held a `$VARIABLE` when it
// was parsed, so Registry, Name and Tag describe a placeholder rather
// than an image. A rule that judges a registry or a tag must skip such an
// image: those fields are present and WRONG, not absent, so a rule that
// merely checks for emptiness would answer confidently over a guess.
//
// The zero value is "resolved", so every hand-built fixture and every
// provider that does not set it behaves exactly as before.
Unresolved bool `json:"unresolved,omitempty"`
}
Image references a container image (job image, service, step base). CredentialsPassword carries the literal value of the image's `credentials.password` field (GitHub Actions `jobs.*.container` and `services.*`). It is the raw YAML string — `${{ secrets.X }}` expressions come through as the template itself, so policies can distinguish a secret reference from a hard-coded literal.
type Include ¶
type Include struct {
Kind string `json:"kind"`
Source string `json:"source"`
Ref string `json:"ref,omitempty"`
Current string `json:"current,omitempty"`
Path string `json:"path,omitempty"`
AltPath string `json:"altPath,omitempty"`
// TemplatePath is the template's own identity, read from its pinned
// ref rather than from where its file happens to live. A template
// repository can tag a file under a name that has nothing to do with
// its file path (a template "gitleaks" served from
// jobs/gitleaks/gitleaks.yml, say), in which case neither Path nor
// AltPath ever equals what a policy requires - only this field does.
// Empty when the collector could not establish it.
TemplatePath string `json:"templatePath,omitempty"`
Nested bool `json:"nested,omitempty"`
ComponentName string `json:"componentName,omitempty"`
// RefIsAmbiguous is set by the collector when the include's
// symbolic ref resolves upstream as BOTH a tag and a branch
// (ref-confusion, ISSUE-402). Zero-valued when the ref is a SHA,
// `~latest`, or when the API probe could not confirm both — the
// rule fires only on a positive double-hit, so a degraded probe
// never invents a finding.
RefIsAmbiguous bool `json:"refIsAmbiguous,omitempty"`
// OriginFile + OriginLine point at the line in the user's
// .gitlab-ci.yml that introduced this include. They let rules
// emit findings with a clickable source pointer instead of a
// repo-level "somewhere in the config" reference. Empty for
// nested includes pulled in transitively by an upstream
// component.
OriginFile string `json:"originFile,omitempty"`
OriginLine int `json:"originLine,omitempty"`
// OriginHash is a stable identifier for the origin of this
// include, used by external tooling to deduplicate the same
// upstream source across pipelines.
OriginHash uint64 `json:"originHash,omitempty"`
OverriddenJobs []OverriddenJob `json:"overriddenJobs,omitempty"`
// OverrideFingerprint is the canonical digest of the override
// content above (see OverrideFingerprint). It lets a consumer tell
// "the same override as last time" from "the override changed"
// without carrying the content itself. Empty when nothing is
// overridden.
OverrideFingerprint string `json:"overrideFingerprint,omitempty"`
}
Include models an external pipeline fragment pulled into the current one. Path is a normalized form of Source suitable for comparison against a user-declared required component/template list (version suffix and instance URL prefix stripped). AltPath optionally carries a second candidate path — some collectors (Plumber-augmented templates) know a template under two equivalent names and both should match. OverriddenJobs enumerates the jobs inherited from this include whose behaviour was overridden locally with one of the CI/CD keys that meaningfully change semantics (script, image, rules, …).
type Job ¶
type Job struct {
Name string `json:"name"`
Image *Image `json:"image,omitempty"`
// MatrixCombinations is the combinations the job's `strategy.matrix` runs,
// each as its keys' literal values, kept by the collector to resolve
// a reusable workflow call's inputs built from them; never part of
// the evaluation input.
MatrixCombinations []map[string]string `json:"-"`
// ImageRef is the container reference as written, kept by the
// collector to resolve a reference built from a reusable workflow's
// inputs against each caller; never part of the evaluation input.
ImageRef string `json:"-"`
Services []Image `json:"services,omitempty"`
// MatrixImages are the images Image resolves to when its reference is
// built from `${{ matrix.X }}` values the job's matrix lists as
// literals (GitHub `strategy.matrix`): one per value, sorted. The
// image policies judge these instead of the reference as written.
// Empty when the reference holds no matrix value, holds another
// expression, or the matrix is computed at run time.
MatrixImages []Image `json:"matrixImages,omitempty"`
// Tags are the job's runner tags (GitLab's `tags:` keyword), read off
// the merged configuration so a tag inherited from `default:` counts
// exactly as a job-level one does. Sorted and de-duplicated, so the
// order never depends on how the pipeline was authored. Empty for a job
// that names no tag, which is the honest answer: the job runs on
// whichever runner the instance picks, and no tag is claimed for it.
Tags []string `json:"tags,omitempty"`
Scripts []string `json:"scripts,omitempty"`
// ScriptBlocks names the source block ("before_script", "script",
// "after_script") for each entry of Scripts, in the same order.
// Lets script-scanning policies surface where the offending line
// lives so legacy consumers (the Rego-port issue payload) can
// echo the v0.2.x scriptBlock attribute. Empty when the collector
// did not track origins (older fixtures).
ScriptBlocks []string `json:"scriptBlocks,omitempty"`
// ScriptIfs carries each Scripts entry's step-level `if:` expression
// (GitHub Actions), in the same order as Scripts, "" for a step that
// declared none. Set only when at least one run step in the job is
// conditional, so the field stays absent in the common unconditional
// case. Read by the situation facts alongside the job's texts.
ScriptIfs []string `json:"scriptIfs,omitempty"`
AllowFailure bool `json:"allowFailure,omitempty"`
When string `json:"when,omitempty"`
Variables map[string]string `json:"variables,omitempty"`
// LocalVariables is the raw `variables:` map authored by the
// project itself (root .gitlab-ci.yml or workflow file), before any
// merge with upstream component/template definitions. Empty when
// the user did not declare a `variables:` block on this job —
// distinguishing "user wrote SAST_DISABLED here" from "upstream
// template ships SAST_DISABLED". Variable-override policies must
// read this field, never the merged Variables, to avoid punishing
// projects for variables their upstream catalogs already set.
LocalVariables map[string]string `json:"localVariables,omitempty"`
// Rules captures the job's `rules:` block (GitLab CI). Each entry
// is a {if, when, allow_failure, exists, changes, …} map; rules
// such as `- when: never` neutralise the job at runtime even when
// the job is otherwise correctly configured. Policies that care
// about effective execution (security-job weakening) read this
// list and reject any rule whose terminal `when` would prevent
// the job from running.
Rules []map[string]any `json:"rules,omitempty"`
// Only and Except capture GitLab's legacy `only:`/`except:` keywords,
// string refs only (branches, tags, merge_requests, schedules, …).
// The map form (`only: {refs: [...]}`) flattens to its `refs` list;
// the `variables`/`changes` conditions of that map form are dropped,
// since they cannot be decided statically. A job with a `rules:`
// block never has these: GitLab treats `rules` and `only`/`except`
// as mutually exclusive.
Only []string `json:"only,omitempty"`
Except []string `json:"except,omitempty"`
OriginFile string `json:"originFile,omitempty"`
OriginLine int `json:"originLine,omitempty"`
OriginKind string `json:"originKind,omitempty"`
// Functions lists this job's `run:`-step function references
// (docs.gitlab.com/ci/functions, GitLab CI only). Kind classifies
// the reference form so policies can skip same-repo file
// references (no supply-chain concern) and flag deprecated forms
// independently of trust.
Functions []Function `json:"functions,omitempty"`
// Overridden is true when the job inherits from an upstream
// component or template but the project locally redefined some of
// its keys. Lets policies distinguish "user-authored override" from
// "vanilla upstream definition" — the rules-redefinition guard in
// security_jobs_weakened depends on this signal.
Overridden bool `json:"overridden,omitempty"`
// OverriddenKeys lists the specific job-level keys the project
// redefined when overriding an upstream definition (`rules`,
// `image`, `when`, …). Empty when the job was not overridden,
// which lets policies target a particular kind of override
// (rules redefinition, image substitution, …) without flagging
// every override globally.
OverriddenKeys []string `json:"overriddenKeys,omitempty"`
// Permissions are the job's effective permissions as declared in the
// source provider. For GitHub Actions this is the job-level block if
// present, otherwise the inherited workflow-level block. The value may
// be a string shortcut ("write-all", "read-all") or a map of scope
// names to access level — policies are expected to handle both.
Permissions any `json:"permissions,omitempty"`
// Triggers are the event names under which the enclosing workflow runs
// (for GitHub Actions, the `on:` section of the workflow file). The
// collector propagates them to every job of the workflow. GitLab jobs
// leave this field empty: the concept maps poorly onto GitLab's
// `workflow.rules` and `only/except` semantics, and the few rules that
// care about triggers are GitHub-specific.
Triggers []string `json:"triggers,omitempty"`
// WorkflowHasTriggers is true when the enclosing workflow declares an
// `on:` key naming at least one event, false when `on:` is absent or
// names none. Set only by the GitHub collector; GitLab jobs leave it
// at its zero value (false), which is why MarkInert gates its "no
// trigger" reason on the pipeline's provider rather than on this
// field alone: a GitLab job with an empty Triggers is the norm, not
// a job that cannot run.
WorkflowHasTriggers bool `json:"workflowHasTriggers,omitempty"`
// TriggerTypes is each event's `on.<event>.types` filter, keyed by
// event name, as written (GitHub Actions). An event with no types:
// filter (every type fires), and every event of the string and list
// forms of on:, has no entry; nil when no event carries one.
// ClassifyGates reads it to decide which event actions an outsider
// can produce.
TriggerTypes map[string][]string `json:"triggerTypes,omitempty"`
// RunsOn is the job's `runs-on:` literal labels (GitHub Actions): a
// single string or a list, normalized to one entry per label as
// written. Empty when the value is a runtime expression or a
// `group:`/`labels:` object form (see RunsOnExpression) this
// collector does not model.
RunsOn []string `json:"runsOn,omitempty"`
// RunsOnExpression is true when `runs-on:` holds a `${{ ... }}`
// expression (in the value itself or any list element) or the
// `group:`/`labels:` object form. MarkInert cannot decide a retired
// runner label from either, so it sets nothing for the runner on
// such a job.
RunsOnExpression bool `json:"runsOnExpression,omitempty"`
// PushBranches, PushBranchesIgnore, PushTags and PushTagsIgnore carry
// GitHub's `on.push.branches` / `branches-ignore` / `tags` /
// `tags-ignore` filters (PR #513 review): each entry is a glob pattern
// exactly as the workflow author wrote it. All four are empty when
// `push:` carries no filter at all (the bare "push" string or
// list-element form, or a `push:` entry with none of these four keys),
// which situation.rego's unprotected_push reads as "every branch",
// never as "no push trigger at all". PushTagsIgnore on its own (no
// branches, no branches-ignore) reads the same as PushTags alone: a
// tag filter with no branch filter at all means the workflow never
// runs on a branch push. GitLab carries no equivalent: its push
// surface is read off its rules:/only:/except: keywords instead.
PushBranches []string `json:"pushBranches,omitempty"`
PushBranchesIgnore []string `json:"pushBranchesIgnore,omitempty"`
PushTags []string `json:"pushTags,omitempty"`
PushTagsIgnore []string `json:"pushTagsIgnore,omitempty"`
// WorkflowRunWorkflows is the `on.workflow_run.workflows` list of the
// enclosing GitHub workflow, verbatim: the upstream workflows (by
// name, or by file path for a workflow with no name) whose runs
// trigger this one. Empty when the workflow has no workflow_run
// trigger or names no upstream. GitLab carries no equivalent.
WorkflowRunWorkflows []string `json:"workflowRunWorkflows,omitempty"`
// Uses lists every third-party action referenced by the job's steps
// (for GitHub Actions, `jobs.<name>.steps[].uses` with its
// accompanying `with:` block). Empty for GitLab jobs, which model
// external code through `include:` instead (already covered by the
// pipeline Includes list).
Uses []Action `json:"uses,omitempty"`
// ReusableWorkflowUses is the `uses:` declared at the job level (not
// the step level). Populated only for GitHub Actions jobs that are
// reusable-workflow calls: `jobs.<name>.uses:
// owner/repo/.github/workflows/x.yml@ref`. Empty for every other
// job type.
ReusableWorkflowUses string `json:"reusableWorkflowUses,omitempty"`
// SecretsInherit is true when a reusable-workflow call forwards
// every caller-visible secret to the callee via `secrets: inherit`.
// Only meaningful when ReusableWorkflowUses is set.
SecretsInherit bool `json:"secretsInherit,omitempty"`
// ReusableSecrets is a reusable-workflow call's explicit `secrets:`
// map: the called workflow's secret name to the expression the caller
// passes (`R2_KEY: ${{ secrets.R2_KEY }}`). ReusableWith is the call's
// `with:` inputs, as written. Both empty on any other job.
ReusableSecrets map[string]string `json:"reusableSecrets,omitempty"`
ReusableWith map[string]any `json:"reusableWith,omitempty"`
// ReusableCallees are, on a job calling a reusable workflow of the
// repository itself, the jobs of that workflow and of every workflow it
// calls in turn, sorted: the jobs the call runs.
ReusableCallees []string `json:"reusableCallees,omitempty"`
// Callers are, on a job of a reusable workflow of the repository
// (on: workflow_call), every call that runs it: the calling job, with
// the token, the events, the secrets and the inputs that call gives
// it. A called workflow runs under its caller's event with its caller's
// token, so these, not the job's own workflow, say what it holds. A call
// from a workflow itself called carries what the top of the chain
// gives, one entry per chain. Sorted by calling job.
Callers []ReusableCaller `json:"callers,omitempty"`
// Nested is true on a job appended from a reusable workflow of another
// repository, Via the chain of calls that runs it (spec 3.2).
Nested bool `json:"nested,omitempty"`
Via []string `json:"via,omitempty"`
// If is the JOB-level `if:` expression (GitHub Actions), verbatim,
// "" when the job is unconditional. Distinct from Conditions, which
// flattens the job's and its steps' conditions together for
// pattern-matching policies; per-trigger resolution (the situation's
// gated_triggers) needs the job gate alone, because it excludes every
// step from the events it rules out.
If string `json:"if,omitempty"`
// Dead is true when the job-level `if:` is the constant false (`if:
// false`, `if: ${{ false }}`, `if: 'false'`): the job never runs, so
// it holds nothing, changes nothing and hands nothing over.
Dead bool `json:"dead,omitempty"`
// Inert is why this job can never run, filled by MarkInert: its
// workflow is disabled, its runner label is retired, it has no
// trigger, it is a reusable workflow nobody calls, or it is pinned
// to another repository (see the MarkInert doc comment for the full
// precedence). Empty for a job that can run. GitHub only; MarkInert
// is a no-op on GitLab.
Inert string `json:"inert,omitempty"`
// Gate is who can make this job run on its privileged triggers, filled
// by ClassifyGates: the weakest gate across the privileged triggers
// the job carries (an outsider on one of them makes it an outsider's).
// Nil for a job with no privileged trigger, and on GitLab.
Gate *Gate `json:"gate,omitempty"`
// Conditions collects every `if:` expression attached to the job
// (job-level + each step's). Kept as raw YAML strings so Rego
// policies can match them with regular expressions — no attempt
// at GitHub-expression-language parsing at the collector level.
Conditions []string `json:"conditions,omitempty"`
// WorkflowName is the value of the top-level `name:` field of the
// enclosing workflow. Empty when the workflow has no explicit
// name (GitHub falls back to the file path in the UI). Propagated
// to every job of the workflow by the collector.
WorkflowName string `json:"workflowName,omitempty"`
// WorkflowHasConcurrency is true when the enclosing workflow
// declares a top-level `concurrency:` block. Job-level
// concurrency is tracked separately via JobHasConcurrency.
WorkflowHasConcurrency bool `json:"workflowHasConcurrency,omitempty"`
// JobHasConcurrency is true when the job has its own
// `concurrency:` block, independent of the workflow-level one.
JobHasConcurrency bool `json:"jobHasConcurrency,omitempty"`
// CacheMode is the GitHub `cache-mode:` in effect for the job: its
// own, else its workflow's. Nil when neither declares one, which
// leaves GitHub's trigger-based default (write on trusted triggers,
// read on low-trust ones).
CacheMode *CacheModeDecl `json:"cacheMode,omitempty"`
// Environment is the `environment:` field declared on the job
// (GitHub Actions deployment environment gate). Accepts both the
// `environment: production` shorthand and the long form — the
// collector keeps only the name. Empty when no environment is set.
Environment string `json:"environment,omitempty"`
// Needs lists the Job.Name values this job depends on (GitHub: qualified
// with the workflow namespace exactly like Name; GitLab: bare job names,
// as GitLab names them). The path assembler walks it one hop. A GitLab
// `needs: []` and an absent `needs:` both decode to nil.
Needs []string `json:"needs,omitempty"`
// Caches and Artifacts are the structured cache and artifact uses of the
// job: on GitHub derived from actions/cache* and actions/*-artifact steps,
// on GitLab from the job's cache:, artifacts: and dependencies: keys.
Caches []CacheRef `json:"caches,omitempty"`
Artifacts []ArtifactRef `json:"artifacts,omitempty"`
}
Job is a single pipeline unit of work.
type MRApprovalRule ¶ added in v0.4.42
type MRApprovalRule struct {
// ID is GitLab's approval-rule ID, stringified. It is stable for the
// rule's lifetime (it churns only on delete-and-recreate, which is a
// new rule), so ISSUE-502 keys its finding identity on ID rather than
// on the renameable Name, per the #370 volatile-field discipline.
ID string `json:"id"`
// Name is the human label. It renders in findings but is deliberately
// NOT an identity field: renaming a rule must not re-key its finding.
Name string `json:"name,omitempty"`
// ApprovalsRequired is how many approvals the rule mandates.
ApprovalsRequired int `json:"approvalsRequired"`
// AppliesToAllProtectedBranches is GitLab's explicit "all protected
// branches" flag. ISSUE-504 requires at least one rule to carry it.
AppliesToAllProtectedBranches bool `json:"appliesToAllProtectedBranches,omitempty"`
// ProtectedBranchCount is how many specific protected branches the rule
// is scoped to. Zero means it is scoped to none in particular, which
// GitLab treats as covering all branches; ISSUE-502 checks a rule that
// covers all protected branches (the explicit flag OR a zero count).
ProtectedBranchCount int `json:"protectedBranchCount"`
}
MRApprovalRule is one merge-request approval rule (GitLab: Settings > Merge requests > Approval rules). It carries the rule's stable identity and the fields the approval-rule controls check: how many approvals it requires and whether it covers all protected branches.
type MRApprovalSettings ¶ added in v0.4.43
type MRApprovalSettings struct {
// PreventApprovalByAuthor is true when MR authors cannot approve their
// own merge requests (GitLab: !merge_requests_author_approval).
PreventApprovalByAuthor bool `json:"preventApprovalByAuthor"`
// PreventApprovalsByCommitters is true when users who committed to an MR
// cannot approve it (GitLab: merge_requests_disable_committers_approval).
PreventApprovalsByCommitters bool `json:"preventApprovalsByCommitters"`
// PreventEditingApprovalRulesInMR is true when approval rules cannot be
// overridden per merge request
// (GitLab: disable_overriding_approvers_per_merge_request).
PreventEditingApprovalRulesInMR bool `json:"preventEditingApprovalRulesInMR"`
// RequireReAuthToApprove is true when approving requires
// re-authentication (GitLab: require_password_to_approve).
RequireReAuthToApprove bool `json:"requireReAuthToApprove"`
// BehaviorWhenCommitIsAdded is one of the MRApprovalBehavior* values,
// derived from GitLab's reset_approvals_on_push and
// selective_code_owner_removals flags.
BehaviorWhenCommitIsAdded string `json:"behaviorWhenCommitIsAdded"`
}
MRApprovalSettings are the project-level merge-request approval settings (GitLab: Settings > Merge requests), normalized to positive-security form: every boolean is true when the safer choice is in effect, regardless of the polarity GitLab's API uses (merge_requests_author_approval, for example, is inverted at projection). Read by the mergeRequestApprovalSettingsMustBeCompliant control (ISSUE-503).
type MRSettings ¶ added in v0.4.43
type MRSettings struct {
// MergeMethod is the project's merge method: "merge", "ff", or
// "rebase_merge" (GitLab: merge_method).
MergeMethod string `json:"mergeMethod"`
// SquashOption is the project's squash policy: "never", "always",
// "default_on", or "default_off" (GitLab: squash_option).
SquashOption string `json:"squashOption"`
// MergePipelinesEnabled is true when merged results pipelines are enabled
// (GitLab: merge_pipelines_enabled; Premium/Ultimate).
MergePipelinesEnabled bool `json:"mergePipelinesEnabled"`
// MergeTrainsEnabled is true when merge trains are enabled
// (GitLab: merge_trains_enabled; Premium/Ultimate).
MergeTrainsEnabled bool `json:"mergeTrainsEnabled"`
// AllowMergeOnSkippedPipeline is true when an MR can be merged while its
// pipeline is skipped (GitLab: allow_merge_on_skipped_pipeline).
AllowMergeOnSkippedPipeline bool `json:"allowMergeOnSkippedPipeline"`
// ResolveOutdatedDiffDiscussions is true when outdated diff discussions are
// auto-resolved on push (GitLab: resolve_outdated_diff_discussions).
ResolveOutdatedDiffDiscussions bool `json:"resolveOutdatedDiffDiscussions"`
// PrintingMergeRequestLinkEnabled is true when the MR-creation link is
// printed on push (GitLab: printing_merge_request_link_enabled).
PrintingMergeRequestLinkEnabled bool `json:"printingMergeRequestLinkEnabled"`
// RemoveSourceBranchAfterMerge is true when the source branch is deleted by
// default after merge (GitLab: remove_source_branch_after_merge).
RemoveSourceBranchAfterMerge bool `json:"removeSourceBranchAfterMerge"`
}
MRSettings are the project-level merge-request/merge settings, carried in raw form (not positive-security) so the mergeRequestSettingsMustBeCompliant control (ISSUE-506) can compare each field against the operator's configured expectation for exact equality. Field semantics mirror GitLab's project payload (Settings > Merge requests).
type MutableRemoteExec ¶ added in v0.4.11
type MutableRemoteExec struct {
Tier string `json:"tier"`
URL string `json:"url"`
Ref string `json:"ref,omitempty"`
File string `json:"file"`
// DownloadGuard is the tool name when the fetch sits under a
// preinstalled-tool guard (`if ! command -v X`, `command -v X ||`,
// `which X ||`, `[ -x <path> ] ||`, `test -x ... ||`, `type X
// >/dev/null ||`): the download never runs on a runner where X is
// already on PATH. Empty when no such guard wraps the fetch.
DownloadGuard string `json:"downloadGuard,omitempty"`
// DownloadVerified is true when a checksum or signature check
// (sha256sum -c/--check, shasum -a 256/512 -c, sha512sum -c, gpg
// --verify, cosign verify-blob, or a literal 64-hex digest compared
// with the downloaded file) follows the fetch in the same step or
// the next: the content is pinned even though the ref is not.
DownloadVerified bool `json:"downloadVerified,omitempty"`
}
MutableRemoteExec describes a mutable-remote-code signal found inside a third-party action's own source. Tier separates the two risk levels the control reports:
- "exec" — a script (.sh/.py/…) fetched from a moving ref and executed, or a `curl … | sh` pipe. Direct remote code execution.
- "data" — a data manifest (.json/.yaml) pulled from a moving ref that steers a later binary download. Weaker: not executed itself.
type NormalizedPipeline ¶
type NormalizedPipeline struct {
Provider Provider `json:"provider"`
ProjectPath string `json:"projectPath,omitempty"`
DefaultBranch string `json:"defaultBranch,omitempty"`
Jobs []Job `json:"jobs,omitempty"`
Includes []Include `json:"includes,omitempty"`
Branches []Branch `json:"branches,omitempty"`
Dependabot *DependabotConfig `json:"dependabot,omitempty"`
// WorkflowStates maps a GitHub Actions workflow file's path, exactly
// as the workflows listing endpoint returns it (e.g.
// ".github/workflows/ci.yml", matching Job.OriginFile), to its state
// ("active", "disabled_manually", "disabled_inactivity",
// "disabled_fork", "deleted", ...). GitHub only.
WorkflowStates map[string]string `json:"workflowStates,omitempty"`
// WorkflowStatesKnown is whether the workflows listing could actually
// be read (false on a 401/403/404/network failure): WorkflowStates
// and ActionsDisabled carry no information when this is false.
WorkflowStatesKnown bool `json:"workflowStatesKnown,omitempty"`
// ActionsDisabled is true when the repository's GitHub Actions are
// turned off entirely. Derived, never fetched as its own signal: true
// only when the listing is known, names at least one workflow, and
// every one of them is disabled_manually or disabled_inactivity. A
// known but empty listing is never read as disabled: the workflows
// endpoint only lists workflows registered on the default branch, so
// a workflow added on a feature branch would otherwise read as
// Actions being off. For the same reason MarkInert applies it only
// to a job whose workflow the listing names: one absent from it
// keeps running.
ActionsDisabled bool `json:"actionsDisabled,omitempty"`
// DefaultTokenWrite is whether the repository's actual default
// GITHUB_TOKEN (the token a job with no permissions block at all,
// its own or its workflow's, runs with) can write: "true" or
// "false" once FetchGitHubDefaultTokenWrite read it from a run's
// own "GITHUB_TOKEN Permissions" log block, "true" when a
// github-actions[bot] release or default-branch commit proved it
// instead. Omitted (absent means unknown) when neither route could
// decide it. GitHub only.
DefaultTokenWrite string `json:"defaultTokenWrite,omitempty"`
// DefaultTokenSource is how DefaultTokenWrite was decided: "log"
// (the run's own permissions log block) or "effect" (a
// github-actions[bot] release or default-branch commit). Omitted
// (absent means unknown) when DefaultTokenWrite is also absent.
// GitHub only.
DefaultTokenSource string `json:"defaultTokenSource,omitempty"`
// RegistryHost is the container registry host of the scanned GitLab
// project's instance (e.g. registry.gitlab.com), where GitLab Function
// OCI references live. It comes from the GitLab API
// (container_registry_image_prefix) or, when the API reports none, is
// derived from the configured instance URL — never from a CI/CD
// variable, which the scanned pipeline itself can redefine. GitLab only.
RegistryHost string `json:"registryHost,omitempty"`
// SelfActionMutableExec is set when the scanned repository is itself
// a GitHub Action (has an action.yml) whose own source fetches and
// executes mutable remote code at runtime. This is the producer-side
// counterpart of the per-`uses:` action.metadata.mutableRemoteExec:
// scanning the action's own repo must flag that the action it
// publishes is the mutable-remote-exec vector. Nil otherwise.
SelfActionMutableExec *MutableRemoteExec `json:"selfActionMutableExec,omitempty"`
// WorkflowFileCount is the number of workflow files the GitHub
// collector fetched in remote (--github-url) mode. Used by
// TotalProgressStepsForPipeline to size the progress bar across
// the listing+fetch+enrichment phases of upstream-fetch scans.
// Stays zero in local-clone mode (the local scanner reports a
// single "Scanning" tick rather than per-file).
WorkflowFileCount int `json:"-"`
// AnalyzedWorkflows are the GitHub workflow files this scan read, each
// with its repo-relative path and content (#443). Carried json:"-" like
// WorkflowFileCount above: a collector-side record for the JSON report,
// never part of the OPA evaluation input. Empty on the GitLab path.
AnalyzedWorkflows []AnalyzedWorkflow `json:"-"`
// GlobalVariables are pipeline-level variables declared at the top
// of the source (e.g. `variables:` block at the root of
// .gitlab-ci.yml). Includes the merge with upstream component /
// template defaults — convenient for "what will actually be set at
// runtime" checks (CI_DEBUG_TRACE leaking secrets, insecure
// DOCKER_TLS_CERTDIR …).
GlobalVariables map[string]string `json:"globalVariables,omitempty"`
// LocalGlobalVariables are the pipeline-level variables the user
// wrote directly at the root of their CI file, before any merge
// with included templates or components. Empty when the user did
// not declare a `variables:` block at the root. Used by policies
// that must distinguish "user-authored override" from "shipped by
// upstream template" — notably ISSUE-205 (variable-override).
// Mirrors the raw-conf scan in
// controlGitlabPipelineJobVariablesOverride.go.
LocalGlobalVariables map[string]string `json:"localGlobalVariables,omitempty"`
// RenovateConfigPath is the file path where a Renovate config was
// discovered (`renovate.json`, `.renovaterc`, `renovate.json5`,
// …). Empty when no Renovate config is present.
RenovateConfigPath string `json:"renovateConfigPath,omitempty"`
// SecurityPolicyPath is the path of the repository's SECURITY.md
// (root, .github/, or docs/). Empty when the file is absent.
SecurityPolicyPath string `json:"securityPolicyPath,omitempty"`
// MRApprovalRules are the project's merge-request approval rules
// (GitLab: Settings > Merge requests > Approval rules), each with the
// number of approvals it requires and its protected-branch coverage.
// Projected from the same protection collection as Branches; empty on
// providers without approval rules. Read by the
// mergeRequestApprovalRulesMust* controls (ISSUE-502/504).
MRApprovalRules []MRApprovalRule `json:"mrApprovalRules,omitempty"`
// MRApprovalRulesKnown is true when the approval-rules listing was
// fetched authoritatively (an empty MRApprovalRules then means "no
// rules", a real state the rules reason about). It is false when the
// listing could not be read — a 401/403 on the approvals API (commonly
// a non-premium GitLab, or a token without scope) — so a control keyed
// on these rules reports not-evaluable rather than a false pass.
// Mirrors Branch.ProtectionDetailsKnown.
MRApprovalRulesKnown bool `json:"mrApprovalRulesKnown,omitempty"`
// Environments are the repository's deployment environments (GitHub:
// Settings > Environments) and the reviewer rules each one carries.
// GitHub only; empty on GitLab.
Environments []Environment `json:"environments,omitempty"`
// EnvironmentsKnown is true when Environments was fetched
// authoritatively (an empty Environments then means "no
// environments", a real state the situation facts reason about). It
// is false when the listing could not be read (a 401/403 on the
// environments API, a 404, or a network failure), so a job's
// environment reviewer fact stays unknown rather than a false "no
// reviewers". Mirrors MRApprovalRulesKnown.
EnvironmentsKnown bool `json:"environmentsKnown,omitempty"`
// SettingsVariables are the project's CI/CD settings variables
// (GitLab: Settings > CI/CD > Variables), each with its security
// flags — NOT the CI file's `variables:` block (that lives in
// GlobalVariables / job Variables). The value is deliberately never
// projected, per the #370 variable-sensitivity tiers, so a scan
// report never carries secret material. Empty on providers without
// settings variables.
SettingsVariables []SettingsVariable `json:"settingsVariables,omitempty"`
// SettingsVariablesKnown is true when the settings-variable listing
// was fetched authoritatively (an empty SettingsVariables then means
// "no variables", a pass). It is false when the listing could not be
// read — a 401/403 on the settings API, or a provider that has no
// such concept — so a control keyed on these variables reports
// not-evaluable rather than a false pass. Mirrors Branch.
// ProtectionDetailsKnown.
SettingsVariablesKnown bool `json:"settingsVariablesKnown,omitempty"`
// MRApprovalSettings are the project's merge-request approval settings
// (GitLab: Settings > Merge requests), normalized to positive-security
// form. nil when the settings could not be read (a 401/403 from a token
// without scope), so the mergeRequestApprovalSettingsMustBeCompliant
// control (ISSUE-503) abstains and reports not-evaluable rather than a
// false pass. Projected from the same protection collection as
// MRApprovalRules.
MRApprovalSettings *MRApprovalSettings `json:"mrApprovalSettings,omitempty"`
// MRSettings are the project's merge-request/merge settings (GitLab:
// Settings > Merge requests — merge method, squash, merge trains, etc.),
// carried in raw form so the mergeRequestSettingsMustBeCompliant control
// (ISSUE-506) can compare each against the configured expectation. nil when
// the project payload could not be read, so the control abstains and reports
// not-evaluable rather than a false pass. Projected from the same protection
// collection as MRApprovalSettings.
MRSettings *MRSettings `json:"mrSettings,omitempty"`
// SecurityPolicyProject is the GitLab security policy project linked to this
// project (Settings > Security & Compliance > Policies), projected from the
// protection collection. nil when the linkage was not collected (the control
// is disabled) or could not be read authoritatively, so the
// projectMustHaveSecurityPolicySource control (ISSUE-601) abstains and
// reports not-evaluable. Distinct from SecurityPolicyPath above, which is the
// repository's SECURITY.md file (a GitHub-oriented, unrelated control).
SecurityPolicyProject *SecurityPolicyProjectState `json:"securityPolicyProject,omitempty"`
// Dockerfiles lists every Dockerfile the collector scanned at the
// repo root and under common build directories, with each FROM
// base-image extracted so policies can check pinning state.
Dockerfiles []Dockerfile `json:"dockerfiles,omitempty"`
// AdvisoryWarnings collects one human-readable line per action whose
// known-CVE check could not be completed because the pinned commit
// could not be resolved to a version (the action repo's tag list was
// unavailable — org IP allow list, rate limit, network). The renderer
// and the JSON/SARIF/GLSAST writers surface these as "could not
// verify" warnings so a degraded check is visible instead of silently
// passing. Empty when every action resolved or no action was degraded.
AdvisoryWarnings []string `json:"advisoryWarnings,omitempty"`
// UnexpandedActions lists what the nested actions resolution could not
// expand, NestedBudgetExceeded says the container budget was passed (R3)
// and NestedUnresolvedCause why the closure could not be resolved at all
// (R6), "" when it was (spec 2026-10-16 section 7). Never in the Rego input.
// The cause is decided by the run's first remote read only, so the two
// cannot both be set; if they ever are, the overflow wins and the run
// degrades (coordinator ruling L7, applied by the control package).
UnexpandedActions []UnexpandedAction `json:"-"`
NestedBudgetExceeded bool `json:"-"`
NestedUnresolvedCause string `json:"-"`
// NestedEntriesOverflow counts the budget references of the list that
// the per-job entry bound left unexpanded, the others being past the
// container budget (NestedBudgetExceeded covers both).
NestedEntriesOverflow int `json:"-"`
// NestedJobsOverflow counts the budget references of the list that the
// run-wide cap on appended nested jobs left unexpanded: the calls of a
// reusable workflow of another repository past the cap.
NestedJobsOverflow int `json:"-"`
Raw map[string]any `json:"raw,omitempty"`
// Visibility is the repository's exposure as the provider reports it:
// VisibilityPublic, VisibilityPrivate or VisibilityUnknown. The situation
// facts weigh contributor entries by it; unknown is treated as public.
Visibility string `json:"visibility,omitempty"`
}
NormalizedPipeline is the provider-agnostic view of a CI/CD pipeline. Provider-specific data that does not fit the shared fields lives in Raw.
type OverriddenJob ¶
type OverriddenJob struct {
Name string `json:"name"`
Keys []string `json:"keys,omitempty"`
// Values holds the whole local job block exactly as the user's own
// configuration declares it, Keys being only the CI/CD keywords the
// override regex matched inside it. It exists to feed the override
// fingerprint, which digests the block as a whole so that any change
// to the overriding job moves it, and is never serialised: this IR is
// the policy engine's input and can be dumped, so the user's job
// content must not travel with it. What leaves the CLI about an
// override is the fingerprint, the job name and the key names, never
// the content behind them.
Values map[string]any `json:"-"`
}
OverriddenJob captures a single job whose inherited definition was locally overridden. Keys is the list of CI/CD fields (script, image, rules, …) whose values were redefined in the pipeline configuration.
type Provider ¶
type Provider string
Provider identifies the CI/CD platform that originated a pipeline.
type ReusableCaller ¶ added in v0.6.0
type ReusableCaller struct {
// Job is the calling job's name.
Job string `json:"job"`
// Permissions is the calling job's effective permissions (its own, its
// workflow's, or, up a chain of calls, the first declared), nil when
// nothing up the chain declares any: the assumed repository default.
Permissions any `json:"permissions,omitempty"`
// Triggers is the events the call runs on: those of the workflow at the
// top of the chain, workflow_call left out.
Triggers []string `json:"triggers,omitempty"`
// SecretsInherit is true when the call passes every secret; Secrets is
// otherwise the explicit map, the called workflow's secret name to the
// expression passed (resolved up the chain where it forwards one).
SecretsInherit bool `json:"secretsInherit,omitempty"`
Secrets map[string]string `json:"secrets,omitempty"`
// With is the call's inputs, an input passed through from a caller up
// the chain (`${{ inputs.x }}`) replaced by what that caller passes.
With map[string]any `json:"with,omitempty"`
// If is the calling job's own job-level `if:`, verbatim, "" when the
// call is unconditional: the called workflow runs only on the events
// it lets through (ClassifyGates reads it through the calling job).
If string `json:"if,omitempty"`
}
ReusableCaller is one call of a reusable workflow, as seen from a job of the called workflow (Job.Callers).
type SecurityPolicyProjectState ¶ added in v0.4.44
type SecurityPolicyProjectState struct {
// Known is true when the linkage was read authoritatively.
Known bool `json:"known"`
// LinkedProjectID is the numeric ID of the linked security policy project,
// or 0 when none is linked.
LinkedProjectID int `json:"linkedProjectId"`
// LinkedProjectPath is the full path of the linked security policy project,
// or "" when none is linked.
LinkedProjectPath string `json:"linkedProjectPath"`
}
SecurityPolicyProjectState is the GitLab security policy project linkage projected onto the IR. Known is true when the linkage was read authoritatively; a Known projection with LinkedProjectID == 0 means no policy project is linked. When Known is false the rule abstains (not-evaluable).
type SettingsVariable ¶ added in v0.4.41
type SettingsVariable struct {
Name string `json:"name"`
// Type and Environment always serialize (no omitempty): both are
// identity fields for the cicdVariablesMustBe* controls, and the rules
// read them unconditionally, so an absent key would break the finding
// or leave an inconsistent identity. GitLab always supplies both
// ("env_var"/"file" and the environment scope, "*" for all).
Type string `json:"type"`
Environment string `json:"environment"`
Protected bool `json:"protected"`
Masked bool `json:"masked"`
}
SettingsVariable is one project CI/CD settings variable (GitLab: Settings > CI/CD > Variables). It carries the variable's identity and its security-relevant flags only: the value is never projected, per the #370 variable-sensitivity tiers, so a finding can name an exposed variable without carrying its secret. Type is GitLab's variable_type ("env_var" or "file"); Environment is the environment scope ("*" for all).
type UnexpandedAction ¶ added in v0.7.2
type UnexpandedAction struct {
Uses string `json:"uses"`
Via []string `json:"via,omitempty"`
Reason string `json:"reason"`
// Invalid marks a reference refused before any fetch because it is
// outside GitHub's grammar (reason fetch). The capped fetch warning
// names the reason from it, so it adds no warning line of its own.
Invalid bool `json:"-"`
}
UnexpandedAction is a reference the nested resolution could not expand (monorepo spec 2026-10-16-nested-actions-resolution-design.md section 7): Uses as written, Via the chain above it, Reason one of the four below.