<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Thib</title>
    <description>The latest articles on DEV Community by Thib (@totarathib).</description>
    <link>https://dev.to/totarathib</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3876604%2Fd901060d-7aa2-4fa6-8715-92bdaa7daeae.png</url>
      <title>DEV Community: Thib</title>
      <link>https://dev.to/totarathib</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/totarathib"/>
    <language>en</language>
    <item>
      <title>Hardening GitLab CI/CD with an open-source pipeline linter</title>
      <dc:creator>Thib</dc:creator>
      <pubDate>Mon, 13 Apr 2026 13:33:42 +0000</pubDate>
      <link>https://dev.to/totarathib/hardening-gitlab-cicd-with-an-open-source-pipeline-linter-30c1</link>
      <guid>https://dev.to/totarathib/hardening-gitlab-cicd-with-an-open-source-pipeline-linter-30c1</guid>
      <description>&lt;p&gt;Most GitLab linters tell you if your YAML is valid. Very few tell you if your YAML is &lt;strong&gt;dangerous&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's easy to feel safe when you see a green build, but a "successful" pipeline can still have major governance gaps. A green checkmark won't tell you if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your pipeline uses mutable &lt;code&gt;:latest&lt;/code&gt; tags or untrusted registries.&lt;/li&gt;
&lt;li&gt;A developer accidentally disabled a security job with &lt;code&gt;allow_failure: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Your "protected" branch settings are actually misconfigured.&lt;/li&gt;
&lt;li&gt;Sensitive variables are being leaked via &lt;code&gt;CI_DEBUG_TRACE&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built &lt;strong&gt;&lt;a href="https://github.com/getplumber/plumber" rel="noopener noreferrer"&gt;Plumber&lt;/a&gt;&lt;/strong&gt; to bridge the gap between "valid syntax" and "secure configuration." It's an open-source CLI that checks both your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; and your GitLab project settings to see if they meet your organization's compliance standards.&lt;/p&gt;




&lt;h3&gt;
  
  
  Beyond Linting: The PBOM
&lt;/h3&gt;

&lt;p&gt;One of the most powerful features is the &lt;strong&gt;Pipeline Bill of Materials (PBOM)&lt;/strong&gt;. Plumber can export a &lt;strong&gt;CycloneDX SBOM&lt;/strong&gt; specifically for your CI/CD. This gives you a machine-readable inventory of every container image, component, and remote template your pipeline touches, essential for auditing your software supply chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it enforces
&lt;/h3&gt;

&lt;p&gt;Plumber ships with a &lt;strong&gt;default policy&lt;/strong&gt; you can customize in &lt;code&gt;.plumber.yaml&lt;/code&gt;. It covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Image Governance:&lt;/strong&gt; Allowed registries, forbidden tags, and mandatory digest pinning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline Integrity:&lt;/strong&gt; Ensures includes are up-to-date and prevents the use of forbidden versions (like &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;HEAD&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Hardening:&lt;/strong&gt; Detects Docker-in-Docker (DinD) usage on shared runners and prevents unsafe variable expansion (mapped to &lt;strong&gt;OWASP CICD-SEC-1&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting Verification:&lt;/strong&gt; Confirms branch protection is active and sensitive variable overrides are blocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More controls are available and documented at &lt;a href="https://getplumber.io/docs/cli#available-controls" rel="noopener noreferrer"&gt;getplumber.io/docs/cli#available-controls&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  What the output looks like
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;plumber analyze&lt;/code&gt; gives you a detailed, actionable report right in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyzing project: mygroup/my-api
Branch: main
Config: .plumber.yaml

CONTROLS

✓  containerImageMustNotUseForbiddenTags
✗  containerImageMustComeFromAuthorizedSources
   • job "build": image "node:18" is not from an authorized registry
   • job "test": image "python:3.11" is not from an authorized registry
✓  branchMustBeProtected
✗  pipelineMustNotIncludeHardcodedJobs
   • job "lint" is hardcoded and not sourced from an include or component
✓  includesMustBeUpToDate
✗  includesMustNotUseForbiddenVersions
   • include "gitlab.com/myorg/templates/security@main" uses forbidden version "main"
✓  pipelineMustIncludeComponent
✓  pipelineMustIncludeTemplate

SUMMARY

Controls passed:    5 / 8
Compliance score:   62%
Threshold:          100%

Status: FAILED

Report written to: plumber-report.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The team gets a clear score plus exact failing controls. Findings are actionable and mapped to docs. &lt;code&gt;plumber-report.json&lt;/code&gt; can be archived in CI for audits and trend tracking.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example 1: Install and first run
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Install&lt;/strong&gt; (via Homebrew):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap getplumber/plumber
brew &lt;span class="nb"&gt;install &lt;/span&gt;plumber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Token Setup:&lt;/strong&gt; Create a GitLab token with &lt;code&gt;read_api&lt;/code&gt; and &lt;code&gt;read_repository&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The token needs &lt;strong&gt;Maintainer&lt;/strong&gt; permissions. GitLab restricts access to branch protection and repository settings to Maintainer roles or higher, and Plumber needs this visibility to verify your project posture.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITLAB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;glpat_xxxxxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generate and Analyze:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a local .plumber.yaml to customize your rules&lt;/span&gt;
plumber config generate

&lt;span class="c"&gt;# Run the analysis&lt;/span&gt;
plumber analyze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip: Plumber auto-detects your GitLab URL and project from your git remote.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Example 2: Local vs. Remote Analysis
&lt;/h3&gt;

&lt;p&gt;A key technical advantage is that Plumber uses your &lt;strong&gt;local&lt;/strong&gt; &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; but compares it against &lt;strong&gt;remote&lt;/strong&gt; project settings. This allows you to catch security regressions before you even &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to focus on specific issues (e.g., during a migration), you can target specific controls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;plumber analyze &lt;span class="nt"&gt;--controls&lt;/span&gt; containerImageMustNotUseForbiddenTags,branchMustBeProtected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Example 3: Continuous Feedback in GitLab CI
&lt;/h3&gt;

&lt;p&gt;You can automate these checks by adding the Plumber component to your pipeline. This provides feedback directly in your Merge Requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH &amp;amp;&amp;amp; $CI_OPEN_MERGE_REQUESTS&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_TAG&lt;/span&gt;

&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab.com/getplumber/plumber/plumber@v0.1.30&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mr_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;badge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;mr_comment: true&lt;/code&gt;, Plumber will post results as a comment on the MR, ensuring no one merges a pipeline that weakens your security posture.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why use a Linter for Governance?
&lt;/h3&gt;

&lt;p&gt;Plumber is about &lt;strong&gt;policy enforcement&lt;/strong&gt;. While scanners like Trivy or Grype look for vulnerabilities inside your code and images, Plumber ensures the &lt;strong&gt;framework&lt;/strong&gt; holding your CI/CD together is solid. It ensures your security scanners are actually running and that your repository settings aren't leaving the back door open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feedback
&lt;/h3&gt;

&lt;p&gt;If you maintain GitLab at scale, what would you want a pipeline linter to enforce that generic CI UI or scanners do not cover well? I am especially curious about policy ideas that fit real-world developer workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/getplumber/plumber" rel="noopener noreferrer"&gt;github.com/getplumber/plumber&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://getplumber.io/docs/cli/" rel="noopener noreferrer"&gt;getplumber.io/docs/cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component:&lt;/strong&gt; &lt;a href="https://gitlab.com/explore/catalog/getplumber/plumber" rel="noopener noreferrer"&gt;GitLab CI Catalog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>gitlab</category>
      <category>security</category>
    </item>
  </channel>
</rss>
