Lint Rules
The GitLab lexicon ships lint rules that run during chant lint and post-synth checks that validate the serialized YAML after chant build.
Lint rules
Section titled “Lint rules”Lint rules analyze your TypeScript source code before build.
WGL001 — Deprecated only/except
Section titled “WGL001 — Deprecated only/except”Severity: warning | Category: style
Flags usage of only: and except: keywords, which are deprecated in favor of rules:. The rules: syntax is more flexible and is the recommended approach.
import { Job, Rule, CI } from "@intentius/chant-lexicon-gitlab";
// chant-disable-next-line WGL001export const deployBad = new Job({ stage: "deploy", script: ["npm run deploy"], only: ["main"],});
export const deployGood = new Job({ stage: "deploy", script: ["npm run deploy"], rules: [new Rule({ if: `${CI.CommitBranch} == ${CI.DefaultBranch}`, })],});WGL002 — Missing script
Section titled “WGL002 — Missing script”Severity: error | Category: correctness
A GitLab CI job must have script, trigger, or run defined. Jobs without any of these will fail pipeline validation.
import { Job, Image, Trigger } from "@intentius/chant-lexicon-gitlab";
// chant-disable-next-line WGL002export const wgl002Bad = new Job({ stage: "build", image: new Image({ name: "node:20" }),});
export const wgl002Good = new Job({ stage: "build", image: new Image({ name: "node:20" }), script: ["npm run build"],});
export const downstream = new Job({ trigger: new Trigger({ project: "my-group/other-repo" }),});WGL003 — Missing stage
Section titled “WGL003 — Missing stage”Severity: info | Category: style
Jobs should declare a stage property. Without it, the job defaults to the test stage, which may not be the intended behavior.
import { Job } from "@intentius/chant-lexicon-gitlab";
// chant-disable-next-line WGL003export const wgl003Bad = new Job({ script: ["npm run build"],});
export const wgl003Good = new Job({ stage: "build", script: ["npm run build"],});WGL004 — Artifacts without expiry
Section titled “WGL004 — Artifacts without expiry”Severity: warning | Category: performance
Flags Artifacts without expireIn. Artifacts without expiry are kept indefinitely, consuming storage. Always set an expiration.
import { Job, Artifacts } from "@intentius/chant-lexicon-gitlab";
// chant-disable-next-line WGL004export const wgl004Bad = new Job({ script: ["npm run build"], artifacts: new Artifacts({ paths: ["dist/"], }),});
export const wgl004Good = new Job({ script: ["npm run build"], artifacts: new Artifacts({ paths: ["dist/"], expire_in: "1 hour", }),});Post-synth checks
Section titled “Post-synth checks”Post-synth checks run against the serialized YAML after build. They catch issues only visible in the final output.
WGL010 — Undefined stage
Section titled “WGL010 — Undefined stage”Severity: error
Flags jobs that reference a stage not present in the collected stages list. This causes a pipeline validation error in GitLab.
WGL011 — Unreachable job
Section titled “WGL011 — Unreachable job”Severity: warning
Flags jobs where all rules: entries have when: "never", making the job unreachable. This usually indicates a configuration error.
import { Job, Rule, CI } from "@intentius/chant-lexicon-gitlab";
// chant-disable-next-line WGL011export const noop = new Job({ script: ["echo unreachable"], rules: [ new Rule({ if: CI.CommitBranch, when: "never" }), new Rule({ if: CI.CommitTag, when: "never" }), ],});WGL012 — Deprecated property usage
Section titled “WGL012 — Deprecated property usage”Severity: warning
Flags properties marked as deprecated in the GitLab CI schema. Deprecation signals are mined from property descriptions (keywords like “deprecated”, “legacy”, “no longer available”). Using deprecated properties may cause unexpected behavior in future GitLab versions.
WGL013 — Invalid needs: target
Section titled “WGL013 — Invalid needs: target”Severity: error
Flags jobs whose needs: entries reference a job not defined in the pipeline, or reference themselves. Both cause GitLab pipeline validation failures. When include: is present, the check is skipped since needed jobs may come from included files.
WGL014 — Invalid extends: target
Section titled “WGL014 — Invalid extends: target”Severity: error
Flags jobs whose extends: references a template or hidden job not defined in the pipeline. GitLab rejects pipelines with unresolved extends references. When include: is present, the check is skipped since templates may come from included files.
WGL015 — Circular needs: chain
Section titled “WGL015 — Circular needs: chain”Severity: error
Detects cycles in the needs: dependency graph. If job A needs B and B needs A (directly or transitively), GitLab rejects the pipeline. Reports the full cycle chain in the diagnostic message.
WGL016 — Secret in a variables block
Section titled “WGL016 — Secret in a variables block”Severity: error
Detects hardcoded passwords, tokens, or keys in a variables: block. Move them to CI/CD masked variables instead of committing them to the pipeline. The precursor to the WGL038–040 secret-scoping checks.
WGL017 — Insecure registry
Section titled “WGL017 — Insecure registry”Severity: warning
Flags Docker push/pull to a non-HTTPS registry in a job script. HTTP gives the registry traffic no transport integrity.
WGL018 — Missing timeout
Section titled “WGL018 — Missing timeout”Severity: warning
Flags jobs without an explicit timeout:. The instance default (often 1 hour) is too long for most jobs and lets a hung job hold a runner.
WGL019 — Missing retry on deploy jobs
Section titled “WGL019 — Missing retry on deploy jobs”Severity: info
Deploy-stage jobs benefit from a retry: strategy to ride out transient infrastructure failures. Informational, not required.
WGL020 — Duplicate job names
Section titled “WGL020 — Duplicate job names”Severity: error
Detects multiple jobs that resolve to the same kebab-case key in the serialized YAML. GitLab silently merges duplicate keys, so one job’s config quietly overwrites the other.
WGL021 — Unused variables
Section titled “WGL021 — Unused variables”Severity: warning
Flags global variables: not referenced by any job script — usually stale configuration adding noise.
WGL022 — Missing artifacts expiry
Section titled “WGL022 — Missing artifacts expiry”Severity: warning
Flags artifacts: without expire_in:. Depending on instance config the default is “never expire,” which bloats storage.
WGL023 — Overly broad rules
Section titled “WGL023 — Overly broad rules”Severity: info
Flags a job whose only rule is when: always with no conditions (if:, changes:, …). That disables all pipeline filtering for the job, which is usually unintended.
WGL024 — Manual without allow_failure
Section titled “WGL024 — Manual without allow_failure”Severity: warning
Flags when: manual jobs that don’t set allow_failure: true. Without it the manual job blocks the pipeline from progressing past its stage until someone triggers it.
WGL025 — Missing cache key
Section titled “WGL025 — Missing cache key”Severity: warning
Flags cache: without a key:. GitLab falls back to the default key, causing cache collisions between unrelated jobs on the same runner.
WGL026 — Privileged services without TLS
Section titled “WGL026 — Privileged services without TLS”Severity: warning
Flags Docker-in-Docker (DinD) services that don’t set DOCKER_TLS_CERTDIR, leaving the Docker daemon on an unencrypted socket. Extended by WGL036 for the merge-request-reachable case.
WGL027 — Empty script
Section titled “WGL027 — Empty script”Severity: error
Detects jobs with script: [] or only empty strings. GitLab rejects empty scripts at pipeline validation time.
WGL028 — Redundant needs
Section titled “WGL028 — Redundant needs”Severity: info
Detects needs: entries already implied by stage ordering. Not incorrect, but redundant needs add noise and make the pipeline harder to maintain.
Supply-chain security pass (WGL029–048)
Section titled “Supply-chain security pass (WGL029–048)”WGL029 onward are a CI/CD supply-chain security pass, the GitLab counterpart to the github lexicon’s GHA029–058: pin & vet includes/components/images, scope CI_JOB_TOKEN and OIDC, guard trust boundaries against untrusted CI input, mask/protect/scope secrets, reject unsound rules: expressions, and keep artifacts/caches honest. They run statically on the emitted .gitlab-ci.yml.
The checks that need a moving external truth — whether a pinned component/include ref still resolves, whether an upstream was archived or moved, whether a new advisory covers a component in use — live in the operational layer instead. Schedule the PipelineAuditOp (temporal lexicon) for that live half; it reads the emitted include: / component: / image: references and reports drift via report | issue | merge-request.
WGL029 — Unpinned include:project / component
Section titled “WGL029 — Unpinned include:project / component”Severity: warning
Flags an include:project or CI/CD component: resolved by a moving ref — a branch, a missing ref: (defaults to the default branch), or a floating component version — instead of a pinned tag or commit SHA.
WGL030 — Mutable or insecure include:remote
Section titled “WGL030 — Mutable or insecure include:remote”Severity: error (HTTP) / warning (HTTPS)
Flags include:remote URLs fetched over HTTP (no transport integrity) or over HTTPS but inherently mutable. Prefer a pinned include:project or component. Generalizes WGL017 to includes.
WGL031 — Container image without a digest
Section titled “WGL031 — Container image without a digest”Severity: warning
Flags image: and services: references not pinned to an immutable @sha256: digest. Variable-based references (e.g. $CI_REGISTRY_IMAGE:tag) are skipped.
WGL032 — Look-alike include/component source
Section titled “WGL032 — Look-alike include/component source”Severity: warning
Flags an include:project / component: source that is a near-miss (edit distance 1–2) of a well-known GitLab CI source but not an exact match — a likely typo or impersonation. Backed by a vendored reference list.
WGL033 — OIDC id_token without a scoped audience
Section titled “WGL033 — OIDC id_token without a scoped audience”Severity: warning
Flags an id_tokens: declaration with no aud: or a wildcard audience. The audience binds the minted OIDC token to a relying party; without it a leaked token is accepted anywhere.
WGL034 — OIDC id_token mintable from a merge-request pipeline
Section titled “WGL034 — OIDC id_token mintable from a merge-request pipeline”Severity: warning
Flags a job that declares id_tokens: and is reachable from merge-request pipelines, which outside contributors can trigger. Restrict OIDC jobs to protected refs or require approval.
The project-level
CI_JOB_TOKENallowlist and a variable’s protected status are project settings, not emitted pipeline YAML, so they are out of scope for these post-synth checks (see issue #298’s caveat).
WGL035 — Untrusted CI variable in a script
Section titled “WGL035 — Untrusted CI variable in a script”Severity: warning
Flags an attacker-controllable predefined variable (branch/tag name, commit or MR title/description, author) referenced in a script: command, where a crafted value can inject shell commands. Quote it and avoid using it in sensitive commands.
WGL036 — Privileged DinD reachable from merge requests
Section titled “WGL036 — Privileged DinD reachable from merge requests”Severity: warning
Flags a Docker-in-Docker (privileged) job reachable from merge-request pipelines, which outside contributors can trigger. Restrict privileged services to protected refs. Complements WGL026.
WGL037 — Regex gate on an untrusted ref
Section titled “WGL037 — Regex gate on an untrusted ref”Severity: warning
Flags a rules:if that gates on a regex match (=~) over an attacker-controllable ref variable — a crafted branch/tag name can satisfy the pattern. Match the full ref with == or gate on a protected condition.
WGL038 — Secret reachable from a merge-request pipeline
Section titled “WGL038 — Secret reachable from a merge-request pipeline”Severity: warning
Flags a user-defined secret-like variable read by a job reachable from merge-request pipelines, which can run untrusted code. Gate the job to protected refs or mark the variable protected. (Built-in CI_* variables are excluded.)
WGL039 — Secret echoed to job logs
Section titled “WGL039 — Secret echoed to job logs”Severity: warning
Flags a script: command that prints a secret-like variable (echo/printf/cat). Logs are broadly readable and masking can be defeated by transforms.
WGL040 — Hardcoded registry credential
Section titled “WGL040 — Hardcoded registry credential”Severity: error
Flags a docker login (or compatible) passing a literal password via -p/--password instead of a variable or --password-stdin. Extends WGL016 to scripts.
Variable masking and protected status are GitLab project/CI-settings, not emitted YAML — WGL016 already nudges toward masked variables; this group covers the exposure paths visible in the pipeline.
WGL041 — Unsound rules:if condition
Section titled “WGL041 — Unsound rules:if condition”Severity: warning
Flags a tautological rules:if where both sides of ==/!= are identical, so the condition is always true or always false. Generalizes WGL011 to conditions presented as gates.
WGL042 — Unreachable rules after an unconditional match
Section titled “WGL042 — Unreachable rules after an unconditional match”Severity: warning
Flags rules listed after an unconditional rule (no if:, not when: never). GitLab takes the first matching rule, so a catch-all makes everything after it dead. Put specific rules first.
WGL043 — Match-anything regex gate
Section titled “WGL043 — Match-anything regex gate”Severity: warning
Flags a rules:if whose =~ regex matches every value (empty, dot-star, anchored dot-star) — a filter that admits everything. Tighten the pattern or remove the gate.
The auto-fix acceptance item is N/A at the post-synth layer (PostSynthDiagnostic has no fix channel); it belongs to the declarative source-lint rules.
WGL044 — Public artifacts
Section titled “WGL044 — Public artifacts”Severity: warning
Flags artifacts:public: true, which makes build output downloadable by anyone. Keep artifacts private unless they are meant to be world-readable.
WGL045 — Credential-bearing artifact path
Section titled “WGL045 — Credential-bearing artifact path”Severity: error
Flags an artifacts:paths: entry that looks like a credential or sensitive file (.env, *.pem, id_rsa, *.key, .npmrc, .netrc, credentials). Artifacts flow downstream and are downloadable — exclude the file.
WGL046 — Cache poisoning from a merge-request pipeline
Section titled “WGL046 — Cache poisoning from a merge-request pipeline”Severity: warning
Flags a job that writes a cache (push policy) and is reachable from merge-request pipelines. An MR can poison the cache for a later protected run that restores the same key. Restrict cache writes to protected refs or scope the key.
Artifact /
dependencies:flow across a protected boundary depends on a ref’s protected status, a project setting not present in emitted YAML — out of scope here.
WGL047 — Software fetched and piped to a shell
Section titled “WGL047 — Software fetched and piped to a shell”Severity: warning
Flags a script: command that pipes a network download straight into a shell (curl ... | bash). The fetched code is unpinned and unverified — download to a file, verify a checksum/signature, then run it.
WGL048 — Pipeline without a name
Section titled “WGL048 — Pipeline without a name”Severity: info
Flags a pipeline that defines a workflow: block but no workflow:name. A pipeline name aids identification in the GitLab UI and audit output.
Two #304 items stay out of scope: a broad privileged-service / DinD check (already covered by WGL026 for DinD-TLS and WGL036 for MR-reachable DinD; a runner’s privileged isolation is not in emitted YAML) and an optional include/component allowlist policy (configuration, overlapping WGL032’s vendored source list).
Running lint
Section titled “Running lint”# Lint your chant projectchant lint
# Lint with auto-fix where supportedchant lint --fixTo suppress a rule on a specific line:
// chant-disable-next-line WGL001export const deploy = new Job({ only: ["main"], script: ["deploy"] });To suppress globally in chant.config.ts:
export default { lint: { rules: { WGL003: "off", }, },};