Semgrep คืออะไร
Semgrep เป็นเครื่องมือ Static Analysis แบบ Open-source ที่สแกนหาช่องโหว่ (Vulnerabilities), Bug และ Anti-patterns ใน Source Code โดยไม่ต้องรัน Application จุดเด่นคือเขียน Rules ง่ายด้วย Pattern Matching ที่คล้ายกับ Code จริง ไม่ต้องเรียนรู้ Abstract Syntax Tree (AST) ทำงานเร็วมากเพราะวิเคราะห์แบบ Intra-file
สำหรับ SRE, Semgrep ช่วยป้องกัน Security Incidents ตั้งแต่ Development Phase โดยสแกนหา SQL Injection, XSS, Hardcoded Secrets, Insecure Configuration และ Anti-patterns อื่นๆ ก่อนที่ Code จะถูก Deploy ลด Mean Time to Detection (MTTD) ให้เป็นศูนย์สำหรับ Known Vulnerability Patterns
ติดตั้งและใช้งาน Semgrep
# ติดตั้ง Semgrep
pip install semgrep
# หรือใช้ Homebrew (macOS)
brew install semgrep
# หรือใช้ Docker
docker pull semgrep/semgrep
# === สแกนด้วย Rules สำเร็จรูป ===
# สแกนด้วย Registry Rules (แนะนำ)
semgrep --config auto .
# สแกนเฉพาะ Security Rules
semgrep --config p/security-audit .
# สแกนเฉพาะภาษา
semgrep --config p/python .
semgrep --config p/javascript .
semgrep --config p/golang .
# สแกนด้วย OWASP Top 10 Rules
semgrep --config p/owasp-top-ten .
# === สแกนด้วย Custom Rules ===
semgrep --config my-rules/ .
# Output Formats
semgrep --config auto --json . # JSON
semgrep --config auto --sarif . # SARIF (GitHub)
semgrep --config auto --junit-xml . # JUnit XML
# === โครงสร้าง Rules Directory ===
semgrep-rules/
├── security/
│ ├── sql-injection.yml
│ ├── xss.yml
│ ├── hardcoded-secrets.yml
│ └── insecure-crypto.yml
├── reliability/
│ ├── error-handling.yml
│ ├── resource-leak.yml
│ └── race-condition.yml
├── best-practices/
│ ├── logging.yml
│ └── config.yml
└── .semgrep.yml # Project Config
# .semgrep.yml — Project Configuration
# paths:
# include:
# - src/
# - lib/
# exclude:
# - tests/
# - node_modules/
# - vendor/
Custom Semgrep Rules
# security/sql-injection.yml — ตรวจจับ SQL Injection
rules:
- id: python-sql-injection
patterns:
- pattern: |
cursor.execute($QUERY % $VAR)
- pattern-not: |
cursor.execute($QUERY, $PARAMS)
message: |
SQL Injection detected. ใช้ Parameterized Query แทน String Formatting
แก้: cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
languages: [python]
severity: ERROR
metadata:
cwe: ["CWE-89"]
owasp: ["A03:2021"]
category: security
confidence: HIGH
- id: python-sql-injection-fstring
pattern: |
cursor.execute(f"...{$VAR}...")
message: |
SQL Injection via f-string. ห้ามใช้ f-string กับ SQL Query
แก้: ใช้ Parameterized Query cursor.execute("... %s ...", (var,))
languages: [python]
severity: ERROR
metadata:
cwe: ["CWE-89"]
- id: js-sql-injection
patterns:
- pattern: |
$DB.query(`... ...`)
- pattern-not: |
$DB.query($QUERY, $PARAMS)
message: |
SQL Injection in template literal. ใช้ Parameterized Query
แก้: db.query("SELECT * FROM users WHERE id = $1", [userId])
languages: [javascript, typescript]
severity: ERROR
# security/hardcoded-secrets.yml — ตรวจจับ Hardcoded Secrets
rules:
- id: hardcoded-api-key
patterns:
- pattern-regex: |
(?i)(api[_-]?key|api[_-]?secret|access[_-]?token)\s*[:=]\s*['"][a-zA-Z0-9_\-]{20,}['"]
message: |
Hardcoded API Key detected. ใช้ Environment Variables แทน
แก้: api_key = os.environ["API_KEY"]
languages: [python, javascript, typescript, go, java]
severity: ERROR
metadata:
cwe: ["CWE-798"]
- id: hardcoded-password
patterns:
- pattern-regex: |
(?i)password\s*[:=]\s*['"][^'"]{8,}['"]
- pattern-not-regex: |
(?i)password\s*[:=]\s*['"](\$\{|os\.environ|process\.env|placeholder|example|changeme)
message: |
Hardcoded password detected. ใช้ Secret Manager หรือ Environment Variables
languages: [python, javascript, typescript]
severity: ERROR
# reliability/error-handling.yml — ตรวจจับ Error Handling ที่ไม่ดี
rules:
- id: python-bare-except
pattern: |
try:
...
except:
...
message: |
Bare except catches all exceptions including SystemExit and KeyboardInterrupt
แก้: except Exception as e: หรือ except SpecificException as e:
languages: [python]
severity: WARNING
metadata:
category: reliability
- id: python-pass-in-except
pattern: |
try:
...
except $E:
pass
message: |
Silent exception handling. อย่างน้อยต้อง Log error
แก้: except Exception as e: logger.error(f"Error: {e}")
languages: [python]
severity: WARNING
- id: go-ignored-error
pattern: |
$VAR, _ = $FUNC(...)
message: |
Error ignored. ต้อง Handle error ใน Go
แก้: result, err := func(); if err != nil { return err }
languages: [go]
severity: WARNING
CI/CD Integration
# .github/workflows/semgrep.yml — GitHub Actions
name: Semgrep Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
run: |
semgrep ci \
--config auto \
--config ./semgrep-rules/ \
--sarif --output=semgrep.sarif \
--json --output=semgrep.json \
--metrics=off
env:
SEMGREP_APP_TOKEN: }
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
- name: Check Critical Findings
if: always()
run: |
CRITICAL=$(cat semgrep.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
critical = [r for r in data.get('results', [])
if r.get('extra', {}).get('severity') == 'ERROR']
print(len(critical))
")
echo "Critical findings: $CRITICAL"
if [ "$CRITICAL" -gt 0 ]; then
echo "CRITICAL vulnerabilities found. Blocking merge."
exit 1
fi
---
# .gitlab-ci.yml — GitLab CI
semgrep:
image: semgrep/semgrep
stage: test
script:
- semgrep ci --config auto --config ./semgrep-rules/ --json
rules:
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_BRANCH == "main"
allow_failure: false
---
# CircleCI Config
version: 2.1
jobs:
semgrep-scan:
docker:
- image: semgrep/semgrep
steps:
- checkout
- run:
name: Semgrep Scan
command: |
semgrep ci \
--config auto \
--config ./semgrep-rules/ \
--json --output=/tmp/semgrep-results.json
- store_artifacts:
path: /tmp/semgrep-results.json
Security SLO สำหรับ SRE
# security_slo_monitor.py — Monitor Security SLO
import json
import time
from datetime import datetime, timedelta
from dataclasses import dataclass
@dataclass
class SecuritySLO:
name: str
target: float # เป็น % หรือ จำนวน
measurement: str # วิธีวัด
window: str # ช่วงเวลา
current_value: float = 0.0
class SecuritySLOMonitor:
"""Monitor Security SLOs สำหรับ SRE"""
def __init__(self):
self.slos = [
SecuritySLO(
"Critical Vulnerability MTTR",
target=24, # ชั่วโมง
measurement="Mean time to remediate Critical findings",
window="30 days",
),
SecuritySLO(
"High Vulnerability MTTR",
target=72, # ชั่วโมง
measurement="Mean time to remediate High findings",
window="30 days",
),
SecuritySLO(
"Scan Coverage",
target=100, # %
measurement="% of repos with Semgrep enabled",
window="continuous",
),
SecuritySLO(
"False Positive Rate",
target=10, # % (ต่ำกว่า)
measurement="% of findings marked as false positive",
window="30 days",
),
SecuritySLO(
"Pre-deploy Block Rate",
target=100, # %
measurement="% of Critical findings blocked before deploy",
window="continuous",
),
]
def check_slos(self, scan_results_path):
"""ตรวจสอบ SLO Status"""
with open(scan_results_path) as f:
results = json.load(f)
findings = results.get("results", [])
critical = [f for f in findings
if f.get("extra", {}).get("severity") == "ERROR"]
high = [f for f in findings
if f.get("extra", {}).get("severity") == "WARNING"]
print("=== Security SLO Dashboard ===")
print(f"Total Findings: {len(findings)}")
print(f"Critical: {len(critical)}")
print(f"High: {len(high)}")
print(f"Timestamp: {datetime.now().isoformat()}")
print(f"\n{'SLO':<35} {'Target':<12} {'Current':<12} {'Status'}")
print("-" * 75)
for slo in self.slos:
status = "PASS" if slo.current_value <= slo.target else "FAIL"
emoji = "OK" if status == "PASS" else "BREACH"
print(f"{slo.name:<35} {slo.target:<12} "
f"{slo.current_value:<12} {emoji}")
# Error Budget
breached = sum(1 for s in self.slos
if s.current_value > s.target)
print(f"\nSLO Status: {len(self.slos) - breached}/{len(self.slos)} passing")
if breached > 0:
print("ACTION REQUIRED: Security SLO breach detected")
print("Recommendation: Pause feature work, focus on security fixes")
monitor = SecuritySLOMonitor()
# monitor.check_slos("semgrep-results.json")
Best Practices
- เริ่มจาก Auto Config: ใช้ semgrep --config auto ก่อน แล้วค่อยเพิ่ม Custom Rules ตาม Codebase
- Block Critical ใน CI: ตั้ง CI ให้ Fail เมื่อพบ Critical/ERROR Findings ปล่อย Warning ผ่านแต่ Track
- ลด False Positives: ใช้ nosemgrep Comment สำหรับ Known False Positives, เขียน Rules ที่ Specific ใช้ pattern-not สำหรับ Exception
- Custom Rules: เขียน Rules สำหรับ Internal Libraries และ Patterns เฉพาะของทีม เช่น ห้ามใช้ Deprecated Functions
- Pre-commit Hook: รัน Semgrep ใน Pre-commit Hook ให้ Developer เห็นปัญหาทันที
- Security SLOs: ตั้ง SLO สำหรับ MTTR ของ Critical Findings, Scan Coverage และ False Positive Rate
- Dashboard: สร้าง Dashboard แสดง Findings Trend, MTTR, SLO Status ให้ทีมเห็น
Semgrep คืออะไร
Semgrep เป็นเครื่องมือ Static Analysis Open-source สแกนหาช่องโหว่ Bug และ Anti-patterns ใน Source Code รองรับกว่า 30 ภาษา เขียน Rules ง่ายด้วย Pattern Matching ทำงานเร็ว ใช้ใน CI/CD Pipeline ป้องกัน Vulnerable Code ก่อน Deploy
SAST ต่างจาก DAST อย่างไร
SAST สแกน Source Code ไม่ต้องรัน Application หา Bug ตั้งแต่เขียน Code เร็วแต่อาจมี False Positive DAST ทดสอบ Application ที่รันอยู่ หา Vulnerability จาก Runtime แม่นกว่าแต่ช้ากว่า ใช้ทั้งคู่เพื่อครอบคลุมมากที่สุด
Semgrep เกี่ยวข้องกับ SRE อย่างไร
SRE ดูแล Reliability ของระบบ Security Vulnerabilities เป็นสาเหตุหลักของ Incidents Semgrep ป้องกัน Security Issues ตั้งแต่ Development ลด Incidents ตั้ง Security SLO เช่น Critical Findings ต้องแก้ภายใน 24 ชั่วโมง Monitor ด้วย Dashboard
วิธีเขียน Semgrep Rules ทำอย่างไร
เขียนด้วย YAML กำหนด Pattern ที่ต้องการตรวจจับ ใช้ Metavariables ($VAR) จับค่าที่เปลี่ยนได้ ใช้ pattern-not สำหรับ Exception กำหนด Severity และ Message อธิบายปัญหาพร้อมวิธีแก้ ทดสอบด้วย semgrep --test
สรุป
Semgrep เป็นเครื่องมือ SAST ที่เหมาะกับ SRE Workflow เขียน Rules ง่าย ทำงานเร็ว รวมเข้า CI/CD Pipeline ได้ทุกระบบ Block Critical Vulnerabilities ก่อน Deploy ตั้ง Security SLOs สำหรับ MTTR, Scan Coverage และ False Positive Rate สิ่งสำคัญคือเริ่มจาก Auto Config แล้วค่อยเพิ่ม Custom Rules ตาม Codebase ลด False Positives และ Track Findings อย่างสม่ำเสมอ
