# Pushproof > Audit a GitLab CI/CD configuration before pushing it: will GitLab accept the file, and is it safe to run. The free check runs in the browser and uploads nothing: it parses a pasted .gitlab-ci.yml (and the local files it includes) with line numbers, resolves YAML anchors, `<<:` merge keys, `!reference`, `extends` and `default:` the way GitLab does, and runs 25 deterministic checks. The paid audit confirms or dismisses every flag, gives the corrected top-level block for each finding and says when each job runs; the paid rewrite returns the whole file with the fixes applied, says what it changed and what it left for a person to decide, and lists the CI/CD variables to define. The page checks every quote, line and fix against the paste, re-runs its own checks on every fix and on the rewritten file, and calls out a reply that repeats a secret from the paste. App: https://pushproof.skillsafe.ai/ API tutorial: https://pushproof.skillsafe.ai/api.html ## Input - The pipeline pasted into one box. Local include files go after a `---` line, each introduced by a `# file: path` comment; dropped files are joined that way automatically. The file with `include:` is the main file; included files are merged under it, as GitLab does. - Nothing talks to GitLab, a runner or the project settings: every flag is a reading of the text, with the block (job, template or keyword) and line it points at. - Remote, project, template and component includes are named but not read; names they might define (a template a job extends, a job another needs) are reported as info, not as missing. - Protected branches, masked variables, the runner executor and the GitLab version are outside the paste: the paid lanes ask rather than assume. ## Flags the free check raises - GL-01 (high): a job in a stage that stages: does not list (or a non-default stage when there is no stages:). - GL-02 (high; info when an unread include may define it): needs: names an undefined job, a hidden template, or a job in a later stage. - GL-03 (high): dependencies: names an unknown job, or one that is not in an earlier stage. - GL-04 (high; info when an unread include may define it): extends: names a missing template, is circular, or is deeper than 11 levels. - GL-05 (high): rules: together with only:/except: in one job. - GL-06 (high): an unknown keyword in a job, default: or workflow:, or a top-level key that is not a job. - GL-07 (high): a job with no script:, trigger: or run:. - GL-08 (high): a value GitLab refuses - retry above 2, parallel outside 1-200, an unknown when or environment action, when: delayed without start_in, a script line YAML reads as a key: value mapping. - GL-09 (high): a secret written into the file - a literal password/token/key variable, a token pattern, a literal registry password, a URL carrying a password. - GL-10 (high in every pipeline; medium when automatic on the default branch): a production deploy with no manual gate. - GL-11 (high): terraform apply -auto-approve with no manual gate. - GL-12 (medium): a download piped straight into a shell. - GL-13 (medium): TLS verification switched off. - GL-14 (medium): a registry password passed as a command-line argument. - GL-15 (medium): an image or service on :latest or untagged. - GL-16 (medium): a deploy job without resource_group. - GL-17 (medium): branch and merge-request pipelines that do not fit - merge-request rules beside branch jobs with no workflow:rules (duplicate pipelines), or workflow:rules that create merge-request pipelines while jobs without rules never run in them. - GL-18 (medium for one key shared by different paths; low for no key): cache keys. - GL-19 (low): deprecated top-level image/services/cache/before_script/after_script. - GL-20 (low): only:/except:. - GL-21 (low): artifacts with no expire_in. - GL-22 (low): jobs with no timeout. - GL-23 (low): test/lint jobs that are not interruptible. - GL-24 (low): a security scan with allow_failure: true. - GL-25 (info): an include the page could not read. The free check also lists every job after extends and default: are applied (stage, when it runs - manual, restricted to named branches/tags, or every pipeline - needs, image), every top-level block with its lines, and the stages with their jobs. Any fix can be put back into the paste and re-checked for free. ## Paid lanes - `audit`: verdict unsafe | needs_work | ready; findings with catalogue id, the page flags confirmed, block, line, severity (high | medium | low), title, verbatim evidence (secrets written as ***), why, and the corrected top-level block(s) as the fix; one answer (confirmed | dismissed with a reason) per page flag; one plain-words line per job on when it runs and what it does; an ordered plan. - `rewrite`: verdict ready | partial; the whole rewritten main file; changes (the flags each fixes, the block, what and why); kept flags with the reason; removed jobs; the CI/CD variables to define (masked, protected); questions. - Handoff: an audit offers "Rewrite the file with these fixes", which runs the rewrite lane on the same paste with the audit's verdict and findings in an `audit_handoff` field. A rewrite offers "Check this file", which puts the rewritten file in the box and runs the free check on it. ## What the page checks in each reply - Audit: every finding names a pasted block and a line inside it; its evidence is really in the paste; its fix parses as YAML and is a mapping of top-level blocks; each fix is put back into the file (replacing the block of the same name, or added) and the free check runs again - it must clear the flag and add no new high flag. Every flag is answered once, consistently; the verdict fits the severities; every job is described, and nothing else. - Rewrite: the file reads as a pipeline; every job survives (or is listed as removed with a reason); no script command disappears unless a change names the job; every flag is fixed or kept exactly once, and a flag claimed as fixed is really gone; no new high flag appears; every variable the reply asks for appears in the file or the paste; an audit handoff's findings are all changed or kept. - Both: no secret value from the paste appears anywhere in the reply. ## Sources - gitlab-ci-patterns, wshobson/agents (github.com/wshobson/agents, plugins/cicd-automation/skills/gitlab-ci-patterns), MIT - https://skillsafe.ai/skill/@wshobson/gitlab-ci-patterns - gitlab-ci-generator, akin-ozer/cc-devops-skills (github.com/akin-ozer/cc-devops-skills, devops-skills-plugin/skills/gitlab-ci-generator), Apache-2.0 - https://skillsafe.ai/skill/@akin-ozer/gitlab-ci-generator - GitLab CI/CD YAML syntax reference (docs.gitlab.com/ci/yaml) for the keyword set, default: keys, needs/dependencies stage rules, retry and parallel limits, extends depth and merge behaviour the checks rely on. - Attribution and licence notes: https://pushproof.skillsafe.ai/NOTICE.txt Model: gpt-terra, metered per run; the free check needs no account. GitLab is a trademark of GitLab Inc. Pushproof is not affiliated with or endorsed by GitLab Inc.