diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 0c5c69b..5894d73 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -1,7 +1,8 @@ # CAST — Deploy official website to GitHub Pages # -# Triggers on push to main (website/ changes). -# Serves website/ directory at https://castops.github.io/cast/ +# Triggers on push to main (website/ or docs/ changes). +# Builds HTML from Markdown sources, then serves website/ at +# https://castops.github.io/cast/ name: Deploy Website @@ -10,6 +11,7 @@ on: branches: [main] paths: - 'website/**' + - 'docs/**' workflow_dispatch: permissions: @@ -31,6 +33,17 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Markdown library + run: pip install markdown + + - name: Build docs HTML from Markdown + run: python scripts/build_docs.py + - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/.gitignore b/.gitignore index c8afffd..209365a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ dist/ build/ .eggs/ .gstack/ +website/docs/ +website/zh/docs/ diff --git a/docs/zh/plugin-guide.md b/docs/zh/plugin-guide.md new file mode 100644 index 0000000..9c2a92d --- /dev/null +++ b/docs/zh/plugin-guide.md @@ -0,0 +1,258 @@ +# 插件指南 — 扩展 CAST + +CAST 专为可扩展性而设计。任何能生成 SARIF 文件的安全工具都可以接入 CAST 门禁——无需 Fork 仓库。 + +## 插件工作原理 + +CAST 的门禁会评估**所有**名称匹配 `cast-sarif-*` 的制品。工作流中任何上传此命名规范制品的作业都会被自动纳入安全门禁评估。 + +``` +Your workflow +│ +├── cast-sast → uploads cast-sarif-sast ─┐ +├── cast-sca → uploads cast-sarif-sca │ Gate evaluates +├── cast-secrets → uploads cast-sarif-secrets │ ALL of these +│ │ +└── my-custom-tool → uploads cast-sarif-custom ──┘ ← plugin! +``` + +门禁作业(`cast-gate`)下载所有匹配 `cast-sarif-*` 的制品,并将每个文件传递给当前活跃的 OPA/conftest 策略进行评估。您的自定义工具发现结果与内置工具的发现结果处理方式完全相同。 + +--- + +## 添加自定义工具(GitHub Actions) + +### 第一步 — 运行工具并生成 SARIF 文件 + +任何能输出 SARIF 的工具均可使用。如果您的工具不原生支持 SARIF,可以编写一个简单的包装器(参见[编写 SARIF 包装器](#sarif-wrapper))。 + +```yaml +jobs: + my-custom-scan: + name: Custom Security Scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run my tool + run: | + my-security-tool --output=results.sarif --format=sarif . + + - name: Upload as CAST plugin + uses: actions/upload-artifact@v4 + with: + name: cast-sarif-custom # ← must start with cast-sarif- + path: results.sarif +``` + +### 第二步 — 完成 + +CAST 门禁作业将在下次运行时自动识别 `cast-sarif-custom`,无需修改门禁作业。 + +--- + +## 添加自定义工具(GitLab CI) + +```yaml +my-custom-scan: + stage: cast-scan + script: + - my-security-tool --output=results.sarif --format=sarif . + artifacts: + name: cast-sarif-custom # ← must start with cast-sarif- + paths: + - results.sarif + when: always +``` + +--- + +## 编写 SARIF 包装器 {#sarif-wrapper} + +如果您的工具不输出 SARIF,可以用一个简单的 Python 脚本进行封装: + +```python +#!/usr/bin/env python3 +"""Convert custom tool output to SARIF 2.1.0.""" + +import json +import subprocess +import sys + +def run_tool(): + result = subprocess.run( + ["my-tool", "--json", "."], + capture_output=True, text=True + ) + return json.loads(result.stdout) + +def to_sarif(findings): + results = [] + for f in findings: + level = "error" if f["severity"] == "CRITICAL" else "warning" + results.append({ + "ruleId": f["rule_id"], + "level": level, # "error" → CAST gate blocks on this + "message": {"text": f["message"]}, + "locations": [{ + "physicalLocation": { + "artifactLocation": {"uri": f["file"]}, + "region": {"startLine": f["line"]} + } + }] + }) + + return { + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{ + "tool": { + "driver": { + "name": "my-tool", + "version": "1.0.0", + "rules": [] + } + }, + "results": results + }] + } + +if __name__ == "__main__": + findings = run_tool() + sarif = to_sarif(findings) + with open("custom.sarif", "w") as f: + json.dump(sarif, f, indent=2) + print(f"Wrote {len(findings)} findings to custom.sarif") +``` + +### SARIF 严重性与门禁行为的对应关系 + +| SARIF `level` | `default` 策略 | `strict` 策略 | +|---------------|----------------|---------------| +| `error` | ❌ 阻断门禁 | ❌ 阻断门禁 | +| `warning` | ✅ 通过门禁 | ❌ 阻断门禁 | +| `note` | ✅ 通过门禁 | ✅ 通过门禁 | + +对于希望阻断合并的发现,将 `level` 设为 `"error"`;对于希望上报但不阻断的发现,将 `level` 设为 `"warning"`。 + +--- + +## 命名规范 + +插件制品名称必须遵循以下模式: + +``` +cast-sarif- +``` + +示例: + +- `cast-sarif-bandit` — Bandit Python 安全检查器 +- `cast-sarif-eslint-security` — ESLint 安全插件 +- `cast-sarif-checkov` — Terraform/IaC 扫描器 +- `cast-sarif-osv-scanner` — OSV 依赖扫描器 +- `cast-sarif-custom` — 任何您自建的工具 + +`` 部分将出现在门禁日志中,用于标识产生发现的工具。 + +--- + +## 针对插件的策略自定义 + +您可以编写 OPA 策略,对插件发现与内置工具发现采取不同处理方式。门禁将完整的 SARIF 文件传递给 conftest,因此您可以检查 `input.runs[_].tool.driver.name`。 + +```rego +package main + +import future.keywords.if +import future.keywords.in + +# Block on CRITICAL from any tool +deny[msg] if { + run := input.runs[_] + result := run.results[_] + result.level == "error" + msg := sprintf("[%s] CRITICAL: %s", [run.tool.driver.name, result.message.text]) +} + +# Block on HIGH from built-in tools only (not custom scanners) +deny[msg] if { + run := input.runs[_] + run.tool.driver.name in {"Semgrep", "Trivy"} + result := run.results[_] + result.level == "warning" + msg := sprintf("[%s] HIGH: %s", [run.tool.driver.name, result.message.text]) +} +``` + +完整的策略编写文档请参阅[策略参考](policy-reference.md)。 + +--- + +## 示例:添加 Bandit(Python 安全检查器) + +[Bandit](https://bandit.readthedocs.io) 原生支持 SARIF 输出。将其作为 CAST 插件添加: + +```yaml +bandit: + name: Bandit (Python Security) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install Bandit + run: pip install bandit[sarif] + - name: Run Bandit + run: bandit -r . -f sarif -o bandit.sarif || true + - name: Upload as CAST plugin + uses: actions/upload-artifact@v4 + if: always() + with: + name: cast-sarif-bandit + path: bandit.sarif +``` + +## 示例:添加 Checkov(基础设施即代码扫描器) + +```yaml +checkov: + name: Checkov (IaC Security) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run Checkov + uses: bridgecrewio/checkov-action@master + with: + output_format: sarif + output_file_path: checkov.sarif + soft_fail: true + - name: Upload as CAST plugin + uses: actions/upload-artifact@v4 + if: always() + with: + name: cast-sarif-checkov + path: checkov.sarif +``` + +--- + +## 故障排查 + +### 门禁未识别到插件的发现结果 + +检查制品名称。名称必须以 `cast-sarif-` 开头(区分大小写)。在 Actions 运行的"Artifacts"部分确认制品已成功上传。 + +### 插件发现未阻断门禁 + +检查您的 SARIF 是否对希望阻断的发现使用了 `level: "error"`。`level: "warning"` 在 `default` 策略下会通过。切换至 `CAST_POLICY=strict` 可对 warning 级别也进行阻断,或编写自定义策略。 + +### conftest 中出现 SARIF 验证错误 + +您的 SARIF 必须符合 SARIF 2.1.0 规范。使用以下命令进行验证: + +```bash +pip install sarif-tools +sarif summary results.sarif +``` diff --git a/scripts/build_docs.py b/scripts/build_docs.py new file mode 100644 index 0000000..153533f --- /dev/null +++ b/scripts/build_docs.py @@ -0,0 +1,546 @@ +#!/usr/bin/env python3 +"""Build documentation HTML from Markdown source files. + +Reads Markdown from docs/ and docs/zh/, wraps each page in the shared +site template, and writes HTML to website/docs/ and website/zh/docs/. + +Usage: + pip install markdown + python scripts/build_docs.py +""" + +import pathlib +import re +import sys + +try: + import markdown +except ImportError: + print("ERROR: 'markdown' package not installed. Run: pip install markdown", file=sys.stderr) + sys.exit(1) + +REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent +DOCS_DIR = REPO_ROOT / "docs" +WEBSITE_DIR = REPO_ROOT / "website" + +# --------------------------------------------------------------------------- +# Per-page metadata +# --------------------------------------------------------------------------- + +# slug -> (html_title, breadcrumb_label) +EN_PAGES: dict[str, tuple[str, str]] = { + "getting-started": ("Getting Started — CAST", "Getting Started"), + "cli-reference": ("CLI Reference — CAST", "CLI Reference"), + "pipeline-reference": ("Pipeline Reference — CAST", "Pipeline Reference"), + "policy-reference": ("Policy Reference — CAST", "Policy Reference"), + "dashboard-guide": ("Security Dashboard Guide — CAST","Security Dashboard Guide"), + "plugin-guide": ("Plugin Guide — CAST", "Plugin Guide"), + "gitlab-guide": ("GitLab CI Guide — CAST", "GitLab CI Guide"), +} + +ZH_PAGES: dict[str, tuple[str, str]] = { + "getting-started": ("快速入门 — CAST", "快速入门"), + "cli-reference": ("CLI 参考 — CAST", "CLI 参考"), + "pipeline-reference": ("流水线参考 — CAST", "流水线参考"), + "policy-reference": ("策略参考 — CAST", "策略参考"), + "dashboard-guide": ("安全看板指南 — CAST", "安全看板指南"), + "plugin-guide": ("插件指南 — CAST", "插件指南"), + "gitlab-guide": ("GitLab CI 指南 — CAST", "GitLab CI 指南"), +} + +# --------------------------------------------------------------------------- +# Sidebar structure (section_label, [(slug, link_label), ...]) +# --------------------------------------------------------------------------- + +EN_SIDEBAR = [ + ("Documentation", [ + ("getting-started", "Getting Started"), + ]), + ("Reference", [ + ("cli-reference", "CLI Reference"), + ("pipeline-reference","Pipeline Reference"), + ("policy-reference", "Policy Reference"), + ]), + ("Guides", [ + ("gitlab-guide", "GitLab CI"), + ("dashboard-guide", "Security Dashboard"), + ("plugin-guide", "Plugin Guide"), + ]), +] + +ZH_SIDEBAR = [ + ("文档", [ + ("getting-started", "快速入门"), + ]), + ("参考", [ + ("cli-reference", "CLI 参考"), + ("pipeline-reference","流水线参考"), + ("policy-reference", "策略参考"), + ]), + ("指南", [ + ("gitlab-guide", "GitLab CI"), + ("dashboard-guide", "安全看板"), + ("plugin-guide", "插件指南"), + ]), +] + +# --------------------------------------------------------------------------- +# Shared CSS (identical to the original hand-crafted pages) +# --------------------------------------------------------------------------- + +CSS = """\ + *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + + :root { + --bg: #0d1117; + --surface: #161b22; + --surface2: #1c2128; + --border: #30363d; + --text: #e6edf3; + --muted: #8b949e; + --green: #3fb950; + --red: #f85149; + --yellow: #d29922; + --blue: #58a6ff; + --purple: #bc8cff; + } + + body { + background: var(--bg); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 15px; + line-height: 1.6; + } + + a { color: var(--blue); text-decoration: none; } + a:hover { text-decoration: underline; } + + nav { + position: sticky; + top: 0; + z-index: 100; + background: rgba(13,17,23,0.85); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); + } + .nav-inner { + max-width: 1100px; + margin: 0 auto; + padding: 0 24px; + height: 60px; + display: flex; + align-items: center; + gap: 32px; + } + .nav-logo { + font-weight: 700; + font-size: 16px; + color: var(--text); + text-decoration: none; + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + } + .nav-links { + display: flex; + gap: 24px; + flex: 1; + } + .nav-links a { + font-size: 14px; + color: var(--muted); + text-decoration: none; + transition: color 0.15s; + } + .nav-links a:hover { color: var(--text); } + .nav-right { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; + } + .lang-switch { + display: flex; + align-items: center; + gap: 2px; + font-size: 13px; + border: 1px solid var(--border); + border-radius: 6px; + overflow: hidden; + } + .lang-switch a { + padding: 4px 10px; + color: var(--muted); + background: transparent; + transition: color 0.15s, background 0.15s; + text-decoration: none; + } + .lang-switch a:hover { color: var(--text); background: var(--surface); } + .lang-switch a.active { color: var(--text); background: var(--surface2); } + .btn-ghost { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 13px; + color: var(--text); + text-decoration: none; + background: transparent; + transition: border-color 0.15s, background 0.15s; + } + .btn-ghost:hover { background: var(--surface); border-color: var(--muted); text-decoration: none; } + + .doc-layout { + max-width: 1100px; + margin: 0 auto; + padding: 40px 24px 80px; + display: grid; + grid-template-columns: 240px 1fr; + gap: 48px; + align-items: start; + } + @media (max-width: 760px) { + .doc-layout { grid-template-columns: 1fr; } + .sidebar { display: none; } + } + + .sidebar { + position: sticky; + top: 80px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 20px 0; + } + .sidebar-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); + padding: 12px 16px 4px; + } + .sidebar a { + display: block; + padding: 6px 16px; + font-size: 14px; + color: var(--muted); + text-decoration: none; + border-left: 2px solid transparent; + transition: color 0.15s, border-color 0.15s; + } + .sidebar a:hover { color: var(--text); } + .sidebar a.active { color: var(--blue); border-left-color: var(--blue); background: rgba(88,166,255,0.05); } + + .doc-content { min-width: 0; } + .doc-content h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; } + .doc-content h2 { font-size: 20px; font-weight: 600; margin: 40px 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border); } + .doc-content h3 { font-size: 16px; font-weight: 600; margin: 28px 0 10px; color: var(--text); } + .doc-content h4 { font-size: 14px; font-weight: 600; margin: 20px 0 8px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; } + .doc-content p { margin-bottom: 16px; line-height: 1.7; } + .doc-content ul, .doc-content ol { margin: 0 0 16px 20px; } + .doc-content li { margin-bottom: 6px; line-height: 1.6; } + .doc-content code { + font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace; + font-size: 13px; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 4px; + padding: 2px 6px; + color: var(--purple); + } + .doc-content pre { + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 8px; + padding: 16px 20px; + overflow-x: auto; + margin-bottom: 20px; + } + .doc-content pre code { + background: none; + border: none; + padding: 0; + color: var(--text); + font-size: 13px; + line-height: 1.6; + } + .doc-content table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 14px; } + .doc-content th { background: var(--surface2); text-align: left; padding: 10px 14px; font-weight: 600; border-bottom: 2px solid var(--border); } + .doc-content td { padding: 10px 14px; border-bottom: 1px solid var(--border); } + .doc-content tr:last-child td { border-bottom: none; } + .doc-content hr { border: none; border-top: 1px solid var(--border); margin: 40px 0; } + .doc-content blockquote { + border-left: 3px solid var(--yellow); + background: rgba(210,153,34,0.08); + border-radius: 0 6px 6px 0; + padding: 12px 16px; + margin: 0 0 16px; + color: var(--muted); + font-size: 14px; + } + .doc-content a { color: var(--blue); } + .doc-content a:hover { text-decoration: underline; } + .doc-content strong { font-weight: 600; color: var(--text); } + + .breadcrumb { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: var(--muted); + margin-bottom: 24px; + } + .breadcrumb a { color: var(--muted); text-decoration: none; } + .breadcrumb a:hover { color: var(--text); } + .breadcrumb .sep { color: var(--border); } + + footer { + border-top: 1px solid var(--border); + padding: 32px 24px; + text-align: center; + font-size: 13px; + color: var(--muted); + } + footer a { color: var(--muted); text-decoration: none; } + footer a:hover { color: var(--text); }""" + +GITHUB_SVG = ( + '' + '' +) + +# --------------------------------------------------------------------------- +# Template helpers +# --------------------------------------------------------------------------- + + +def _render_sidebar(sidebar: list, active_slug: str) -> str: + parts: list[str] = [' ") + return "\n".join(parts) + + +def _render_nav_en(slug: str) -> str: + return f"""\ + """ + + +def _render_nav_zh(slug: str) -> str: + return f"""\ + """ + + +def _render_breadcrumb_en(label: str) -> str: + return f"""\ + """ + + +def _render_breadcrumb_zh(label: str) -> str: + return f"""\ + """ + + +def _md_to_html(md_text: str) -> str: + """Convert Markdown text to an HTML fragment.""" + extensions = ["tables", "fenced_code"] + body = markdown.markdown(md_text, extensions=extensions) + # Rewrite bare *.md links to *.html (internal cross-doc links) + body = re.sub(r'href="([^"#/][^"]*?)\.md"', r'href="\1.html"', body) + return body + + +def render_page( + *, + lang: str, + slug: str, + title: str, + breadcrumb_label: str, + sidebar: list, + body_html: str, +) -> str: + html_lang = "zh-CN" if lang == "zh" else "en" + nav = _render_nav_zh(slug) if lang == "zh" else _render_nav_en(slug) + breadcrumb = ( + _render_breadcrumb_zh(breadcrumb_label) + if lang == "zh" + else _render_breadcrumb_en(breadcrumb_label) + ) + sidebar_html = _render_sidebar(sidebar, slug) + + return f"""\ + + + + + + {title} + + + + +{nav} + +
+ +{sidebar_html} + +
+{breadcrumb} + +{body_html} +
+ +
+ + + + + +""" + + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- + + +def build_lang( + *, + lang: str, + source_dir: pathlib.Path, + output_dir: pathlib.Path, + pages: dict[str, tuple[str, str]], + sidebar: list, +) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + + built = 0 + for slug, (title, breadcrumb_label) in pages.items(): + md_path = source_dir / f"{slug}.md" + if not md_path.exists(): + print(f" WARNING: {md_path} not found — skipping", file=sys.stderr) + continue + + md_text = md_path.read_text(encoding="utf-8") + body_html = _md_to_html(md_text) + + html = render_page( + lang=lang, + slug=slug, + title=title, + breadcrumb_label=breadcrumb_label, + sidebar=sidebar, + body_html=body_html, + ) + + out_path = output_dir / f"{slug}.html" + out_path.write_text(html, encoding="utf-8") + print(f" wrote {out_path.relative_to(REPO_ROOT)}") + built += 1 + + print(f" {built} page(s) built for lang={lang!r}") + + +def main() -> None: + print("Building English docs …") + build_lang( + lang="en", + source_dir=DOCS_DIR, + output_dir=WEBSITE_DIR / "docs", + pages=EN_PAGES, + sidebar=EN_SIDEBAR, + ) + + print("Building Chinese docs …") + build_lang( + lang="zh", + source_dir=DOCS_DIR / "zh", + output_dir=WEBSITE_DIR / "zh" / "docs", + pages=ZH_PAGES, + sidebar=ZH_SIDEBAR, + ) + + print("Done.") + + +if __name__ == "__main__": + main() diff --git a/website/docs/cli-reference.html b/website/docs/cli-reference.html deleted file mode 100644 index db0b888..0000000 --- a/website/docs/cli-reference.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - CLI Reference — CAST - - - - - - -
- - - -
- - -

CLI Reference

-

Complete reference for the cast command-line interface.

- -

Table of Contents

- - -
- -

Installation

-
pip install castops
-

Requirements: Python 3.9 or higher.

-

After installation, the cast command is available in your PATH.

-
cast --help
- -
- -

Global Options

-
Options:
-  --help    Show help message and exit.
-

Running cast with no arguments displays the help message.

- -
- -

Commands

- -

cast init

-

Initialize a DevSecOps pipeline in the current directory.

- -

Synopsis

-
cast init [OPTIONS]
- -

Description

-

Writes a production-ready GitHub Actions workflow to .github/workflows/devsecops.yml.

-

The command:

-
    -
  1. Detects your project type from marker files (or uses --type if provided)
  2. -
  3. Checks if a workflow file already exists
  4. -
  5. Reads the embedded template for your project type
  6. -
  7. Creates .github/workflows/ if it does not exist
  8. -
  9. Writes the workflow file
  10. -
- -

Options

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OptionShortTypeDefaultDescription
--force-fflagfalseOverwrite an existing workflow file
--type-tstring(auto-detect)Project type: python
--helpShow help and exit
- -

Examples

-

Auto-detect project type:

-
cd my-python-project
-cast init
-

Output:

-
╭──────────────────────────────────────────────────╮
-│ CAST — CI/CD Automation & Security Toolkit        │
-╰──────────────────────────────────────────────────╯
-Detected project type: python
-Downloading template... done
-
-✓ Created .github/workflows/devsecops.yml
-
-Commit and push to activate your DevSecOps pipeline:
-  git add .github/workflows/devsecops.yml
-  git commit -m 'ci: add CAST DevSecOps pipeline'
-  git push
- -

Specify project type explicitly:

-
cast init --type python
- -

Overwrite an existing workflow:

-
cast init --force
-# or
-cast init -f
- -

Auto-detection Logic

-

CAST scans the current directory for the following marker files:

- - - - - - - - - - - - - - - - - - - - - - - - - -
Project TypeMarker FilesStatus
pythonpyproject.toml, requirements.txt, setup.py, setup.cfg✅ Available
nodejspackage.json🔜 Coming soon
gogo.mod🔜 Coming soon
-

The first matching project type wins. If no marker files are found, cast init exits with an error and prompts you to use --type.

- -

Error Conditions

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ErrorCauseResolution
Could not detect project type.No marker files foundUse --type python
Workflow already exists.github/workflows/devsecops.yml existsUse --force to overwrite
Unsupported project typeType not recognizedUse a supported type
nodejs support is coming soonStack not yet availableUse python for now
- -
- -

cast version

-

Display the installed version of castops.

- -

Synopsis

-
cast version
- -

Description

-

Reads the version from the installed package metadata and prints it.

- -

Example

-
cast version
-# cast 0.1.0
- -
- -

Exit Codes

- - - - - - - - - - - - - - - - - -
CodeMeaning
0Success
1Error (detection failed, unsupported type, existing file, template error)
- -
- -

Environment Variables

-

The cast CLI does not read any environment variables directly. Environment variables relevant to the generated workflow (e.g., SEMGREP_APP_TOKEN) are configured as GitHub Actions secrets, not in your local environment.

-
- -
- - - - - diff --git a/website/docs/dashboard-guide.html b/website/docs/dashboard-guide.html deleted file mode 100644 index c9cb31d..0000000 --- a/website/docs/dashboard-guide.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - Security Dashboard Guide — CAST - - - - - - -
- - - -
- - -

Security Dashboard Guide

-

CAST can generate a static HTML compliance dashboard from SARIF scan results and publish it to GitHub Pages — no external services, no SaaS accounts.

- -

What the Dashboard Shows

-

Each SARIF file becomes one row in the dashboard:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ColumnDescription
Project / ToolSARIF filename + tools that produced it
Status🟢 PASS / 🔴 FAIL / 🟡 WARN
CriticalCount of SARIF error-level findings
HighCount of SARIF warning-level findings
MediumCount of SARIF note-level findings
DetailsExpandable list of individual findings
-

The header shows total counts and the commit SHA of the last scanned run.

- -
- -

Setup

- -

Step 1 — Enable GitHub Pages

-

In your repository: Settings → Pages → Source: GitHub Actions

- -

Step 2 — Add the publish workflow

-
mkdir -p .github/workflows
-curl -o .github/workflows/publish-dashboard.yml \
-  https://raw.githubusercontent.com/castops/cast/main/templates/github/publish-dashboard.yml
-

Or copy it from the CAST repo:

-
templates/github/publish-dashboard.yml → .github/workflows/publish-dashboard.yml
- -

Step 3 — Copy the dashboard scripts

-
mkdir -p dashboard
-curl -o dashboard/generate.py \
-  https://raw.githubusercontent.com/castops/cast/main/dashboard/generate.py
-curl -o dashboard/template.html \
-  https://raw.githubusercontent.com/castops/cast/main/dashboard/template.html
- -

Step 4 — Commit and push

-
git add .github/workflows/publish-dashboard.yml dashboard/
-git commit -m "ci: add CAST security dashboard"
-git push
-

The dashboard publishes automatically after each CAST DevSecOps pipeline run.

- -
- -

How It Works

-
CAST DevSecOps pipeline
-  ├── sast job     → uploads cast-sarif-sast artifact (semgrep.sarif)
-  └── container job → uploads cast-sarif-container artifact (trivy.sarif)
-
-publish-dashboard workflow (triggered on workflow_run: completed)
-  ├── downloads all cast-sarif-* artifacts
-  ├── runs python dashboard/generate.py
-  └── deploys _site/index.html to GitHub Pages
-

The workflow_run trigger ensures the dashboard only publishes after a complete pipeline run, not on every push.

- -
- -

Generating the Dashboard Locally

-
# Place your SARIF files in sarif-results/
-mkdir -p sarif-results
-cp path/to/semgrep.sarif sarif-results/
-cp path/to/trivy.sarif sarif-results/
-
-# Generate
-python dashboard/generate.py
-
-# Open
-open dashboard/index.html
- -

Options:

-
python dashboard/generate.py --help
-
-  --sarif-dir DIR    Directory containing .sarif files (default: sarif-results)
-  --output FILE      Output HTML path (default: dashboard/index.html)
-  --commit SHA       Commit SHA to display in header
- -
- -

Customizing the Dashboard

-

The template lives in dashboard/template.html. It uses plain HTML and inline CSS — no build step, no npm, no webpack.

-

To customize colors, edit the CSS variables in :root:

-
:root {
-  --green: #3fb950;   /* PASS badge color */
-  --red: #f85149;     /* FAIL badge color */
-  --yellow: #d29922;  /* WARN badge color */
-}
-

To add columns (e.g., scanner version), extend generate.py and the matching <th>/<td> in the template.

- -
- -

Multi-repo Dashboards

-

To aggregate findings from multiple repositories into one dashboard:

-
    -
  1. Have each repo upload its SARIF artifacts with a unique prefix: -
    - uses: actions/upload-artifact@v4
    -  with:
    -    name: cast-sarif-myrepo-sast
    -    path: semgrep.sarif
    -
  2. -
  3. In a central dashboard repository, download artifacts from all repos using gh run download or the GitHub API, then run generate.py.
  4. -
-

This approach requires a GitHub token with actions:read access to each repo.

-
- -
- - - - - diff --git a/website/docs/getting-started.html b/website/docs/getting-started.html deleted file mode 100644 index e9397f5..0000000 --- a/website/docs/getting-started.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - Getting Started — CAST - - - - - - -
- - - -
- - -

Getting Started with CAST

-

This guide walks you through adding a production-grade DevSecOps pipeline to your repository in under five minutes.

- -

Prerequisites

-
    -
  • A GitHub repository with GitHub Actions enabled
  • -
  • Python 3.9+ (only required for the cast CLI)
  • -
-

No external accounts, tokens, or SaaS subscriptions are required.

- -
- -

Step 1 — Install the CLI

-
pip install castops
-

Verify the installation:

-
cast --help
- -
- -

Step 2 — Initialize Your Pipeline

-

Navigate to your project root and run:

-
cast init
-

CAST will auto-detect your project type and write the workflow file:

-
╭──────────────────────────────────────────────────╮
-│ CAST — CI/CD Automation & Security Toolkit        │
-╰──────────────────────────────────────────────────╯
-Detected project type: python
-Downloading template... done
-
-✓ Created .github/workflows/devsecops.yml
-
-Commit and push to activate your DevSecOps pipeline:
-  git add .github/workflows/devsecops.yml
-  git commit -m 'ci: add CAST DevSecOps pipeline'
-  git push
-

If auto-detection fails (no pyproject.toml, requirements.txt, etc.), specify the type explicitly:

-
cast init --type python
- -
- -

Step 3 — Commit and Push

-
git add .github/workflows/devsecops.yml
-git commit -m "ci: add CAST DevSecOps pipeline"
-git push
-

GitHub Actions will pick up the workflow and run your first pipeline immediately.

- -
- -

Step 4 — Review Your First Run

-
    -
  1. Go to your repository on GitHub
  2. -
  3. Click the Actions tab
  4. -
  5. You should see "CAST DevSecOps" running
  6. -
-

The pipeline runs six jobs:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
JobToolWhat to Expect
Secrets DetectionGitleaksPass if no secrets in git history
SASTSemgrepPass with open-source rules; configure cloud token for more
SCApip-auditPass if no CVEs in your dependencies
Container SecurityTrivySkipped if no Dockerfile
Code QualityRuffPass if code meets style rules
Security GateBuilt-inPasses if all critical checks pass
- -
- -

Step 5 — View Security Findings

-

All findings from Semgrep and Trivy are uploaded to GitHub's Security tab:

-
    -
  1. Go to your repository → Security tab
  2. -
  3. Click "Code scanning alerts"
  4. -
  5. Review any findings
  6. -
-

New findings will also appear as inline comments on future pull requests.

- -
- -

Step 6 — Enforce the Gate (Optional but Recommended)

-

To prevent merging pull requests that fail the Security Gate:

-
    -
  1. Go to Settings → Branches
  2. -
  3. Click "Add branch protection rule"
  4. -
  5. Set Branch name pattern to main
  6. -
  7. Enable "Require status checks to pass before merging"
  8. -
  9. Search for and select "Security Gate"
  10. -
  11. Save the rule
  12. -
-

From now on, any pull request with security failures will be blocked from merging.

- -
- -

Optional: Enable Semgrep Cloud

-

For additional security rules and a centralized findings dashboard:

-
    -
  1. Sign up at semgrep.dev (free tier available)
  2. -
  3. Go to Settings → Tokens and create a CI token
  4. -
  5. In your GitHub repository, go to Settings → Secrets and variables → Actions
  6. -
  7. Add a secret named SEMGREP_APP_TOKEN with your token value
  8. -
-

The pipeline will automatically use your cloud token on the next run.

- -
- -

Manual Installation (No CLI)

-

If you prefer not to install the CLI, copy the template directly:

-
# Create the workflows directory
-mkdir -p .github/workflows
-
-# Download the Python template
-curl -o .github/workflows/devsecops.yml \
-  https://raw.githubusercontent.com/castops/cast/main/src/cast_cli/templates/python/devsecops.yml
-
-# Commit and push
-git add .github/workflows/devsecops.yml
-git commit -m "ci: add CAST DevSecOps pipeline"
-git push
- -
- -

Next Steps

-
    -
  • Read the Pipeline Reference for a full technical breakdown of each job, how to customize thresholds, and how to suppress false positives
  • -
  • Read the CLI Reference for all available options
  • -
  • See CONTRIBUTING.md to add support for a new language stack
  • -
-
- -
- - - - - diff --git a/website/docs/gitlab-guide.html b/website/docs/gitlab-guide.html deleted file mode 100644 index 09a658e..0000000 --- a/website/docs/gitlab-guide.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - GitLab CI Guide — CAST - - - - - - -
- - - -
- - -

GitLab CI Integration Guide

-

CAST supports GitLab CI alongside GitHub Actions. Use cast init --platform gitlab to generate a .gitlab-ci.yml, or include a remote template directly.

- -

Quick Start

- -

Option A — CLI

-
pip install castops
-cast init --platform gitlab
-

CAST auto-detects your project type and writes .gitlab-ci.yml.

- -

Option B — Remote include (zero-install)

-

Add to your .gitlab-ci.yml:

-
include:
-  - remote: 'https://raw.githubusercontent.com/castops/cast/main/src/cast_cli/templates/gitlab/python/devsecops.yml'
-

Replace python with nodejs or go to match your stack.

- -
- -

What Gets Installed

-

The pipeline adds six jobs across two stages:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StageJobToolBlocks pipeline?
cast-scancast-secretsGitleaksYes
cast-scancast-sastSemgrepNo (gate evaluates)
cast-scancast-scapip-audit / npm audit / govulncheckYes
cast-scancast-containerTrivyNo (gate evaluates)
cast-scancast-qualityRuff / ESLint / staticcheckNo (informational)
cast-gatecast-gateconftestYes
- -
- -

GitLab Security Dashboard Integration

-

Semgrep results are reported via the sast artifact report type — they appear automatically in GitLab's Security & Compliance → Security dashboard.

-

Trivy results are reported via container_scanning — they appear in the Container Scanning report in merge requests.

- -
- -

Policy Customization

-

The gate job uses conftest to evaluate SARIF findings against an OPA Rego policy.

-

Default behavior: block if any CRITICAL finding (SARIF level error) is found.

-

Override the policy by creating a policy/ directory in your repo:

-
policy/
-  default.rego   ← CAST looks here first
-

Or set the CAST_POLICY CI/CD variable to default, strict, or permissive:

-
variables:
-  CAST_POLICY: strict   # block on HIGH + CRITICAL
-

See Policy Reference for policy details.

- -
- -

Merge Request Integration

-

When triggered on a merge request, the pipeline:

-
    -
  1. Runs all security scans in parallel
  2. -
  3. Reports Semgrep findings inline on the diff
  4. -
  5. Posts container scanning results to the MR security widget
  6. -
  7. Blocks the MR from merging if the gate job fails
  8. -
- -
- -

Extending the Pipeline

-

Since the template uses cast- prefixed stage names (cast-scan, cast-gate), you can add your own stages without conflict:

-
include:
-  - remote: 'https://raw.githubusercontent.com/castops/cast/main/src/cast_cli/templates/gitlab/python/devsecops.yml'
-
-stages:
-  - cast-scan
-  - cast-gate
-  - test       # your own stage
-  - deploy
-
-my-tests:
-  stage: test
-  script: pytest
- -
- -

Troubleshooting

- -

cast-sca fails with "no requirements file"

-

pip-audit looks for requirements*.txt. If you use pyproject.toml only, add:

-
cast-sca:
-  script:
-    - pip install --quiet pip-audit
-    - pip install -e .
-    - pip-audit
- -

cast-container runs but Dockerfile is present

-

The job uses trivy fs (filesystem scan) rather than building and scanning the image, so no Docker daemon is required. To scan a built image, override the job with Docker-in-Docker configuration.

- -

Gate passes but I see findings in the Security dashboard

-

The gate only blocks on CRITICAL findings by default. Set CAST_POLICY: strict to also block on HIGH findings.

-
- -
- - - - - diff --git a/website/docs/pipeline-reference.html b/website/docs/pipeline-reference.html deleted file mode 100644 index 8ea8a7e..0000000 --- a/website/docs/pipeline-reference.html +++ /dev/null @@ -1,811 +0,0 @@ - - - - - - Pipeline Reference — CAST - - - - - - -
- - - -
- - -

Pipeline Reference

-

This document provides a complete technical reference for the CAST DevSecOps pipeline.

- -

Table of Contents

- - -
- -

Overview

-

The CAST DevSecOps pipeline is a GitHub Actions workflow that runs six jobs on every push and pull request to your main branch.

-
Trigger: push / pull_request / workflow_dispatch
-│
-├── Job 1: secrets        (Gitleaks)       ─┐
-├── Job 2: sast           (Semgrep)         │ Run in parallel
-├── Job 3: sca            (pip-audit)       │
-├── Job 4: container      (Trivy)          ─┘
-├── Job 5: quality        (Ruff)
-│
-└── Job 6: gate           (Security Gate)  ← waits for 1, 2, 3, 5
- -
- -

Job Reference

- -

1. Secrets Detection

-

Job ID: secrets
- Runner: ubuntu-latest
- Tool: Gitleaks v2

- -

What it does

-

Scans the complete git history of the repository for hardcoded secrets, API keys, tokens, passwords, and other credentials. Gitleaks uses a comprehensive set of regex patterns to detect over 150 types of secrets.

- -

Configuration

-
secrets:
-  name: Secrets Detection
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-      with:
-        fetch-depth: 0   # Full history — critical for catching old commits
-    - name: Gitleaks
-      uses: gitleaks/gitleaks-action@v2
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- -

Key settings

- - - - - - - - - - - - - - - - - - - - -
SettingValueWhy
fetch-depth: 0Full historyGitleaks scans all commits, not just the latest
GITHUB_TOKENAuto-providedUsed for rate limit avoidance on public repos
- -

Failure behavior

-
    -
  • Fails if any secret is detected in any commit
  • -
  • Blocks merge via the Security Gate
  • -
- -

Suppressing false positives

-

Add a .gitleaks.toml to your repository root:

-
[allowlist]
-  description = "Allowlist"
-  regexes = [
-    '''false-positive-pattern''',
-  ]
-  paths = [
-    '''path/to/test/fixtures''',
-  ]
- -
- -

2. SAST

-

Job ID: sast
- Runner: ubuntu-latest (containerized)
- Tool: Semgrep

- -

What it does

-

Performs Static Application Security Testing (SAST) — analyzing source code for security vulnerabilities, insecure patterns, and common coding mistakes without executing the code.

- -

Configuration

-
sast:
-  name: SAST
-  runs-on: ubuntu-latest
-  container:
-    image: semgrep/semgrep
-  steps:
-    - uses: actions/checkout@v4
-    - name: Semgrep scan
-      run: semgrep ci --sarif --output=semgrep.sarif || true
-      env:
-        SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
-    - name: Upload to GitHub Security tab
-      uses: github/codeql-action/upload-sarif@v3
-      if: always()
-      with:
-        sarif_file: semgrep.sarif
- -

Key settings

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SettingValueWhy
semgrep/semgrep containerOfficial imageEnsures correct Semgrep version
|| trueAlways exit 0Findings are surfaced via SARIF, not exit code
SEMGREP_APP_TOKENOptional secretEnables cloud rules; falls back to open-source rules
if: always() on uploadAlways uploadEnsures SARIF is uploaded even if scan has findings
- -

SARIF Integration

-

Findings are uploaded to the GitHub Security tab and appear as:

-
    -
  • Code scanning alerts on the repository
  • -
  • Inline annotations on pull request diffs
  • -
- -

Optional: Semgrep Cloud

-

Set SEMGREP_APP_TOKEN as a GitHub Actions secret to enable:

-
    -
  • Additional proprietary rulesets
  • -
  • Historical tracking in Semgrep's dashboard
  • -
  • Team-wide finding management
  • -
- -
- -

3. SCA

-

Job ID: sca
- Runner: ubuntu-latest
- Tool: pip-audit

- -

What it does

-

Performs Software Composition Analysis (SCA) — checking your Python dependencies against known vulnerability databases (PyPI Advisory Database, OSV).

- -

Configuration

-
sca:
-  name: SCA
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
-      with:
-        python-version: "3.x"
-        cache: pip
-    - name: pip-audit
-      uses: pypa/gh-action-pip-audit@v1
-      with:
-        inputs: requirements*.txt
- -

Key settings

- - - - - - - - - - - - - - - - - - - - -
SettingValueWhy
cache: pipEnabledSpeeds up subsequent runs
inputs: requirements*.txtGlob patternScans all requirements files
- -

Failure behavior

-
    -
  • Fails if any dependency has a known CVE
  • -
  • Blocks merge via the Security Gate
  • -
- -

Customization

-

To scan pyproject.toml dependencies instead of requirements files:

-
- uses: pypa/gh-action-pip-audit@v1
-  with:
-    inputs: pyproject.toml
- -
- -

4. Container Security

-

Job ID: container
- Runner: ubuntu-latest
- Tool: Trivy

- -

What it does

-

Scans your Docker image for known CVEs in OS packages and application dependencies. The job is automatically skipped if no Dockerfile is present in the repository.

- -

Configuration

-
container:
-  name: Container Security
-  runs-on: ubuntu-latest
-  if: hashFiles('Dockerfile') != ''
-  steps:
-    - uses: actions/checkout@v4
-    - name: Build image
-      run: docker build -t cast-scan:${{ github.sha }} .
-    - name: Trivy scan
-      uses: aquasecurity/trivy-action@master
-      with:
-        image-ref: cast-scan:${{ github.sha }}
-        format: sarif
-        output: trivy.sarif
-        severity: CRITICAL,HIGH
-        exit-code: "0"
-    - name: Upload to GitHub Security tab
-      uses: github/codeql-action/upload-sarif@v3
-      if: always()
-      with:
-        sarif_file: trivy.sarif
- -

Key settings

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SettingValueWhy
hashFiles('Dockerfile') != ''ConditionalSkips gracefully if no Dockerfile exists
severity: CRITICAL,HIGHFilterFocuses on actionable findings
exit-code: "0"No exitGate job handles blocking logic
github.sha tagUnique tagPrevents collision between concurrent runs
- -

Failure behavior

-
    -
  • Does not directly block merges — findings are surfaced via SARIF
  • -
  • To add blocking behavior, add container to the gate job's needs array
  • -
- -
- -

5. Code Quality

-

Job ID: quality
- Runner: ubuntu-latest
- Tool: Ruff

- -

What it does

-

Runs Ruff — an extremely fast Python linter and formatter — to enforce code style and catch common mistakes.

- -

Configuration

-
quality:
-  name: Code Quality
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - uses: astral-sh/ruff-action@v1
- -

Customization

-

Ruff reads configuration from pyproject.toml or ruff.toml:

-
# pyproject.toml
-[tool.ruff]
-target-version = "py39"
-line-length = 100
-
-[tool.ruff.lint]
-select = ["E", "F", "W", "I"]
-ignore = ["E501"]
- -

Failure behavior

-
    -
  • Fails if any lint errors are found
  • -
  • Does not block merges by default (not in gate's needs array)
  • -
  • To block on quality failures, add quality to the gate's blocking condition
  • -
- -
- -

6. Security Gate

-

Job ID: gate
- Runner: ubuntu-latest
- Dependencies: secrets, sast, sca, quality

- -

What it does

-

Aggregates the results of all security jobs and makes a single pass/fail decision. This is the job that GitHub branch protection rules should target.

- -

Configuration

-
gate:
-  name: Security Gate
-  runs-on: ubuntu-latest
-  needs: [secrets, sast, sca, quality]
-  if: always()
-  steps:
-    - name: Evaluate results
-      run: |
-        echo "secrets : ${{ needs.secrets.result }}"
-        echo "sast    : ${{ needs.sast.result }}"
-        echo "sca     : ${{ needs.sca.result }}"
-        echo "quality : ${{ needs.quality.result }}"
-
-        if [[ "${{ needs.secrets.result }}" == "failure" ||
-              "${{ needs.sast.result }}"    == "failure" ||
-              "${{ needs.sca.result }}"     == "failure" ]]; then
-          echo "❌ Security gate failed — merge blocked"
-          exit 1
-        fi
-
-        echo "✅ All checks passed — safe to merge"
- -

Gate decision matrix

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SecretsSASTSCAGate Result
✅ pass✅ pass✅ pass✅ Allow merge
❌ fail✅ pass✅ pass❌ Block merge
✅ pass❌ fail✅ pass❌ Block merge
✅ pass✅ pass❌ fail❌ Block merge
anyanyany❌ Block merge (if any of the above fail)
-
Note: Code quality (Ruff) failures are reported but do not block merges in the default configuration.
- -

Enforcing the gate with branch protection

-

To make the Security Gate mandatory for pull requests:

-
    -
  1. Go to Settings → Branches → Branch protection rules
  2. -
  3. Add a rule for main (or master)
  4. -
  5. Enable "Require status checks to pass before merging"
  6. -
  7. Search for and add "Security Gate"
  8. -
  9. Optionally enable "Require branches to be up to date before merging"
  10. -
- -
- -

Permissions

-

The workflow requests the minimum permissions needed:

-
permissions:
-  contents: read          # checkout code
-  security-events: write  # upload SARIF to Security tab
-  actions: read           # read workflow status for gate
-

No write access to repository contents is granted.

- -
- -

Trigger Configuration

-

The default triggers cover the most common workflow:

-
on:
-  push:
-    branches: [main, master]
-  pull_request:
-    branches: [main, master]
-  workflow_dispatch:        # manual trigger from GitHub UI
- -

Running only on pull requests

-

To reduce Actions minutes, remove the push trigger:

-
on:
-  pull_request:
-    branches: [main, master]
-  workflow_dispatch:
- -

Adding schedule-based scanning

-

To run a full scan daily (e.g., to catch newly disclosed CVEs):

-
on:
-  schedule:
-    - cron: "0 6 * * *"   # 06:00 UTC daily
-  push:
-    branches: [main, master]
-  pull_request:
-    branches: [main, master]
- -
- -

Customization Guide

- -

Changing severity thresholds

-

In the Trivy job, change the severity input:

-
severity: CRITICAL,HIGH,MEDIUM   # report medium and above
- -

Adding email notifications on gate failure

-
gate:
-  # ... existing config ...
-  steps:
-    - name: Evaluate results
-      # ... existing step ...
-    - name: Notify on failure
-      if: failure()
-      uses: dawidd6/action-send-mail@v3
-      with:
-        server_address: smtp.example.com
-        to: security-team@example.com
-        subject: "CAST Security Gate Failed — ${{ github.repository }}"
-        body: "Security gate failed on ${{ github.ref }}"
- -

Skipping checks on specific paths

-
on:
-  push:
-    branches: [main, master]
-    paths-ignore:
-      - "docs/**"
-      - "*.md"
- -
- -

Troubleshooting

- -

Gitleaks fails on legitimate test fixtures

-

Add a .gitleaks.toml allowlist (see Secrets Detection section).

- -

Semgrep SARIF upload fails

-

Ensure security-events: write is set in permissions. This is required for the github/codeql-action/upload-sarif action.

- -

pip-audit finds no requirements file

-

If you use only pyproject.toml, change the inputs parameter:

-
with:
-  inputs: pyproject.toml
-

Or install your package and audit the environment:

-
- run: pip install -e .
-- uses: pypa/gh-action-pip-audit@v1
- -

Container job is skipped unexpectedly

-

The hashFiles('Dockerfile') condition evaluates to empty string if the file is in a subdirectory. Change the condition to match your path:

-
if: hashFiles('**/Dockerfile') != ''
-
- -
- - - - - diff --git a/website/docs/plugin-guide.html b/website/docs/plugin-guide.html deleted file mode 100644 index cb84bec..0000000 --- a/website/docs/plugin-guide.html +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - Plugin Guide — CAST - - - - - - -
- - - -
- - -

Plugin Guide — Extending CAST

-

CAST is designed to be extended. Every security tool that can produce a SARIF file can plug into the CAST gate — no fork required.

- -

How Plugins Work

-

CAST's gate evaluates all artifacts whose name matches cast-sarif-*. Any job in your workflow that uploads an artifact with this naming convention is automatically included in the security gate evaluation.

- -
Your workflow
-│
-├── cast-sast       → uploads cast-sarif-sast      ─┐
-├── cast-sca        → uploads cast-sarif-sca         │ Gate evaluates
-├── cast-secrets    → uploads cast-sarif-secrets      │ ALL of these
-│                                                    │
-└── my-custom-tool  → uploads cast-sarif-custom    ──┘  ← plugin!
- -

The gate job (cast-gate) downloads everything matching cast-sarif-* and passes each file through the active OPA/conftest policy. Your custom tool's findings are treated identically to built-in findings.

- -
- -

Adding a Custom Tool (GitHub Actions)

- -

Step 1 — Run your tool and produce a SARIF file

-

Any tool that can output SARIF works. If your tool doesn't support SARIF natively, you can write a small wrapper (see Writing a SARIF Wrapper).

- -
jobs:
-  my-custom-scan:
-    name: Custom Security Scan
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Run my tool
-        run: |
-          my-security-tool --output=results.sarif --format=sarif .
-
-      - name: Upload as CAST plugin
-        uses: actions/upload-artifact@v4
-        with:
-          name: cast-sarif-custom   # ← must start with cast-sarif-
-          path: results.sarif
- -

Step 2 — That's it

-

The CAST gate job automatically picks up cast-sarif-custom on its next run. No changes to the gate job are needed.

- -
- -

Adding a Custom Tool (GitLab CI)

- -
my-custom-scan:
-  stage: cast-scan
-  script:
-    - my-security-tool --output=results.sarif --format=sarif .
-  artifacts:
-    name: cast-sarif-custom        # ← must start with cast-sarif-
-    paths:
-      - results.sarif
-    when: always
- -
- -

Writing a SARIF Wrapper

-

If your tool doesn't output SARIF, wrap it with a small Python script:

- -
#!/usr/bin/env python3
-"""Convert custom tool output to SARIF 2.1.0."""
-
-import json
-import subprocess
-import sys
-
-def run_tool():
-    result = subprocess.run(
-        ["my-tool", "--json", "."],
-        capture_output=True, text=True
-    )
-    return json.loads(result.stdout)
-
-def to_sarif(findings):
-    results = []
-    for f in findings:
-        level = "error" if f["severity"] == "CRITICAL" else "warning"
-        results.append({
-            "ruleId": f["rule_id"],
-            "level": level,           # "error" → CAST gate blocks on this
-            "message": {"text": f["message"]},
-            "locations": [{
-                "physicalLocation": {
-                    "artifactLocation": {"uri": f["file"]},
-                    "region": {"startLine": f["line"]}
-                }
-            }]
-        })
-
-    return {
-        "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
-        "version": "2.1.0",
-        "runs": [{
-            "tool": {
-                "driver": {
-                    "name": "my-tool",
-                    "version": "1.0.0",
-                    "rules": []
-                }
-            },
-            "results": results
-        }]
-    }
-
-if __name__ == "__main__":
-    findings = run_tool()
-    sarif = to_sarif(findings)
-    with open("custom.sarif", "w") as f:
-        json.dump(sarif, f, indent=2)
-    print(f"Wrote {len(findings)} findings to custom.sarif")
- -

SARIF severity → gate behavior

- - - - - - - - - - - - - - - - - - - - - - - - - - -
SARIF leveldefault policystrict policy
error❌ Blocks gate❌ Blocks gate
warning✅ Passes gate❌ Blocks gate
note✅ Passes gate✅ Passes gate
- -

Set level: "error" for findings you want to block merges. Set level: "warning" for findings you want to surface without blocking.

- -
- -

Naming Convention

-

Plugin artifact names must follow this pattern:

- -
cast-sarif-<tool-name>
- -

Examples:

-
    -
  • cast-sarif-bandit — Bandit Python security linter
  • -
  • cast-sarif-eslint-security — ESLint security plugin
  • -
  • cast-sarif-checkov — Terraform/IaC scanner
  • -
  • cast-sarif-osv-scanner — OSV dependency scanner
  • -
  • cast-sarif-custom — anything you build
  • -
- -

The <tool-name> portion appears in gate logs to identify which tool produced findings.

- -
- -

Policy Customization for Plugins

-

You can write an OPA policy that treats your plugin's findings differently from built-in findings. The gate passes the full SARIF file to conftest, so you can inspect input.runs[_].tool.driver.name.

- -
package main
-
-import future.keywords.if
-import future.keywords.in
-
-# Block on CRITICAL from any tool
-deny[msg] if {
-    run := input.runs[_]
-    result := run.results[_]
-    result.level == "error"
-    msg := sprintf("[%s] CRITICAL: %s", [run.tool.driver.name, result.message.text])
-}
-
-# Block on HIGH from built-in tools only (not custom scanners)
-deny[msg] if {
-    run := input.runs[_]
-    run.tool.driver.name in {"Semgrep", "Trivy"}
-    result := run.results[_]
-    result.level == "warning"
-    msg := sprintf("[%s] HIGH: %s", [run.tool.driver.name, result.message.text])
-}
- -

See the Policy Reference for full policy authoring documentation.

- -
- -

Example: Adding Bandit (Python Security Linter)

-

Bandit produces SARIF natively. Add it as a CAST plugin:

- -
bandit:
-  name: Bandit (Python Security)
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
-      with:
-        python-version: "3.x"
-    - name: Install Bandit
-      run: pip install bandit[sarif]
-    - name: Run Bandit
-      run: bandit -r . -f sarif -o bandit.sarif || true
-    - name: Upload as CAST plugin
-      uses: actions/upload-artifact@v4
-      if: always()
-      with:
-        name: cast-sarif-bandit
-        path: bandit.sarif
- -

Example: Adding Checkov (Infrastructure-as-Code Scanner)

- -
checkov:
-  name: Checkov (IaC Security)
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - name: Run Checkov
-      uses: bridgecrewio/checkov-action@master
-      with:
-        output_format: sarif
-        output_file_path: checkov.sarif
-        soft_fail: true
-    - name: Upload as CAST plugin
-      uses: actions/upload-artifact@v4
-      if: always()
-      with:
-        name: cast-sarif-checkov
-        path: checkov.sarif
- -
- -

Troubleshooting

- -

Gate doesn't see my plugin's findings

-

Check the artifact name. It must start with cast-sarif- exactly (case-sensitive). Verify in the Actions run under "Artifacts" that the artifact was uploaded.

- -

Plugin findings don't block the gate

-

Check that your SARIF uses level: "error" for findings you want to block. level: "warning" passes the default policy. Switch to CAST_POLICY=strict to block on warnings, or write a custom policy.

- -

SARIF validation errors in conftest

-

Your SARIF must conform to the SARIF 2.1.0 schema. Validate with:

-
pip install sarif-tools
-sarif summary results.sarif
- -
- -
- - - - - diff --git a/website/docs/policy-reference.html b/website/docs/policy-reference.html deleted file mode 100644 index da0600b..0000000 --- a/website/docs/policy-reference.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - Policy Reference — CAST - - - - - - -
- - - -
- - -

Policy Reference — CAST Security Gate

-

CAST uses conftest with OPA Rego policies to evaluate SARIF security findings. This approach replaces hard-coded shell logic with version-controlled, auditable policies.

- -

Built-in Policies

-

Three policies ship with CAST, selectable via the CAST_POLICY variable:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PolicyFileBlocks onUse case
defaultpolicy/default.regoCRITICAL onlyProduction pipelines
strictpolicy/strict.regoCRITICAL + HIGHHigh-security projects
permissivepolicy/permissive.regoNeverAudit / onboarding
- -

Selecting a policy

-

GitHub Actions — set a repository variable:

-
Settings → Secrets and variables → Actions → Variables → New variable
-Name: CAST_POLICY
-Value: strict
- -

GitLab CI — set a CI/CD variable:

-
Settings → CI/CD → Variables → Add variable
-Key: CAST_POLICY
-Value: strict
- -

If CAST_POLICY is unset, the default policy is used.

- -
- -

How the Gate Works

-
    -
  1. SARIF-generating jobs (Semgrep, Trivy) upload their output as artifacts.
  2. -
  3. The gate / cast-gate job downloads all cast-sarif-* artifacts.
  4. -
  5. conftest evaluates each SARIF file against the active policy.
  6. -
  7. If any deny rule fires, the gate job exits 1, blocking the merge/MR.
  8. -
-

SARIF severity → conftest level mapping:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Tool severitySARIF leveldefaultstrict
CRITICALerror❌ BLOCK❌ BLOCK
HIGHwarning✅ PASS❌ BLOCK
MEDIUMwarning✅ PASS❌ BLOCK
LOWnote✅ PASS✅ PASS
-
Note: Semgrep maps ERROR→error, WARNING→warning. Trivy maps CRITICAL→error, HIGH→error (when --severity CRITICAL,HIGH flag is used in the scan).
- -
- -

Writing a Custom Policy

-

To override the CAST policy, create a policy/ directory in your repository with at least one .rego file. The gate job detects this directory and uses it instead of fetching from the CAST repo.

-
your-repo/
-├── policy/
-│   └── my-policy.rego   ← gates evaluate this
-├── .github/workflows/
-│   └── devsecops.yml
- -

Example: block only on specific rule IDs

-
package main
-
-import future.keywords.if
-import future.keywords.in
-
-BLOCKED_RULES := {"sql-injection", "hardcoded-secret", "eval-injection"}
-
-deny[msg] if {
-    run := input.runs[_]
-    result := run.results[_]
-    result.ruleId in BLOCKED_RULES
-    msg := sprintf("Blocked rule %s: %s", [result.ruleId, result.message.text])
-}
- -

Example: block only findings in specific paths

-
package main
-
-import future.keywords.if
-
-deny[msg] if {
-    run := input.runs[_]
-    result := run.results[_]
-    result.level == "error"
-    location := result.locations[_]
-    path := location.physicalLocation.artifactLocation.uri
-    not startswith(path, "test/")   # ignore findings in test code
-    msg := sprintf("CRITICAL in %s: %s", [path, result.message.text])
-}
- -
- -

Testing Policies Locally

-

Install conftest:

-
brew install conftest           # macOS
-# or
-curl -Lo conftest.tar.gz \
-  https://github.com/open-policy-agent/conftest/releases/download/v0.50.0/conftest_0.50.0_Linux_x86_64.tar.gz
-tar xzf conftest.tar.gz && sudo mv conftest /usr/local/bin/
- -

Run against a SARIF file:

-
# Test with default policy (blocks CRITICAL)
-conftest test path/to/semgrep.sarif --policy policy/default.rego
-
-# Test with strict policy (blocks HIGH + CRITICAL)
-conftest test path/to/trivy.sarif --policy policy/strict.rego
-
-# Test with all policies in directory
-conftest test path/to/*.sarif --policy policy/
- -

A non-zero exit code means the policy blocked — the same result as in CI.

-
- -
- - - - - diff --git a/website/zh/docs/cli-reference.html b/website/zh/docs/cli-reference.html deleted file mode 100644 index 8523ef1..0000000 --- a/website/zh/docs/cli-reference.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - CLI 参考 — CAST - - - - - - -
- - - -
- - -

CLI 参考

-

cast 命令行界面的完整参考文档。

- -

目录

- - -
- -

安装

-
pip install castops
-

要求:Python 3.9 或更高版本。

-

安装完成后,cast 命令即可在您的 PATH 中使用。

-
cast --help
- -
- -

全局选项

-
Options:
-  --help    Show help message and exit.
-

不带参数运行 cast 将显示帮助信息。

- -
- -

命令

- -

cast init

-

在当前目录中初始化 DevSecOps 流水线。

- -

语法

-
cast init [OPTIONS]
- -

说明

-

将生产就绪的 GitHub Actions 工作流写入 .github/workflows/devsecops.yml

-

该命令执行以下步骤:

-
    -
  1. 从标记文件检测项目类型(或使用 --type 指定)
  2. -
  3. 检查工作流文件是否已存在
  4. -
  5. 读取对应项目类型的内嵌模板
  6. -
  7. .github/workflows/ 不存在则创建该目录
  8. -
  9. 写入工作流文件
  10. -
- -

选项

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
选项缩写类型默认值说明
--force-f标志false覆盖已存在的工作流文件
--type-t字符串(自动检测)项目类型:python
--help显示帮助并退出
- -

示例

-

自动检测项目类型:

-
cd my-python-project
-cast init
-

输出:

-
╭──────────────────────────────────────────────────╮
-│ CAST — CI/CD Automation & Security Toolkit        │
-╰──────────────────────────────────────────────────╯
-Detected project type: python
-Downloading template... done
-
-✓ Created .github/workflows/devsecops.yml
-
-Commit and push to activate your DevSecOps pipeline:
-  git add .github/workflows/devsecops.yml
-  git commit -m 'ci: add CAST DevSecOps pipeline'
-  git push
- -

手动指定项目类型:

-
cast init --type python
- -

覆盖已存在的工作流:

-
cast init --force
-# or
-cast init -f
- -

自动检测逻辑

-

CAST 扫描当前目录中的以下标记文件:

- - - - - - - - - - - - - - - - - - - - - - - - - -
项目类型标记文件状态
pythonpyproject.toml, requirements.txt, setup.py, setup.cfg✅ 可用
nodejspackage.json🔜 即将推出
gogo.mod🔜 即将推出
-

第一个匹配的项目类型优先生效。若未找到标记文件,cast init 将退出并提示使用 --type

- -

错误情况

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
错误原因解决方法
Could not detect project type.未找到标记文件使用 --type python
Workflow already exists.github/workflows/devsecops.yml 已存在使用 --force 覆盖
Unsupported project type类型不被识别使用受支持的类型
nodejs support is coming soon该技术栈尚未可用目前请使用 python
- -
- -

cast version

-

显示已安装的 castops 版本。

- -

语法

-
cast version
- -

说明

-

从已安装的包元数据中读取版本号并输出。

- -

示例

-
cast version
-# cast 0.1.0
- -
- -

退出码

- - - - - - - - - - - - - - - - - -
退出码含义
0成功
1错误(检测失败、不支持的类型、文件已存在、模板错误)
- -
- -

环境变量

-

cast CLI 不直接读取任何环境变量。生成的工作流所需的环境变量(如 SEMGREP_APP_TOKEN)应配置为 GitHub Actions 密钥,而非本地环境变量。

-
- -
- - - - - diff --git a/website/zh/docs/dashboard-guide.html b/website/zh/docs/dashboard-guide.html deleted file mode 100644 index 2567829..0000000 --- a/website/zh/docs/dashboard-guide.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - 安全看板指南 — CAST - - - - - - -
- - - -
- - -

安全看板指南

-

CAST 可以从 SARIF 扫描结果生成静态 HTML 合规看板,并将其发布到 GitHub Pages——无需外部服务,无需 SaaS 账号。

- -

看板展示内容

-

每个 SARIF 文件在看板中对应一行:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
说明
项目 / 工具SARIF 文件名 + 生成该文件的工具
状态🟢 通过 / 🔴 失败 / 🟡 警告
严重SARIF error 级别发现数量
高危SARIF warning 级别发现数量
中危SARIF note 级别发现数量
详情可展开的单条发现列表
-

页头显示总计数量以及上次扫描运行的提交 SHA。

- -
- -

配置步骤

- -

第一步 — 启用 GitHub Pages

-

在您的仓库中:Settings → Pages → Source: GitHub Actions

- -

第二步 — 添加发布工作流

-
mkdir -p .github/workflows
-curl -o .github/workflows/publish-dashboard.yml \
-  https://raw.githubusercontent.com/castops/cast/main/templates/github/publish-dashboard.yml
-

或从 CAST 仓库复制:

-
templates/github/publish-dashboard.yml → .github/workflows/publish-dashboard.yml
- -

第三步 — 复制看板脚本

-
mkdir -p dashboard
-curl -o dashboard/generate.py \
-  https://raw.githubusercontent.com/castops/cast/main/dashboard/generate.py
-curl -o dashboard/template.html \
-  https://raw.githubusercontent.com/castops/cast/main/dashboard/template.html
- -

第四步 — 提交并推送

-
git add .github/workflows/publish-dashboard.yml dashboard/
-git commit -m "ci: add CAST security dashboard"
-git push
-

看板将在每次 CAST DevSecOps 流水线运行完成后自动发布。

- -
- -

工作原理

-
CAST DevSecOps pipeline
-  ├── sast job     → uploads cast-sarif-sast artifact (semgrep.sarif)
-  └── container job → uploads cast-sarif-container artifact (trivy.sarif)
-
-publish-dashboard workflow (triggered on workflow_run: completed)
-  ├── downloads all cast-sarif-* artifacts
-  ├── runs python dashboard/generate.py
-  └── deploys _site/index.html to GitHub Pages
-

workflow_run 触发器确保看板仅在完整流水线运行结束后发布,而非在每次推送时发布。

- -
- -

在本地生成看板

-
# Place your SARIF files in sarif-results/
-mkdir -p sarif-results
-cp path/to/semgrep.sarif sarif-results/
-cp path/to/trivy.sarif sarif-results/
-
-# Generate
-python dashboard/generate.py
-
-# Open
-open dashboard/index.html
- -

选项:

-
python dashboard/generate.py --help
-
-  --sarif-dir DIR    Directory containing .sarif files (default: sarif-results)
-  --output FILE      Output HTML path (default: dashboard/index.html)
-  --commit SHA       Commit SHA to display in header
- -
- -

自定义看板

-

模板文件位于 dashboard/template.html,使用纯 HTML 和内联 CSS——无需构建步骤,无需 npm,无需 webpack。

-

若要自定义颜色,编辑 :root 中的 CSS 变量:

-
:root {
-  --green: #3fb950;   /* PASS badge color */
-  --red: #f85149;     /* FAIL badge color */
-  --yellow: #d29922;  /* WARN badge color */
-}
-

若要添加列(如扫描器版本),请扩展 generate.py 并在模板中添加对应的 <th>/<td>

- -
- -

多仓库看板

-

若要将多个仓库的发现结果汇总到一个看板:

-
    -
  1. 让每个仓库使用唯一前缀上传 SARIF 制品: -
    - uses: actions/upload-artifact@v4
    -  with:
    -    name: cast-sarif-myrepo-sast
    -    path: semgrep.sarif
    -
  2. -
  3. 在一个中心 dashboard 仓库中,使用 gh run download 或 GitHub API 从所有仓库下载制品,然后运行 generate.py
  4. -
-

此方式需要一个具有对每个仓库 actions:read 权限的 GitHub 令牌。

-
- -
- - - - - diff --git a/website/zh/docs/getting-started.html b/website/zh/docs/getting-started.html deleted file mode 100644 index eed51fb..0000000 --- a/website/zh/docs/getting-started.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - 快速入门 — CAST - - - - - - -
- - - -
- - -

CAST 快速入门

-

本指南将带您在五分钟内为项目仓库添加一套生产级 DevSecOps 流水线。

- -

前提条件

-
    -
  • 已启用 GitHub Actions 的 GitHub 仓库
  • -
  • Python 3.9+(仅在使用 cast CLI 时需要)
  • -
-

无需任何外部账号、令牌或 SaaS 订阅。

- -
- -

第一步 — 安装 CLI

-
pip install castops
-

验证安装是否成功:

-
cast --help
- -
- -

第二步 — 初始化流水线

-

进入项目根目录后运行:

-
cast init
-

CAST 会自动检测您的项目类型并生成工作流文件:

-
╭──────────────────────────────────────────────────╮
-│ CAST — CI/CD Automation & Security Toolkit        │
-╰──────────────────────────────────────────────────╯
-Detected project type: python
-Downloading template... done
-
-✓ Created .github/workflows/devsecops.yml
-
-Commit and push to activate your DevSecOps pipeline:
-  git add .github/workflows/devsecops.yml
-  git commit -m 'ci: add CAST DevSecOps pipeline'
-  git push
-

若自动检测失败(未找到 pyproject.tomlrequirements.txt 等),请手动指定项目类型:

-
cast init --type python
- -
- -

第三步 — 提交并推送

-
git add .github/workflows/devsecops.yml
-git commit -m "ci: add CAST DevSecOps pipeline"
-git push
-

GitHub Actions 会立即识别该工作流并运行您的首次流水线。

- -
- -

第四步 — 查看首次运行结果

-
    -
  1. 进入您在 GitHub 上的仓库
  2. -
  3. 点击 Actions 标签页
  4. -
  5. 您应该能看到 "CAST DevSecOps" 正在运行
  6. -
-

该流水线包含六个作业:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
作业工具预期结果
密钥检测GitleaksGit 历史中无密钥则通过
SASTSemgrep使用开源规则通过;配置云端令牌可获取更多规则
SCApip-audit依赖项无 CVE 则通过
容器安全Trivy无 Dockerfile 时跳过
代码质量Ruff代码符合规范则通过
安全门禁内置所有关键检查通过则放行
- -
- -

第五步 — 查看安全发现

-

Semgrep 和 Trivy 的所有发现均会上传至 GitHub 的 Security 标签页

-
    -
  1. 进入仓库 → Security 标签页
  2. -
  3. 点击 "Code scanning alerts"
  4. -
  5. 审查发现的问题
  6. -
-

新发现的问题也会在后续 Pull Request 中以内联注释的形式呈现。

- -
- -

第六步 — 启用门禁保护(可选但推荐)

-

为防止安全门禁失败的 Pull Request 被合并:

-
    -
  1. 进入 Settings → Branches
  2. -
  3. 点击 "Add branch protection rule"
  4. -
  5. Branch name pattern 设置为 main
  6. -
  7. 启用 "Require status checks to pass before merging"
  8. -
  9. 搜索并选择 "Security Gate"
  10. -
  11. 保存规则
  12. -
-

此后,任何存在安全问题的 Pull Request 将被阻止合并。

- -
- -

可选:启用 Semgrep Cloud

-

获取更多安全规则并使用集中式发现看板:

-
    -
  1. semgrep.dev 注册账号(提供免费套餐)
  2. -
  3. 进入 Settings → Tokens,创建一个 CI 令牌
  4. -
  5. 在 GitHub 仓库中,进入 Settings → Secrets and variables → Actions
  6. -
  7. 添加名为 SEMGREP_APP_TOKEN 的 Secret,值为您的令牌
  8. -
-

下次运行时,流水线将自动使用您的云端令牌。

- -
- -

手动安装(无需 CLI)

-

如果您不想安装 CLI,可以直接复制模板:

-
# 创建工作流目录
-mkdir -p .github/workflows
-
-# 下载 Python 模板
-curl -o .github/workflows/devsecops.yml \
-  https://raw.githubusercontent.com/castops/cast/main/src/cast_cli/templates/python/devsecops.yml
-
-# 提交并推送
-git add .github/workflows/devsecops.yml
-git commit -m "ci: add CAST DevSecOps pipeline"
-git push
- -
- -

后续步骤

-
    -
  • 阅读 流水线参考,了解每个作业的完整技术说明、如何自定义阈值以及如何抑制误报
  • -
  • 阅读 CLI 参考,了解所有可用选项
  • -
  • 参阅 CONTRIBUTING.md,了解如何为新语言栈添加支持
  • -
-
- -
- - - - - diff --git a/website/zh/docs/gitlab-guide.html b/website/zh/docs/gitlab-guide.html deleted file mode 100644 index 8109d7d..0000000 --- a/website/zh/docs/gitlab-guide.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - GitLab CI 指南 — CAST - - - - - - -
- - - -
- - -

GitLab CI 集成指南

-

CAST 同时支持 GitLab CI 和 GitHub Actions。使用 cast init --platform gitlab 生成 .gitlab-ci.yml,或直接引入远程模板。

- -

快速开始

- -

方式 A — 使用 CLI

-
pip install castops
-cast init --platform gitlab
-

CAST 自动检测您的项目类型并生成 .gitlab-ci.yml

- -

方式 B — 远程引入(无需安装)

-

在您的 .gitlab-ci.yml 中添加:

-
include:
-  - remote: 'https://raw.githubusercontent.com/castops/cast/main/src/cast_cli/templates/gitlab/python/devsecops.yml'
-

python 替换为 nodejsgo 以匹配您的技术栈。

- -
- -

安装内容

-

该流水线在两个阶段中添加六个作业:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
阶段作业工具是否阻断流水线?
cast-scancast-secretsGitleaks
cast-scancast-sastSemgrep否(由门禁评估)
cast-scancast-scapip-audit / npm audit / govulncheck
cast-scancast-containerTrivy否(由门禁评估)
cast-scancast-qualityRuff / ESLint / staticcheck否(仅作参考)
cast-gatecast-gateconftest
- -
- -

GitLab 安全看板集成

-

Semgrep 结果通过 sast 制品报告类型上报——它们会自动出现在 GitLab 的安全与合规 → 安全看板中。

-

Trivy 结果通过 container_scanning 上报——它们将显示在合并请求的容器扫描报告中。

- -
- -

策略自定义

-

门禁作业使用 conftest 通过 OPA Rego 策略评估 SARIF 发现结果。

-

默认行为:若发现任何 CRITICAL 级别(SARIF level 为 error)的问题则阻断。

-

覆盖策略:在仓库中创建 policy/ 目录:

-
policy/
-  default.rego   ← CAST looks here first
-

或将 CAST_POLICY CI/CD 变量设置为 defaultstrictpermissive

-
variables:
-  CAST_POLICY: strict   # block on HIGH + CRITICAL
-

详情请参阅 策略参考

- -
- -

合并请求集成

-

当流水线在合并请求上触发时:

-
    -
  1. 并行运行所有安全扫描
  2. -
  3. 在差异视图中内联展示 Semgrep 发现结果
  4. -
  5. 将容器扫描结果发布到 MR 安全小部件
  6. -
  7. 若门禁作业失败则阻止 MR 合并
  8. -
- -
- -

扩展流水线

-

由于模板使用以 cast- 为前缀的阶段名(cast-scancast-gate),您可以添加自己的阶段而不会产生冲突:

-
include:
-  - remote: 'https://raw.githubusercontent.com/castops/cast/main/src/cast_cli/templates/gitlab/python/devsecops.yml'
-
-stages:
-  - cast-scan
-  - cast-gate
-  - test       # your own stage
-  - deploy
-
-my-tests:
-  stage: test
-  script: pytest
- -
- -

故障排查

- -

cast-sca 报错"找不到 requirements 文件"

-

pip-audit 会查找 requirements*.txt。若您仅使用 pyproject.toml,请添加:

-
cast-sca:
-  script:
-    - pip install --quiet pip-audit
-    - pip install -e .
-    - pip-audit
- -

cast-container 运行但 Dockerfile 已存在

-

该作业使用 trivy fs(文件系统扫描)而非构建并扫描镜像,因此不需要 Docker 守护进程。若要扫描构建好的镜像,请使用 Docker-in-Docker 配置覆盖该作业。

- -

门禁通过但安全看板中有发现结果

-

门禁默认仅阻断 CRITICAL 级别的发现。将 CAST_POLICY: strict 设置为也对 HIGH 级别发现进行阻断。

-
- -
- - - - - diff --git a/website/zh/docs/pipeline-reference.html b/website/zh/docs/pipeline-reference.html deleted file mode 100644 index 9fb51a8..0000000 --- a/website/zh/docs/pipeline-reference.html +++ /dev/null @@ -1,811 +0,0 @@ - - - - - - 流水线参考 — CAST - - - - - - -
- - - -
- - -

流水线参考

-

本文档提供 CAST DevSecOps 流水线的完整技术参考。

- -

目录

- - -
- -

概述

-

CAST DevSecOps 流水线是一个 GitHub Actions 工作流,在每次推送和向主分支发起 Pull Request 时运行六个作业。

-
Trigger: push / pull_request / workflow_dispatch
-│
-├── Job 1: secrets        (Gitleaks)       ─┐
-├── Job 2: sast           (Semgrep)         │ Run in parallel
-├── Job 3: sca            (pip-audit)       │
-├── Job 4: container      (Trivy)          ─┘
-├── Job 5: quality        (Ruff)
-│
-└── Job 6: gate           (Security Gate)  ← waits for 1, 2, 3, 5
- -
- -

作业参考

- -

1. 密钥检测

-

作业 ID:secrets
- 运行器:ubuntu-latest
- 工具:Gitleaks v2

- -

功能说明

-

扫描仓库的完整 Git 历史,检测硬编码的密钥、API 密钥、令牌、密码及其他凭证。Gitleaks 使用一套全面的正则表达式模式,能检测超过 150 种类型的密钥。

- -

配置

-
secrets:
-  name: Secrets Detection
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-      with:
-        fetch-depth: 0   # Full history — critical for catching old commits
-    - name: Gitleaks
-      uses: gitleaks/gitleaks-action@v2
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- -

关键配置项

- - - - - - - - - - - - - - - - - - - - -
配置项原因
fetch-depth: 0完整历史Gitleaks 扫描所有提交,而非仅扫描最新提交
GITHUB_TOKEN自动提供用于公开仓库的请求频率限制规避
- -

失败行为

-
    -
  • 若任何提交中检测到密钥则失败
  • -
  • 通过安全门禁阻止合并
  • -
- -

抑制误报

-

在仓库根目录添加 .gitleaks.toml

-
[allowlist]
-  description = "Allowlist"
-  regexes = [
-    '''false-positive-pattern''',
-  ]
-  paths = [
-    '''path/to/test/fixtures''',
-  ]
- -
- -

2. SAST

-

作业 ID:sast
- 运行器:ubuntu-latest(容器化)
- 工具:Semgrep

- -

功能说明

-

执行静态应用程序安全测试(SAST)——在不执行代码的情况下,分析源代码中的安全漏洞、不安全模式和常见编码错误。

- -

配置

-
sast:
-  name: SAST
-  runs-on: ubuntu-latest
-  container:
-    image: semgrep/semgrep
-  steps:
-    - uses: actions/checkout@v4
-    - name: Semgrep scan
-      run: semgrep ci --sarif --output=semgrep.sarif || true
-      env:
-        SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
-    - name: Upload to GitHub Security tab
-      uses: github/codeql-action/upload-sarif@v3
-      if: always()
-      with:
-        sarif_file: semgrep.sarif
- -

关键配置项

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
配置项原因
semgrep/semgrep 容器官方镜像确保使用正确的 Semgrep 版本
|| true始终退出码为 0发现结果通过 SARIF 上报,而非退出码
SEMGREP_APP_TOKEN可选密钥启用云端规则;未配置则回退到开源规则
上传步骤的 if: always()始终上传即使扫描有发现,也确保 SARIF 被上传
- -

SARIF 集成

-

发现结果上传至 GitHub Security 标签页后,将以以下形式呈现:

-
    -
  • 仓库的代码扫描告警
  • -
  • Pull Request 差异视图上的内联标注
  • -
- -

可选:Semgrep Cloud

-

SEMGREP_APP_TOKEN 配置为 GitHub Actions 密钥以启用:

-
    -
  • 额外的专有规则集
  • -
  • 在 Semgrep 看板中进行历史追踪
  • -
  • 团队级别的发现管理
  • -
- -
- -

3. SCA

-

作业 ID:sca
- 运行器:ubuntu-latest
- 工具:pip-audit

- -

功能说明

-

执行软件成分分析(SCA)——对照已知漏洞数据库(PyPI Advisory Database、OSV)检查您的 Python 依赖项。

- -

配置

-
sca:
-  name: SCA
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
-      with:
-        python-version: "3.x"
-        cache: pip
-    - name: pip-audit
-      uses: pypa/gh-action-pip-audit@v1
-      with:
-        inputs: requirements*.txt
- -

关键配置项

- - - - - - - - - - - - - - - - - - - - -
配置项原因
cache: pip已启用加速后续运行
inputs: requirements*.txt通配符模式扫描所有 requirements 文件
- -

失败行为

-
    -
  • 若任何依赖项存在已知 CVE 则失败
  • -
  • 通过安全门禁阻止合并
  • -
- -

自定义

-

若要扫描 pyproject.toml 中的依赖项而非 requirements 文件:

-
- uses: pypa/gh-action-pip-audit@v1
-  with:
-    inputs: pyproject.toml
- -
- -

4. 容器安全

-

作业 ID:container
- 运行器:ubuntu-latest
- 工具:Trivy

- -

功能说明

-

扫描 Docker 镜像中操作系统软件包和应用程序依赖项的已知 CVE。若仓库中不存在 Dockerfile,该作业将自动跳过

- -

配置

-
container:
-  name: Container Security
-  runs-on: ubuntu-latest
-  if: hashFiles('Dockerfile') != ''
-  steps:
-    - uses: actions/checkout@v4
-    - name: Build image
-      run: docker build -t cast-scan:${{ github.sha }} .
-    - name: Trivy scan
-      uses: aquasecurity/trivy-action@master
-      with:
-        image-ref: cast-scan:${{ github.sha }}
-        format: sarif
-        output: trivy.sarif
-        severity: CRITICAL,HIGH
-        exit-code: "0"
-    - name: Upload to GitHub Security tab
-      uses: github/codeql-action/upload-sarif@v3
-      if: always()
-      with:
-        sarif_file: trivy.sarif
- -

关键配置项

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
配置项原因
hashFiles('Dockerfile') != ''条件判断若无 Dockerfile 则优雅跳过
severity: CRITICAL,HIGH过滤器聚焦于可操作的发现
exit-code: "0"不退出由门禁作业处理阻断逻辑
github.sha 标签唯一标签防止并发运行时发生冲突
- -

失败行为

-
    -
  • 不会直接阻止合并——发现结果通过 SARIF 上报
  • -
  • 若要添加阻断行为,请将 container 加入门禁作业的 needs 数组
  • -
- -
- -

5. 代码质量

-

作业 ID:quality
- 运行器:ubuntu-latest
- 工具:Ruff

- -

功能说明

-

运行 Ruff——一款极速 Python 代码检查器和格式化工具——以强制执行代码风格规范并捕获常见错误。

- -

配置

-
quality:
-  name: Code Quality
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - uses: astral-sh/ruff-action@v1
- -

自定义

-

Ruff 从 pyproject.tomlruff.toml 读取配置:

-
# pyproject.toml
-[tool.ruff]
-target-version = "py39"
-line-length = 100
-
-[tool.ruff.lint]
-select = ["E", "F", "W", "I"]
-ignore = ["E501"]
- -

失败行为

-
    -
  • 若发现任何代码检查错误则失败
  • -
  • 默认情况下不阻止合并(未包含在门禁的 needs 数组中)
  • -
  • 若要在质量检查失败时阻止合并,请将 quality 添加到门禁的阻断条件中
  • -
- -
- -

6. 安全门禁

-

作业 ID:gate
- 运行器:ubuntu-latest
- 依赖:secretssastscaquality

- -

功能说明

-

汇总所有安全作业的结果,并做出单一的通过/失败决策。GitHub 分支保护规则应以此作业为目标。

- -

配置

-
gate:
-  name: Security Gate
-  runs-on: ubuntu-latest
-  needs: [secrets, sast, sca, quality]
-  if: always()
-  steps:
-    - name: Evaluate results
-      run: |
-        echo "secrets : ${{ needs.secrets.result }}"
-        echo "sast    : ${{ needs.sast.result }}"
-        echo "sca     : ${{ needs.sca.result }}"
-        echo "quality : ${{ needs.quality.result }}"
-
-        if [[ "${{ needs.secrets.result }}" == "failure" ||
-              "${{ needs.sast.result }}"    == "failure" ||
-              "${{ needs.sca.result }}"     == "failure" ]]; then
-          echo "❌ Security gate failed — merge blocked"
-          exit 1
-        fi
-
-        echo "✅ All checks passed — safe to merge"
- -

门禁决策矩阵

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
密钥检测SASTSCA门禁结果
✅ 通过✅ 通过✅ 通过✅ 允许合并
❌ 失败✅ 通过✅ 通过❌ 阻止合并
✅ 通过❌ 失败✅ 通过❌ 阻止合并
✅ 通过✅ 通过❌ 失败❌ 阻止合并
任意任意任意❌ 阻止合并(以上任一失败)
-
注意:代码质量(Ruff)失败会被上报,但在默认配置下不会阻止合并。
- -

通过分支保护强制执行门禁

-

若要将安全门禁设为 Pull Request 的强制要求:

-
    -
  1. 进入 Settings → Branches → Branch protection rules
  2. -
  3. main(或 master)添加规则
  4. -
  5. 启用 "Require status checks to pass before merging"
  6. -
  7. 搜索并添加 "Security Gate"
  8. -
  9. 可选:启用 "Require branches to be up to date before merging"
  10. -
- -
- -

权限

-

工作流申请最小必要权限:

-
permissions:
-  contents: read          # checkout code
-  security-events: write  # upload SARIF to Security tab
-  actions: read           # read workflow status for gate
-

不授予对仓库内容的写入权限。

- -
- -

触发配置

-

默认触发器覆盖最常见的工作流:

-
on:
-  push:
-    branches: [main, master]
-  pull_request:
-    branches: [main, master]
-  workflow_dispatch:        # manual trigger from GitHub UI
- -

仅在 Pull Request 时运行

-

为减少 Actions 用时,移除 push 触发器:

-
on:
-  pull_request:
-    branches: [main, master]
-  workflow_dispatch:
- -

添加定时扫描

-

每日运行一次完整扫描(例如,用于捕获新披露的 CVE):

-
on:
-  schedule:
-    - cron: "0 6 * * *"   # 06:00 UTC daily
-  push:
-    branches: [main, master]
-  pull_request:
-    branches: [main, master]
- -
- -

自定义指南

- -

修改严重性阈值

-

在 Trivy 作业中修改 severity 输入:

-
severity: CRITICAL,HIGH,MEDIUM   # report medium and above
- -

在门禁失败时发送邮件通知

-
gate:
-  # ... existing config ...
-  steps:
-    - name: Evaluate results
-      # ... existing step ...
-    - name: Notify on failure
-      if: failure()
-      uses: dawidd6/action-send-mail@v3
-      with:
-        server_address: smtp.example.com
-        to: security-team@example.com
-        subject: "CAST Security Gate Failed — ${{ github.repository }}"
-        body: "Security gate failed on ${{ github.ref }}"
- -

跳过特定路径的检查

-
on:
-  push:
-    branches: [main, master]
-    paths-ignore:
-      - "docs/**"
-      - "*.md"
- -
- -

故障排查

- -

Gitleaks 对合法测试固件报错

-

添加 .gitleaks.toml 允许列表(参见 密钥检测 章节)。

- -

Semgrep SARIF 上传失败

-

确保 permissions 中已设置 security-events: writegithub/codeql-action/upload-sarif 操作需要此权限。

- -

pip-audit 找不到 requirements 文件

-

若您仅使用 pyproject.toml,请修改 inputs 参数:

-
with:
-  inputs: pyproject.toml
-

或安装您的包并审计环境:

-
- run: pip install -e .
-- uses: pypa/gh-action-pip-audit@v1
- -

容器作业意外被跳过

-

若 Dockerfile 位于子目录中,hashFiles('Dockerfile') 条件将返回空字符串。请修改条件以匹配您的路径:

-
if: hashFiles('**/Dockerfile') != ''
-
- -
- - - - - diff --git a/website/zh/docs/plugin-guide.html b/website/zh/docs/plugin-guide.html deleted file mode 100644 index 0004246..0000000 --- a/website/zh/docs/plugin-guide.html +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - 插件指南 — CAST - - - - - - -
- - - -
- - -

插件指南 — 扩展 CAST

-

CAST 专为可扩展性而设计。任何能生成 SARIF 文件的安全工具都可以接入 CAST 门禁——无需 Fork 仓库。

- -

插件工作原理

-

CAST 的门禁会评估所有名称匹配 cast-sarif-* 的制品。工作流中任何上传此命名规范制品的作业都会被自动纳入安全门禁评估。

- -
Your workflow
-│
-├── cast-sast       → uploads cast-sarif-sast      ─┐
-├── cast-sca        → uploads cast-sarif-sca         │ Gate evaluates
-├── cast-secrets    → uploads cast-sarif-secrets      │ ALL of these
-│                                                    │
-└── my-custom-tool  → uploads cast-sarif-custom    ──┘  ← plugin!
- -

门禁作业(cast-gate)下载所有匹配 cast-sarif-* 的制品,并将每个文件传递给当前活跃的 OPA/conftest 策略进行评估。您的自定义工具发现结果与内置工具的发现结果处理方式完全相同。

- -
- -

添加自定义工具(GitHub Actions)

- -

第一步 — 运行工具并生成 SARIF 文件

-

任何能输出 SARIF 的工具均可使用。如果您的工具不原生支持 SARIF,可以编写一个简单的包装器(参见 编写 SARIF 包装器)。

- -
jobs:
-  my-custom-scan:
-    name: Custom Security Scan
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Run my tool
-        run: |
-          my-security-tool --output=results.sarif --format=sarif .
-
-      - name: Upload as CAST plugin
-        uses: actions/upload-artifact@v4
-        with:
-          name: cast-sarif-custom   # ← must start with cast-sarif-
-          path: results.sarif
- -

第二步 — 完成

-

CAST 门禁作业将在下次运行时自动识别 cast-sarif-custom,无需修改门禁作业。

- -
- -

添加自定义工具(GitLab CI)

- -
my-custom-scan:
-  stage: cast-scan
-  script:
-    - my-security-tool --output=results.sarif --format=sarif .
-  artifacts:
-    name: cast-sarif-custom        # ← must start with cast-sarif-
-    paths:
-      - results.sarif
-    when: always
- -
- -

编写 SARIF 包装器

-

如果您的工具不输出 SARIF,可以用一个简单的 Python 脚本进行封装:

- -
#!/usr/bin/env python3
-"""Convert custom tool output to SARIF 2.1.0."""
-
-import json
-import subprocess
-import sys
-
-def run_tool():
-    result = subprocess.run(
-        ["my-tool", "--json", "."],
-        capture_output=True, text=True
-    )
-    return json.loads(result.stdout)
-
-def to_sarif(findings):
-    results = []
-    for f in findings:
-        level = "error" if f["severity"] == "CRITICAL" else "warning"
-        results.append({
-            "ruleId": f["rule_id"],
-            "level": level,           # "error" → CAST gate blocks on this
-            "message": {"text": f["message"]},
-            "locations": [{
-                "physicalLocation": {
-                    "artifactLocation": {"uri": f["file"]},
-                    "region": {"startLine": f["line"]}
-                }
-            }]
-        })
-
-    return {
-        "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
-        "version": "2.1.0",
-        "runs": [{
-            "tool": {
-                "driver": {
-                    "name": "my-tool",
-                    "version": "1.0.0",
-                    "rules": []
-                }
-            },
-            "results": results
-        }]
-    }
-
-if __name__ == "__main__":
-    findings = run_tool()
-    sarif = to_sarif(findings)
-    with open("custom.sarif", "w") as f:
-        json.dump(sarif, f, indent=2)
-    print(f"Wrote {len(findings)} findings to custom.sarif")
- -

SARIF 严重性与门禁行为的对应关系

- - - - - - - - - - - - - - - - - - - - - - - - - - -
SARIF leveldefault 策略strict 策略
error❌ 阻断门禁❌ 阻断门禁
warning✅ 通过门禁❌ 阻断门禁
note✅ 通过门禁✅ 通过门禁
- -

对于希望阻断合并的发现,将 level 设为 "error";对于希望上报但不阻断的发现,将 level 设为 "warning"

- -
- -

命名规范

-

插件制品名称必须遵循以下模式:

- -
cast-sarif-<tool-name>
- -

示例:

-
    -
  • cast-sarif-bandit — Bandit Python 安全检查器
  • -
  • cast-sarif-eslint-security — ESLint 安全插件
  • -
  • cast-sarif-checkov — Terraform/IaC 扫描器
  • -
  • cast-sarif-osv-scanner — OSV 依赖扫描器
  • -
  • cast-sarif-custom — 任何您自建的工具
  • -
- -

<tool-name> 部分将出现在门禁日志中,用于标识产生发现的工具。

- -
- -

针对插件的策略自定义

-

您可以编写 OPA 策略,对插件发现与内置工具发现采取不同处理方式。门禁将完整的 SARIF 文件传递给 conftest,因此您可以检查 input.runs[_].tool.driver.name

- -
package main
-
-import future.keywords.if
-import future.keywords.in
-
-# Block on CRITICAL from any tool
-deny[msg] if {
-    run := input.runs[_]
-    result := run.results[_]
-    result.level == "error"
-    msg := sprintf("[%s] CRITICAL: %s", [run.tool.driver.name, result.message.text])
-}
-
-# Block on HIGH from built-in tools only (not custom scanners)
-deny[msg] if {
-    run := input.runs[_]
-    run.tool.driver.name in {"Semgrep", "Trivy"}
-    result := run.results[_]
-    result.level == "warning"
-    msg := sprintf("[%s] HIGH: %s", [run.tool.driver.name, result.message.text])
-}
- -

完整的策略编写文档请参阅 策略参考

- -
- -

示例:添加 Bandit(Python 安全检查器)

-

Bandit 原生支持 SARIF 输出。将其作为 CAST 插件添加:

- -
bandit:
-  name: Bandit (Python Security)
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
-      with:
-        python-version: "3.x"
-    - name: Install Bandit
-      run: pip install bandit[sarif]
-    - name: Run Bandit
-      run: bandit -r . -f sarif -o bandit.sarif || true
-    - name: Upload as CAST plugin
-      uses: actions/upload-artifact@v4
-      if: always()
-      with:
-        name: cast-sarif-bandit
-        path: bandit.sarif
- -

示例:添加 Checkov(基础设施即代码扫描器)

- -
checkov:
-  name: Checkov (IaC Security)
-  runs-on: ubuntu-latest
-  steps:
-    - uses: actions/checkout@v4
-    - name: Run Checkov
-      uses: bridgecrewio/checkov-action@master
-      with:
-        output_format: sarif
-        output_file_path: checkov.sarif
-        soft_fail: true
-    - name: Upload as CAST plugin
-      uses: actions/upload-artifact@v4
-      if: always()
-      with:
-        name: cast-sarif-checkov
-        path: checkov.sarif
- -
- -

故障排查

- -

门禁未识别到插件的发现结果

-

检查制品名称。名称必须以 cast-sarif- 开头(区分大小写)。在 Actions 运行的"Artifacts"部分确认制品已成功上传。

- -

插件发现未阻断门禁

-

检查您的 SARIF 是否对希望阻断的发现使用了 level: "error"level: "warning"default 策略下会通过。切换至 CAST_POLICY=strict 可对 warning 级别也进行阻断,或编写自定义策略。

- -

conftest 中出现 SARIF 验证错误

-

您的 SARIF 必须符合 SARIF 2.1.0 规范。使用以下命令进行验证:

-
pip install sarif-tools
-sarif summary results.sarif
- -
- -
- - - - - diff --git a/website/zh/docs/policy-reference.html b/website/zh/docs/policy-reference.html deleted file mode 100644 index f1eae49..0000000 --- a/website/zh/docs/policy-reference.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - 策略参考 — CAST - - - - - - -
- - - -
- - -

策略参考 — CAST 安全门禁

-

CAST 使用 conftest 配合 OPA Rego 策略来评估 SARIF 安全发现结果。这种方式用经过版本控制、可审计的策略替代了硬编码的 Shell 逻辑。

- -

内置策略

-

CAST 内置三种策略,可通过 CAST_POLICY 变量选择:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
策略文件阻断条件适用场景
defaultpolicy/default.rego仅 CRITICAL生产流水线
strictpolicy/strict.regoCRITICAL + HIGH高安全要求项目
permissivepolicy/permissive.rego从不阻断审计 / 初始接入
- -

选择策略

-

GitHub Actions — 设置仓库变量:

-
Settings → Secrets and variables → Actions → Variables → New variable
-Name: CAST_POLICY
-Value: strict
- -

GitLab CI — 设置 CI/CD 变量:

-
Settings → CI/CD → Variables → Add variable
-Key: CAST_POLICY
-Value: strict
- -

若未设置 CAST_POLICY,则使用 default 策略。

- -
- -

门禁工作原理

-
    -
  1. SARIF 生成作业(Semgrep、Trivy)将输出作为制品上传。
  2. -
  3. gate / cast-gate 作业下载所有 cast-sarif-* 制品。
  4. -
  5. conftest 将每个 SARIF 文件与当前活跃策略进行评估。
  6. -
  7. 若任何 deny 规则触发,门禁作业以退出码 1 退出,阻止合并/MR。
  8. -
-

SARIF 严重性与 conftest 级别的映射关系:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
工具严重性SARIF leveldefaultstrict
CRITICALerror❌ 阻断❌ 阻断
HIGHwarning✅ 通过❌ 阻断
MEDIUMwarning✅ 通过❌ 阻断
LOWnote✅ 通过✅ 通过
-
注意:Semgrep 将 ERROR 映射为 error,WARNING 映射为 warning。Trivy 在使用 --severity CRITICAL,HIGH 参数时,将 CRITICAL 和 HIGH 均映射为 error
- -
- -

编写自定义策略

-

若要覆盖 CAST 策略,请在仓库中创建 policy/ 目录并包含至少一个 .rego 文件。门禁作业会检测到该目录,并优先使用它,而非从 CAST 仓库获取策略。

-
your-repo/
-├── policy/
-│   └── my-policy.rego   ← gates evaluate this
-├── .github/workflows/
-│   └── devsecops.yml
- -

示例:仅对特定规则 ID 进行阻断

-
package main
-
-import future.keywords.if
-import future.keywords.in
-
-BLOCKED_RULES := {"sql-injection", "hardcoded-secret", "eval-injection"}
-
-deny[msg] if {
-    run := input.runs[_]
-    result := run.results[_]
-    result.ruleId in BLOCKED_RULES
-    msg := sprintf("Blocked rule %s: %s", [result.ruleId, result.message.text])
-}
- -

示例:仅对特定路径中的发现进行阻断

-
package main
-
-import future.keywords.if
-
-deny[msg] if {
-    run := input.runs[_]
-    result := run.results[_]
-    result.level == "error"
-    location := result.locations[_]
-    path := location.physicalLocation.artifactLocation.uri
-    not startswith(path, "test/")   # ignore findings in test code
-    msg := sprintf("CRITICAL in %s: %s", [path, result.message.text])
-}
- -
- -

在本地测试策略

-

安装 conftest:

-
brew install conftest           # macOS
-# or
-curl -Lo conftest.tar.gz \
-  https://github.com/open-policy-agent/conftest/releases/download/v0.50.0/conftest_0.50.0_Linux_x86_64.tar.gz
-tar xzf conftest.tar.gz && sudo mv conftest /usr/local/bin/
- -

对 SARIF 文件运行测试:

-
# Test with default policy (blocks CRITICAL)
-conftest test path/to/semgrep.sarif --policy policy/default.rego
-
-# Test with strict policy (blocks HIGH + CRITICAL)
-conftest test path/to/trivy.sarif --policy policy/strict.rego
-
-# Test with all policies in directory
-conftest test path/to/*.sarif --policy policy/
- -

非零退出码表示策略已阻断——与在 CI 中的结果一致。

-
- -
- - - - -