Eljakani/ward
GitHub: Eljakani/ward
专为 Laravel 设计的 Go 语言安全扫描器,能解析项目结构并检测配置错误、漏洞和敏感信息泄露。
Stars: 231 | Forks: 14
专为 Laravel 构建的安全扫描器。
Ward 能够理解你的 Laravel 应用程序——包括其路由、模型、控制器、中间件、Blade 模板、配置文件、`.env` 密钥、Composer 依赖项等。它不仅仅是 grep 匹配模式。它会首先解析你项目的结构,然后针对该结构运行针对性的安全检查。 ## 为什么选择 Ward? Laravel 开箱即提供了许多功能——CSRF 保护、Eloquent 的批量赋值守卫、Bcrypt 哈希、加密 Cookie。但是,很容易出现配置错误或留下标准 linter 无法捕捉的漏洞: - `APP_DEBUG=true` 被推送到生产环境 - 控制器操作没有授权检查 - 处理支付的模型上设置了 `$guarded = []` - `DB::raw()` 中使用了插值的用户输入 - Session Cookie 没有 `Secure` 标志 - API 路由组缺少 `auth:sanctum` - 过时的 Composer 包存在已知的 CVE 漏洞 - Blade 模板在用户数据上使用了 `{!! !!}` Ward 可以检查所有这些以及更多问题。它旨在适应你现有的工作流程——在开发期间在本地运行,或将其连接到 CI 中以控制部署。 ## 工作原理 Ward 通过五个阶段的流水线扫描你的项目: ``` Provider --> Resolvers --> Scanners --> Post-Process --> Report ``` **1. Provider(提供者)** — 定位并准备你的项目源代码。支持本地路径和 git URL(浅克隆)。 **2. Resolvers(解析器)** — 解析 `composer.json`、`composer.lock`、`.env` 和 `config/*.php` 以构建结构化的项目上下文:框架版本、PHP 版本、已安装的包、环境变量、配置文件。 **3. Scanners(扫描器)** — 针对已解析的上下文运行的独立安全检查: | Scanner | What it checks | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `env-scanner` | `.env` 配置错误 — 调试模式、空的 APP_KEY、非生产环境、弱凭据、`.env.example` 中泄露的密钥 | | `config-scanner` | `config/*.php` — 硬编码的调试模式、session cookie 标志、CORS 通配符、配置文件中的硬编码凭据 | | `dependency-scanner` | `composer.lock` — 通过 [OSV.dev](https://osv.dev) 针对整个 Packagist 咨询数据库进行 **实时 CVE 查询**(非硬编码列表,始终保持最新) | | `rules-scanner` | 40 条内置 YAML 规则,涵盖密钥、SQL/命令/代码注入、XSS、调试残留、弱加密、认证问题、批量赋值、不安全的文件上传 | **4. Post-Process(后处理)** — 对发现进行去重,按最低严重性(来自配置)过滤,并与上次扫描进行差异对比,以显示新增与已解决的问题。 **5. Report(报告)** — 生成多种格式的输出并保存扫描历史记录以进行趋势分析。 ## 快速开始 ### 安装 ``` go install github.com/eljakani/ward@latest ``` 确保 `$GOPATH/bin` 位于你的 `PATH` 中(Go 会将二进制文件安装在那里): ``` export PATH="$PATH:$(go env GOPATH)/bin" ``` 或者从源代码构建: ``` git clone https://github.com/Eljakani/ward.git cd ward make build # builds ./ward with embedded version, commit, and date make install # installs to $GOPATH/bin ``` ### 初始化 ``` ward init ``` 这将创建 `~/.ward/` 目录,其中包含你的配置和 40 条默认安全规则: ``` ~/.ward/ ├── config.yaml # Main configuration ├── rules/ # Security rules (YAML) │ ├── secrets.yaml # 7 rules: hardcoded passwords, API keys, AWS creds, JWT, tokens │ ├── injection.yaml # 6 rules: SQL injection, command injection, eval, unserialize │ ├── xss.yaml # 4 rules: unescaped Blade output, JS injection │ ├── debug.yaml # 6 rules: dd(), dump(), phpinfo(), debug bars │ ├── crypto.yaml # 5 rules: md5, sha1, rand(), mcrypt, base64-as-encryption │ ├── security-config.yaml # 7 rules: CORS, SSL verify, CSRF, mass assignment, uploads │ ├── auth.yaml # 5 rules: missing middleware, rate limiting, loginUsingId │ └── custom-example.yaml # Disabled template showing how to write your own rules ├── reports/ # Scan report output └── store/ # Scan history for diffing between runs ``` ### 扫描本地项目 ``` ward scan /path/to/your/laravel-project ``` ### 扫描远程仓库 ``` ward scan https://github.com/user/laravel-project.git ```bash ward scan ./my-app --output json ``` 当没有可用的 TTY 或指定了 `--output` 时,Ward 以无头模式运行,输出带样式的文本——没有交互式 TUI。 ### CI 退出代码 (`--fail-on`) ``` # 如果存在任何 High 或 Critical 发现,则退出代码为 1 ward scan . --output json --fail-on high # 任何发现均失败(包括 Info) ward scan . --output json --fail-on info ``` 严重性阈值是包含性的:`--fail-on medium` 会在遇到 Medium、High 和 Critical 时失败。 ### 基线(抑制已知发现) 首次运行时,生成当前发现的基线: ``` ward scan . --output json --update-baseline .ward-baseline.json ``` 在随后的运行中,抑制那些已知的发现: ``` ward scan . --output json --baseline .ward-baseline.json --fail-on high ``` 只有 **新** 发现(不在基线中)才会被报告。将 `.ward-baseline.json` 提交到你的仓库以跟踪已确认的发现。 ### CI 流水线示例 ``` - name: Run Ward run: | ward scan . --output json,sarif \ --baseline .ward-baseline.json \ --fail-on high ``` ## 报告格式 在 `~/.ward/config.yaml` 中配置输出格式: ``` output: formats: - json # ward-report.json — machine-readable - sarif # ward-report.sarif — GitHub Code Scanning / IDE integration - html # ward-report.html — standalone visual report (dark theme) - markdown # ward-report.md — text-based, great for PRs dir: ./reports ``` JSON 始终作为基线生成。所有报告文件都写入配置的输出目录(默认为 `.`)。 ### GitHub Code Scanning 集成 添加 SARIF 格式并在你的 CI 工作流中上传: ``` - name: Run Ward run: ward scan . --output json - name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ward-report.sarif ``` 请参阅下面的 GitHub Actions 示例以获取完整的工作流。 ## 配置 Ward 从 `~/.ward/config.yaml` 加载其配置: ``` # 报告的最低严重性:info, low, medium, high, critical severity: info output: formats: [json, sarif, html, markdown] dir: ./reports scanners: disable: [] # scanner names to skip, e.g. ["dependency-scanner"] rules: disable: [] # rule IDs to silence, e.g. ["DEBUG-001", "AUTH-001"] override: # change severity for specific rules DEBUG-002: severity: low # custom_dirs: # load rules from additional directories # - /path/to/team-rules providers: git_depth: 1 # shallow clone depth (0 = full history) ``` ## 自定义规则 将 `.yaml` 文件放入 `~/.ward/rules/`,Ward 会自动提取它们。有关带有文档的模板,请参见 `custom-example.yaml`。 ``` rules: - id: TEAM-001 title: "Hardcoded internal service URL" description: "Detects hardcoded URLs to internal services." severity: medium category: Configuration enabled: true patterns: - type: regex target: php-files pattern: 'https?://internal-service\.\w+' remediation: | Use environment variables: $url = env('INTERNAL_SERVICE_URL'); ``` ### 模式类型 | Type | Description | | -------------- | --------------------------------------------------------------------- | | `regex` | 正则表达式匹配(逐行) | | `contains` | 精确子字符串匹配 | | `file-exists` | 检查匹配 glob 模式的文件是否存在 | | `regex-scoped` | 正则匹配,抑制检测到的范围块内的发现 | ### 目标 | Target | Files matched | | ----------------- | --------------------------------------------- | | `php-files` | 所有 `.php` 文件(递归,跳过 `vendor/`) | | `blade-files` | `resources/views/**/*.blade.php` | | `config-files` | `config/*.php` | | `env-files` | `.env`, `.env.*` | | `routes-files` | `routes/*.php` | | `migration-files` | `database/migrations/*.php` | | `js-files` | `resources/js/**/*.{js,ts,jsx,tsx}` | | `path/to/*.ext` | 任何自定义 glob 模式 | ### 反向模式 设置 `negative: true` 可在模式 **缺失** 时触发——对于“必须具有 X”的检查很有用: ``` patterns: - type: contains target: blade-files pattern: "@csrf" negative: true # fire if @csrf is NOT found ``` ### 范围模式 当匹配项 **不** 在周围块(例如中间件组)内时才应标记时,请使用 `type: regex-scoped`。扫描器读取整个文件,跟踪大括号深度以查找以 `scope_exclude` 开头的块,并抑制落在这些块内的任何匹配项。 ``` patterns: - type: regex-scoped target: routes-files pattern: 'Route::(get|post|put|patch|delete)\s*\([^;]*\)\s*;' scope_exclude: 'Route::middleware|->middleware\(' exclude_pattern: '(login|register|password|reset|webhook|health)' ``` | Field | Required | Description | | --------------- | -------- | --------------------------------------------------------------------------- | | `pattern` | yes | 每行匹配的正则表达式 | | `scope_exclude` | yes | 识别受保护范围行的正则表达式(跟踪大括号深度) | | `exclude_pattern` | no | 范围过滤后应用的附加逐行排除(同 `regex`) | | `negative` | no | 反转:模式缺失时触发(语义同 `regex`) | **工作原理:** 1. 文件被扫描一次以定位所有范围块——匹配 `scope_exclude` 且打开 `{...}` 块的行。通过计算大括号深度找到闭合的 `}`。 2. 落在这些范围内的任何行号都标记为受保护。 3. 然后逐行应用主 `pattern` 正则表达式,跳过受保护的行。 **已知限制:** - 字符串字面量或注释中的大括号字符可能会影响深度计算。对于格式不寻常的文件,请记住这一点。 - 在组内 `require` 的单独文件中定义的路由不会跨文件链接;这些文件仍将被单独扫描。 - 对于遗漏的边缘情况,请使用 [基线](#baseline-suppress-known-findings) 永久抑制已确认的误报。 ### 规则覆盖 在 `config.yaml` 中禁用或更改任何规则的严重性,而无需编辑规则文件: ``` rules: disable: [DEBUG-001, DEBUG-002] override: CRYPTO-003: severity: low AUTH-001: enabled: false ``` ## 扫描历史 Ward 自动将每次扫描保存到 `~/.ward/store/`。在随后扫描同一项目时,它会显示发生了什么变化: ``` [info] vs last scan: 2 new, 3 resolved (12->11) ``` 这使你可以随时间跟踪安全态势并捕获回归。 ## 终端 UI Ward 的 TUI 基于 [Bubble Tea](https://github.com/charmbracelet/bubbletea) 构建,可自动适应浅色和深色终端。 ### 扫描视图 在扫描进行时显示——显示流水线阶段进度、带旋转器的扫描器状态、实时严重性计数和可滚动的事件日志。 ### 结果视图 扫描完成后显示——可排序的发现表,带有严重性徽章、类别分组,以及显示描述、代码片段、修复建议和参考的详细信息面板。 ### 键盘快捷键 | Key | Action | | ------------------ | -------------------------------------------- | | `q` / `Ctrl+C` | 退出 | | `?` | 切换帮助 | | `Tab` | 切换视图或面板 | | `j` / `k` / arrows | 导航发现 | | `s` | 切换排序列(严重性、类别、文件) | | `Esc` | 返回扫描视图 | ## CI 集成 ### GitHub Actions 将此工作流作为 `.github/workflows/ward.yml` 添加到你的 Laravel 项目中: ``` name: Ward Security Scan on: [push, pull_request] jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Ward run: go install github.com/eljakani/ward@latest - name: Run Ward run: ward init && ward scan . --output json - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ward-report.sarif ``` ### GitLab CI ``` ward-scan: image: golang:latest script: - go install github.com/eljakani/ward@latest - ward init && ward scan . --output json artifacts: paths: - ward-report.* when: always ``` ## 内置扫描器 ### env-scanner (8 项检查) | ID | Check | Severity | | ------- | ---------------------------------- | -------- | | ENV-001 | 缺少 `.env` 文件 | Info | | ENV-002 | `APP_DEBUG=true` | High | | ENV-003 | `APP_KEY` 为空或缺失 | Critical | | ENV-004 | 弱/默认 `APP_KEY` | Critical | | ENV-005 | 非生产环境 `APP_ENV` | Medium | | ENV-006 | `DB_PASSWORD` 为空 | Low | | ENV-007 | 生产环境中使用文件 sessions | Low | | ENV-008 | `.env.example` 中的真实凭据 | Medium | ### config-scanner (13 项检查) 检查 `config/app.php`、`auth.php`、`session.php`、`mail.php`、`cors.php`、`database.php`、`broadcasting.php` 和 `logging.php` 是否存在硬编码密钥、不安全的默认值和缺少的安全标志。 ### dependency-scanner (实时 CVE 数据库) 将你的 `composer.lock` 作为 SBOM 读取,并实时查询 [OSV.dev](https://osv.dev) 漏洞数据库。每个 Packagist 包都会被检查——没有硬编码的咨询列表。这涵盖了整个 PHP/Composer 生态系统:Laravel、Symfony、Guzzle、Doctrine、Monolog、Livewire、Filament 以及你 lock 文件中的所有其他依赖项。 需要网络访问权限。结果包括 CVE ID、严重性、受影响的版本范围、已修复版本和修复命令。 ### rules-scanner (40 条默认规则) 从 `~/.ward/rules/*.yaml` 加载的基于模式的检查,涵盖密钥、注入、XSS、调试、加密、配置和认证类别。 ## 命令 | Command | Description | | -------------------------------- | ----------------------------------------------------------- | | `ward` | 显示 banner 和用法 | | `ward init` | 使用默认配置和 40 条安全规则创建 `~/.ward/` | | `ward init --force` | 重新创建配置文件(覆盖现有文件) | | `ward scan标签:CI/CD 安全, CISA项目, DNS 反向解析, EVTX分析, GraphQL安全矩阵, Laravel, LLM应用, LNA, OpenVAS, PHP, SQL 注入, TUI, Web 安全, XSS, 多包管理, 安全扫描器, 开源安全工具, 数据投毒防御, 日志审计, 漏洞情报, 终端用户界面, 误配置检测, 身份验证绕过, 逆向工程平台, 错误基检测, 静态代码分析