Context
v2.8.0 Phase .a 軌道二 Policy-as-Code 落地 7 個新 lint,但全部都在 Python / JSX / shell 層。Container / Kubernetes manifest 層完全沒 codified lint coverage:
- Dockerfile:build 完整性 + over-permissive COPY + 缺
.dockerignore
- Helm template:危險環境變數(
*ALLOW_EMPTY_PASSWORD / INSECURE_*)
- Helm template:擴大的 K8s
securityContext.capabilities.add
- Helm values:secrets hardcoded(
password: "..."/token: "...")
- K8s manifest:
hostNetwork: true / hostPID: true / privileged container
v2.8.0 closure session(2026-05-12)owner 本機 kind setup 嘗試暴露此 gap:
- Dockerfile silent-break since v2.7.0(cmd/+internal/ subdir added but COPY rule not updated)— no lint caught it
- mariadb-instance deployment 加
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "yes" 修 local startup — 沒 lint 攔本 commit-time
- 3 個 capabilities(CHOWN/SETGID/SETUID)加進來 — 雖然 mariadb 真實需要,但沒被 review 也無 codified policy
Strategic shift(v2 rewrite, 2026-05-12)
過去 v2.8.0 之前的 lint 路線是「DIY check_*.py 看到就打」(50 個 lint 之證)。本票作為全新 IaC SAST 層,試點 hybrid policy:
業界工具當 engine(hadolint / kube-linter / trivy config)+ Vibe wrapper 加 project-specific 規則(rationale 註解強制 / 雙語錯誤 / dev-rules 連結)。
選擇理由:IaC 規則(runAsNonRoot / hostNetwork / capabilities)是業界標準(CIS-aligned),重寫成 Python 浪費 + 漏 edge case + 稽核難自證。Vibe-specific(如「capabilities.add 必有 rationale 註解」)open-source 無內建,由 wrapper 補。
不回頭遷現有 50 個 lint — 已能跑、有測試,改的 risk > 收益。讓自然汰換發生。本票成功後 hybrid policy 寫進 dev-rules.md,未來新 lint 都按這套。
Pre-flight findings(v2 rewrite 期間發現)
| Issue 原 prose / Gemini 推測 |
現實 |
影響 |
「check_helm_template_security.py AST scan templates」 |
4 個 deployment.yaml 平均 14-43 個 {{ }} Go template,純 YAML parser 必崩 |
改 雙模式:text regex scan source + render-then-lint via helm template | kube-linter |
「每個 Dockerfile 必須有對應 .dockerignore」 |
6 個 Dockerfile,只 1 個有 .dockerignore(da-tools) |
AC 改 fix-then-enforce:先補 5 個檔案再啟動 enforce mode |
| Gemini 假設 mariadb 是 third-party |
helm/mariadb-instance/ 是 in-house Helm chart |
Framing 改成「Capability-elevation 必須 explicit rationale」,in-house 也適用 |
「Layer 3 掃 values*.yaml」 |
mariadb-instance 把 secret 放在 templates/secret.yaml;da-portal 有 values-tier{1,2}.yaml 多環境 |
Scope 擴:values*.yaml + templates/secret*.yaml |
| Trivy 已用但 informational |
release.yaml line 97 註解:「Security scan (post-push, informational)」— 4 個 image 都這樣 |
新 IaC SAST Critical → block PR,CVE image scan 維持 informational |
Acceptance criteria
AC 0 — Tool engine 採用
AC 1 — Dockerfile (Layer 1)
AC 2 — Helm template security(Layer 2,雙模式)
AC 3 — Helm values secret-shape(Layer 3)
AC 4 — K8s manifest security(Layer 4,純 kube-linter)
AC 5 — CI integration
AC 6 — Doc-as-Code sync(dev-rules #4)
AC 7 — Baseline acceptance
Dependencies
Out of scope
- ❌ 回頭遷移現有 50 個 check_*.py — 已 stable,risk > reward。讓自然汰換發生
- ❌ Runtime admission webhook(OPA Gatekeeper / Kyverno)— production cluster 責任,不是 SAST
- ❌ Image CVE scanning policy 變更 — 既有 trivy 維持 informational
- ❌ Custom CIS benchmark scoring — kube-linter 內建已對齊 CIS,不重做
- ❌ Cosign / SBOM 整合 release.yaml 變動 — 已存在,不本票範圍
Sizing(v2 修訂)
| AC |
估時 |
主要工作 |
| AC 0 tool engine 採用 |
0.2w |
Dev Container 安裝 + version pin + config 初稿 |
AC 1 Dockerfile + 5 個 .dockerignore 補齊 + Vibe wrapper HEALTHCHECK 規則 |
0.4w |
5 個 baseline 補檔 + hadolint wire-up |
| AC 2 Helm 雙模式(text + render-lint)+ rationale wrapper |
0.7w |
最重,雙 mode 設計 + kube-linter wrap |
AC 3 secret-shape + OAuth ${VAR} 排除 + 雙 scope(values + secret templates) |
0.4w |
False-positive 消除最費時 |
| AC 4 kube-linter wrapper(stub mode) |
0.2w |
trigger condition 設計,啟用先 stub |
| AC 5 CI integration + branch protection |
0.2w |
required check + severity mapping |
| AC 6 Doc-as-Code sync |
0.3w |
hybrid policy 寫進 dev-rules + 5 處 doc |
| AC 7 Baseline acceptance + 0 Critical 對齊 |
0.3w |
mariadb rationale 註解 + High 列管 |
| 總計 |
~2.5w |
原估 1-1.5w,rewrite 後實際面積較大 |
Why v2.9.0
References
- v2.8.0 closure session 2026-05-12 audit 發現
- v2.8.0 Policy-as-Code 8 提案(Phase .a 軌道二 baseline)
- 相關 issue: #447 Dockerfile precise allowlist (v2.8.1)
- 配套 issue: #445 Secret scan multi-layer (v2.8.1)
- 配套 epic: #449 Try-local onboarding (v2.9.0)
- 既有 lint patterns: #382 codename-leak / #387 (b) class lints
- Pre-flight finding source:
helm/*/templates/*.yaml Go template token count + .dockerignore glob (1/6 covered) + .pre-commit-config.yaml (0 IaC tool) + release.yaml trivy informational scan
Rewritten v2 (2026-05-12) incorporating Claude + Gemini three-round review — focusing on hybrid policy (open-source engine + Vibe wrapper) replacing whack-a-mole DIY default, Helm dual-mode scanning (text + render-then-lint), severity → action mapping (Critical block / High warn), explicit false-positive escape mechanism, .dockerignore fix-then-enforce with baseline content, and v2.9.0 P2 independent epic positioning.
Context
v2.8.0 Phase .a 軌道二 Policy-as-Code 落地 7 個新 lint,但全部都在 Python / JSX / shell 層。Container / Kubernetes manifest 層完全沒 codified lint coverage:
.dockerignore*ALLOW_EMPTY_PASSWORD/INSECURE_*)securityContext.capabilities.addpassword: "..."/token: "...")hostNetwork: true/hostPID: true/ privileged containerv2.8.0 closure session(2026-05-12)owner 本機 kind setup 嘗試暴露此 gap:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "yes"修 local startup — 沒 lint 攔本 commit-timeStrategic shift(v2 rewrite, 2026-05-12)
過去 v2.8.0 之前的 lint 路線是「DIY check_*.py 看到就打」(50 個 lint 之證)。本票作為全新 IaC SAST 層,試點 hybrid policy:
選擇理由:IaC 規則(runAsNonRoot / hostNetwork / capabilities)是業界標準(CIS-aligned),重寫成 Python 浪費 + 漏 edge case + 稽核難自證。Vibe-specific(如「
capabilities.add必有 rationale 註解」)open-source 無內建,由 wrapper 補。不回頭遷現有 50 個 lint — 已能跑、有測試,改的 risk > 收益。讓自然汰換發生。本票成功後 hybrid policy 寫進
dev-rules.md,未來新 lint 都按這套。Pre-flight findings(v2 rewrite 期間發現)
check_helm_template_security.pyAST scan templates」{{ }}Go template,純 YAML parser 必崩helm template | kube-linter.dockerignore」.dockerignore(da-tools)helm/mariadb-instance/是 in-house Helm chartvalues*.yaml」templates/secret.yaml;da-portal 有values-tier{1,2}.yaml多環境values*.yaml+templates/secret*.yamlAcceptance criteria
AC 0 — Tool engine 採用
hadolintv2.12.0+(containerized 跑,無需 host install)kube-linterv0.7+(Go binary,Dev Container 內 install)trivy config模式(既有 trivy-action 同 toolchain extension,0 新依賴)scripts/tools/lint/check_iac_vibe_rules.py— engine 跑完聚合 output、加 Vibe-specific 規則、雙語錯誤訊息AC 1 — Dockerfile (Layer 1)
.hadolint.yaml,rule set 包含:COPY <src>不允許*/.單獨:latesttagclean--no-cacheHEALTHCHECK或# rationale: K8s probes註解(distroless 例外明列).dockerignorefix-then-enforce:.dockerignore(da-portal / tenant-api / threshold-exporter / e2e-bench/driver / e2e-bench/receiver).git/、tests/、scripts/、docs/、*.md、.github/、*.log、.env*AC 2 — Helm template security(Layer 2,雙模式)
{{ if }}包住):*ALLOW_EMPTY_PASSWORD*: "yes"/*ALLOW_EMPTY_*: "true"/INSECURE_*: "true"helm template <chart>→ pipe tokube-linter lint --format jsonrun-as-non-root/privileged-container/host-network/host-pid/no-read-only-root-fs/unset-cpu-requirements等values.yaml+values-tier*.yaml各一次securityContext.capabilities.add: [...]必須 同行或上一行有# rationale: <reason>註解runAsNonRoot: false/ ALLOW_EMPTY_PASSWORD)→ BLOCK PRAC 3 — Helm values secret-shape(Layer 3)
scripts/tools/lint/check_helm_values_secrets.py(Vibe wrapper,無對應 open-source engine)helm/*/values*.yaml+helm/*/templates/secret*.yamlpassword:/token:/apiKey:/secret:/clientSecret:後接非空字串\${[A-Z0-9_]+}— 環境變數插值(如${OAUTH_CLIENT_SECRET})""— 空字串(must-be-set 標記){{ .Values.* }}— Helm template reference<placeholder>/<changeme>— 文件範例AC 4 — K8s manifest security(Layer 4,純 kube-linter)
k8s/**/*.yamlraw manifest(非 Helm output),跑kube-linter lint k8s/k8s/**/*.yaml出現第一個檔案 → CI 自動 enableAC 5 — CI integration
requiredcheck)AC 6 — Doc-as-Code sync(dev-rules #4)
docs/internal/dev-rules.md§security 新增「container/k8s SAST 4-layer」條目 + hybrid policy 段(adopt-then-wrap 取代 DIY-only)CLAUDE.md架構速查段補 Security gate L0/L1/L2/L3 + IaC SAST(與 A-14: Secret scan dual-layer + release-preflight digest verification (deferred from v2.8.0) #445 整合)docs/internal/doc-map.md新增條目:.hadolint.yaml/check_iac_vibe_rules.py/secret-leak-remediation-sop.md(如 A-14: Secret scan dual-layer + release-preflight digest verification (deferred from v2.8.0) #445 已建)docs/internal/tool-map.md加 hadolint / kube-linter / trivy config 條目CHANGELOG.md### Added:「Container/k8s SAST 4-layer lint (hadolint + kube-linter + trivy config + Vibe wrapper)」CHANGELOG.md### Changed:「Lint adoption policy: open-source engine + Vibe wrapper (was DIY-only)」AC 7 — Baseline acceptance
mainHEAD → 0 Critical(必須),High 數量記錄為 baselinedocs/internal/iac-lint-baseline.md列入 + rationale + 預計修補時程# rationale: mariadb-server requires for file ownership註解Dependencies
Out of scope
Sizing(v2 修訂)
.dockerignore補齊 + Vibe wrapper HEALTHCHECK 規則${VAR}排除 + 雙 scope(values + secret templates)Why v2.9.0
ALLOW_EMPTY_ROOT_PASSWORD是 local-only,不入 image / production manifest)References
helm/*/templates/*.yamlGo template token count +.dockerignoreglob (1/6 covered) +.pre-commit-config.yaml(0 IaC tool) +release.yamltrivy informational scanRewritten v2 (2026-05-12) incorporating Claude + Gemini three-round review — focusing on hybrid policy (open-source engine + Vibe wrapper) replacing whack-a-mole DIY default, Helm dual-mode scanning (text + render-then-lint), severity → action mapping (Critical block / High warn), explicit false-positive escape mechanism, .dockerignore fix-then-enforce with baseline content, and v2.9.0 P2 independent epic positioning.