<?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: Ice</title>
    <description>The latest articles on DEV Community by Ice (@icelimited).</description>
    <link>https://dev.to/icelimited</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F357164%2Ff8138d55-c522-4d5a-bf5d-169a8a182a87.jpg</url>
      <title>DEV Community: Ice</title>
      <link>https://dev.to/icelimited</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/icelimited"/>
    <language>en</language>
    <item>
      <title>วางเลเยอร์ security scan ใน pipeline ยังไงไม่ให้ dev เกลียด</title>
      <dc:creator>Ice</dc:creator>
      <pubDate>Fri, 10 Jul 2026 02:06:42 +0000</pubDate>
      <link>https://dev.to/icelimited/waangeleyr-security-scan-ain-pipeline-yangaingaimaih-dev-ekliiyd-30m4</link>
      <guid>https://dev.to/icelimited/waangeleyr-security-scan-ain-pipeline-yangaingaimaih-dev-ekliiyd-30m4</guid>
      <description>&lt;p&gt;มีอยู่ช่วงนึงที่ pipeline เราช้าลงจาก 4 นาทีเป็น 18 นาที แล้วทุกคนก็เริ่มบ่น จนเกือบมีมติให้ปิด security scan บางตัวทิ้งเพื่อความเร็ว นั่นแหละคือจุดที่รู้ตัวว่าเราวาง scanning layer ผิดตั้งแต่ต้น ไม่ใช่เพราะ tool ห่วย แต่เพราะยัดทุกอย่างไว้จุดเดียวแล้วรันตามลำดับแบบโง่ๆ&lt;/p&gt;

&lt;p&gt;เรื่องนี้เลยอยากเขียนสรุปไว้ว่าจริงๆ แล้ว pipeline ที่มี security gate ครบ ไม่จำเป็นต้องช้า ไม่จำเป็นต้องทำให้ dev ด่า ถ้าวางเลเยอร์ให้ถูกจุด&lt;/p&gt;

&lt;h2&gt;
  
  
  ทำไมต้องมีหลายเลเยอร์ ไม่ใช่ tool เดียวจบ
&lt;/h2&gt;

&lt;p&gt;คำถามที่เจอบ่อยคือ "ทำไมไม่ใช้ตัวเดียวสแกนให้หมดเลย" คำตอบสั้นๆ คือไม่มี tool ตัวไหนสแกนทุกชั้นได้ดีเท่ากันหมด แต่ละตัวถูกออกแบบมาสำหรับ attack surface คนละแบบ:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secret ที่หลุดเข้า commit&lt;/strong&gt; — ต้องใช้ tool ที่สแกนเนื้อหา diff/history โดยเฉพาะ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic bug และ security anti-pattern ในโค้ด&lt;/strong&gt; — ต้องใช้ static analysis ที่เข้าใจ syntax ของภาษานั้นจริงๆ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerability ใน dependency ที่ดึงมาใช้&lt;/strong&gt; — ต้องเทียบกับ CVE database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ช่องโหว่ใน base image ตอน build container&lt;/strong&gt; — ต้องสแกนที่ layer ของ image ไม่ใช่ source code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;รวมเป็น scan ตัวเดียวไม่ได้ เพราะแต่ละอันมองคนละมุม ทีมเราเลยจบที่ 4 ชั้น: Gitleaks, Semgrep, OWASP Dependency-Check (กับ OSV เสริม), แล้วก็ Trivy&lt;/p&gt;

&lt;h2&gt;
  
  
  เลเยอร์ที่ 1: Gitleaks — ดักตั้งแต่ก่อน commit หลุด
&lt;/h2&gt;

&lt;p&gt;Gitleaks เป็นด่านแรกที่ต้องไวที่สุด เพราะ secret ที่หลุดเข้า git history แล้วลบยากกว่าที่คิดเยอะ ต่อให้ force-push ทับ commit ปัจจุบัน key เก่าก็ยังอยู่ใน history เดิม (เว้นแต่จะ rewrite history ทั้ง repo ซึ่งเจ็บกว่าเดิมอีก)&lt;/p&gt;

&lt;p&gt;ทีมเราวาง Gitleaks ไว้ 2 จุด:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;pre-commit hook&lt;/strong&gt; — เบาสุด รันแค่ diff ที่กำลังจะ commit ไม่กิน CI time เลย&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI stage แรกสุด&lt;/strong&gt; — เผื่อกรณีมีคน bypass pre-commit hook มา (เกิดขึ้นจริง บ่อยกว่าที่คิด)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;จุดที่คนมักพลาด: ตั้ง Gitleaks แล้วไม่เคย custom allowlist สำหรับ false positive พวก test fixture ที่มี string หน้าตาเหมือน API key แต่จริงๆ เป็น dummy data — พอ noise เยอะ dev ก็เริ่ม ignore alert ทั้งหมด ซึ่งอันตรายกว่าไม่มี scan อีก&lt;/p&gt;

&lt;h2&gt;
  
  
  เลเยอร์ที่ 2: Semgrep — จับ pattern ที่ linter ธรรมดาจับไม่ได้
&lt;/h2&gt;

&lt;p&gt;Semgrep ต่างจาก linter ทั่วไปตรงที่เขียน rule แบบ semantic ได้ ไม่ใช่แค่ regex บนข้อความ เช่นจับ pattern "query string ต่อกับ user input ตรงๆ โดยไม่ผ่าน parameterized query" ซึ่งเป็นต้นตอ SQL injection&lt;/p&gt;

&lt;p&gt;ข้อดีคือ community rule set ของ Semgrep ครอบคลุมเยอะมากอยู่แล้ว ไม่ต้องเขียนเองทั้งหมด แต่ rule set default บางอันก็ noisy เกินไปสำหรับ codebase เฉพาะทาง ทีมเราเลยแยก rule เป็น 2 ระดับ:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blocking rule&lt;/strong&gt; — เจอแล้ว fail pipeline ทันที (injection pattern, hardcoded credential pattern, insecure deserialization)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warning rule&lt;/strong&gt; — ขึ้นเป็น comment ใน PR แต่ไม่ block ให้ reviewer ตัดสินใจเอง&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;แบ่งแบบนี้แล้ว noise ลดไปเยอะ คนไม่เบื่อ alert&lt;/p&gt;

&lt;h2&gt;
  
  
  เลเยอร์ที่ 3: OWASP Dependency-Check + OSV — เช็ค dependency ที่ดึงมาใช้
&lt;/h2&gt;

&lt;p&gt;อันนี้คือจุดที่ pipeline เราเคยช้าที่สุด เพราะ OWASP Dependency-Check ต้อง sync NVD database ซึ่งบางทีช้าหรือ rate-limit เอา ทางแก้คือ:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cache NVD database ไว้ใน artifact storage แทนที่จะ download ใหม่ทุกรอบ&lt;/li&gt;
&lt;li&gt;รัน OSV-Scanner คู่กันไปด้วย เพราะ query ผ่าน API ตรงๆ ไม่ต้อง sync database เอง เร็วกว่ามาก แต่ coverage อาจไม่ครบเท่า NVD ในบาง ecosystem&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;พอทำสองข้อนี้ เวลารวมของเลเยอร์นี้ลดจาก ~6 นาที เหลือประมาณ 40 วินาที นี่แหละคือจุดที่กู้เวลากลับมาได้เยอะสุดในทั้ง pipeline&lt;/p&gt;

&lt;h2&gt;
  
  
  เลเยอร์ที่ 4: Trivy — สแกน container image ก่อน push ขึ้น registry
&lt;/h2&gt;

&lt;p&gt;เลเยอร์สุดท้ายอยู่หลัง build image เสร็จ สแกนทั้ง OS package และ language-specific dependency ที่ฝังอยู่ใน image เกณฑ์ที่ทีมเราใช้คือ:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRITICAL/HIGH ที่มี fix แล้ว&lt;/strong&gt; → block ทันที ห้าม push&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRITICAL/HIGH ที่ยังไม่มี fix&lt;/strong&gt; → ให้ผ่านได้แต่ต้องมี ticket track ไว้ ห้ามเงียบหาย&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MEDIUM ลงไป&lt;/strong&gt; → ไม่ block แต่รวมอยู่ใน weekly report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;จุดสำคัญคือต้อง set exit-code ให้แยกตาม severity ไม่ใช่ fail ทุกอย่างเป็นก้อนเดียว ไม่งั้น dev จะเจอ pipeline แดงจาก MEDIUM vulnerability ที่ยังไม่มี fix ด้วยซ้ำ แล้วก็เริ่มเบื่อ ignore ทั้งกระดาน&lt;/p&gt;

&lt;h2&gt;
  
  
  ตัวอย่าง Jenkinsfile แบบย่อ
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;
  &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Secret scan'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'gitleaks detect --source . --exit-code 1'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SAST'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'semgrep --config=p/security-audit --error'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SCA'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;parallel&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'OWASP DC'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'dependency-check.sh --project myapp --scan . --nvdApiKey $NVD_KEY'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'OSV'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'osv-scanner -r .'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Build image'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'docker build -t myapp:$BUILD_NUMBER .'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Image scan'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:$BUILD_NUMBER'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จุดที่ทำให้เร็วขึ้นเยอะคือเอา SCA (OWASP DC กับ OSV) มารันแบบ &lt;code&gt;parallel&lt;/code&gt; กัน แทนที่จะรันเรียงต่อกัน เพราะสองตัวนี้ไม่มี dependency ต่อกันเลย&lt;/p&gt;

&lt;h2&gt;
  
  
  บทเรียนที่แพงที่สุดจากเรื่องนี้
&lt;/h2&gt;

&lt;p&gt;ตอนที่ pipeline ช้า 18 นาทีนั่น ปัญหาจริงๆ ไม่ได้อยู่ที่ tool scan ช้า แต่อยู่ที่เรารันทุกอย่างแบบ sequential ทั้งที่ Gitleaks กับ Semgrep ไม่เกี่ยวกันเลย รันคู่กันได้สบายๆ พอจัดใหม่เป็น parallel ตาม dependency จริง เวลารวมลดลงมากกว่าครึ่งโดยไม่ต้องปิด scan ตัวไหนทิ้งสักตัว&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist สรุปเลเยอร์ทั้งหมด
&lt;/h2&gt;

&lt;p&gt;ลำดับที่ทีมเราใช้จริงตอนนี้ (บาง stage รันขนานกันได้ตามที่บอกไปข้างบน):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bw1vzd35vx2po7abouq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bw1vzd35vx2po7abouq.png" alt=" " width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ถ้าใครกำลังเริ่มวาง pipeline ใหม่ ลองเริ่มจาก Gitleaks กับ Trivy ก่อนสองตัวนี้ effort ต่ำ ผลตอบแทนไว แล้วค่อยๆ เพิ่ม Semgrep กับ SCA ทีหลังได้ ไม่ต้องรีบทำครบ 4 ชั้นตั้งแต่วันแรก&lt;/p&gt;

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