coroboros/shellscan

GitHub: coroboros/shellscan

一个基于 shellcheck 的容器化 shell 脚本检查工具,支持扫描 .sh 文件、shebang 文件以及嵌入在 GitLab CI 和 GitHub Actions YAML 中的 shell 脚本。

Stars: 0 | Forks: 0

shellscan # shellscan **查找并 lint 项目中的每一个 shell —— `.sh` 文件、shebang 以及嵌入在 GitLab CI 和 GitHub Actions YAML 中的脚本。** 一个基于 Alpine 的镜像封装了 [`shellcheck`](https://github.com/koalaman/shellcheck) 并提供四种发现模式:`.sh` 文件、带有 shell shebang 的文件(`/bin/bash` 和 `env bash` 形式),以及嵌入在 CI YAML 中的 shell 脚本 —— 通过 [`yq`](https://github.com/mikefarah/yq) 提取 GitLab CI 的 `before_script` / `script` / `after_script` 键和 GitHub Actions 的 `run` 步骤,同时展开了 YAML 锚点。文件发现使用 [`fd`](https://github.com/sharkdp/fd)。检查结果以终端输出、[用于合并请求小部件的 GitLab Code Quality JSON,或用于 GitHub 代码扫描的 SARIF 2.1.0](#reports) 格式呈现;嵌入脚本的检查结果会映射回其原始的 YAML 行。可选的[安全规则](#security-rules) 可标记通过管道传递给 shell 的远程代码、输出到作业日志的密钥、未加引号且受攻击者控制的 CI 变量,以及将不受信任的数据注入到 `run` 脚本中的 GitHub 表达式。 [![latest](https://img.shields.io/gitlab/v/release/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?style=flat-square&label=latest&color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan/-/releases) [![pipeline](https://img.shields.io/gitlab/pipeline-status/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?branch=main&style=flat-square&label=pipeline&color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan/-/pipelines) [![ghcr.io](https://img.shields.io/badge/ghcr.io-shellscan-000000?style=flat-square&logo=github)](https://github.com/orgs/coroboros/packages/container/package/shellscan) [![Docker Hub](https://img.shields.io/badge/docker_hub-shellscan-000000?style=flat-square&logo=docker&logoColor=white)](https://hub.docker.com/r/coroboros/shellscan) [![license](https://img.shields.io/badge/license-Apache_2.0-000000?style=flat-square)](LICENSE.md) [![stars](https://img.shields.io/gitlab/stars/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?style=flat-square&label=stars&color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan) [![coverage](https://img.shields.io/gitlab/pipeline-coverage/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?branch=main&job=test-shell-coverage&style=flat-square&label=coverage&color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan/-/jobs/artifacts/main/file/coverage/index.html?job=test-shell-coverage) [![skills](https://img.shields.io/badge/skills-000000?style=flat-square&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2IiBmaWxsPSJ3aGl0ZSI+PHBvbHlnb24gcG9pbnRzPSI4LDAgMTAsNiAxNiw4IDEwLDEwIDgsMTYgNiwxMCAwLDggNiw2Ii8+PC9zdmc+)](https://github.com/coroboros/agent-skills) [![coroboros.com](https://img.shields.io/badge/coroboros.com-000000?style=flat-square&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48cGF0aCBkPSJNMiAxMmgyME0xMiAyYTE1LjMgMTUuMyAwIDAgMSA0IDEwIDE1LjMgMTUuMyAwIDAgMS00IDEwIDE1LjMgMTUuMyAwIDAgMS00LTEwIDE1LjMgMTUuMyAwIDAgMSA0LTEweiIvPjwvc3ZnPg==)](https://coroboros.com)
## 目录 - [要求](#requirements) - [镜像](#image) - [标签](#tags) - [命令](#commands) - [运行](#run) - [报告](#reports) - [安全规则](#security-rules) - [代理](#agents) - [包](#packages) - [出处](#provenance) - [与替代方案比较](#compared-to-alternatives) - [安全](#security) - [贡献](#contributing) - [许可证](#license) ## 要求 Docker、BuildKit,或任何能够从 GitHub Container Registry(或 Docker Hub 镜像)拉取的 OCI runtime。 ## 镜像 `ghcr.io/coroboros/shellscan:{tag}` —— 镜像同步至 `docker.io/coroboros/shellscan:{tag}`,以及位于 GitLab Container Registry 的 `registry.gitlab.com/coroboros/security/infrastructure/shellscan:{tag}`。 - **架构**: `linux/amd64`, `linux/arm64` - **工作目录**: `/shellscan` - **Entrypoint**: `shellscan` - **用户**: 非特权用户 `shellscan` (uid 10000) ## 标签 | 标签 | 基础镜像 | 架构 | 大小 | 来源 | 可变性 | | --- | --- | --- | --- | --- | --- | | `` | [`koalaman/shellcheck-alpine:v0.11.0`](Dockerfile#L3) | `amd64`, `arm64` | 18.6 MB (amd64), 27.0 MB (arm64) | SemVer git tag | 不可变 | | `main` | 同 `` | 同 `` | 视构建而定 | 最新的绿色 `main` 构建 | 滚动更新 | | `` | 同 `` | 同 `` | 视构建而定 | 每次构建 | 不可变 | 大小是来自 GHCR 的压缩层总和。基础镜像的可靠来源是 `Dockerfile` 中的 `FROM` 行。固定 digest 以实现可重现的构建;参见[出处](#provenance)。 ## 命令 `shellscan [mode] [fd_options]`
mode
作为第一个位置参数传递的发现模式。 | 模式 | 行为 | | --- | --- | | `all` | 默认值。扫描以下所有情况的组合。 | | `gitlab-ci` | 扫描 `.yml` / `.yaml` 文件中 `before_script` / `script` / `after_script` 键下嵌入的脚本。YAML 锚点会被展开。 | | `github-actions` | 扫描 `jobs..steps[].run`(workflows)和 `runs.steps[].run`(composite actions)下嵌入在 `.yml` / `.yaml` 文件中的 `run` 脚本。`${{ }}` 表达式在 lint 前会被中和;shellcheck 会以该步骤的 bash/sh 方言运行,并跳过其有效 `shell:` 解析为 pwsh、python、cmd 或 node 的步骤 —— Windows runner 和仅限 windows 的矩阵默认使用 pwsh。[安全规则](#security-rules) 会在每个 `run` 脚本上运行,无论其为何种 shell。YAML 锚点会被展开。 | | `shebang` | 扫描带有 shell shebang(`sh`, `bash`, `dash`, `ksh`)的文件。 | | `.sh` | 扫描 `.sh` 文件。 | | `-h` | 打印帮助并退出。 |
fd_options
`fd` 调用的额外选项,作为一个带引号的参数传递([参考](https://github.com/sharkdp/fd#how-to-use))。选项按空白字符拆分并原样传递 —— `fd` 匹配其自身的 glob 模式,shell 永远不会展开它们。命令执行标志(`-x`, `-X`, `--exec`, `--exec-batch`)会被拒绝。
环境变量
| 变量 | 默认值 | 用途 | | --- | --- | --- | | `SHELLCHECK_OPTS` | `--color=always` | 传递给 [`shellcheck`](https://github.com/koalaman/shellcheck/blob/master/shellcheck.1.md#environment-variables) 的选项。 | | `SHELLSCAN_JOBS` | `1` | 并行扫描 worker。大于 `1` 可加快大规模扫描速度;shellcheck 输出会跨文件交错,但最终计数和退出码会保持正确。 | | `SHELLSCAN_FORMAT` | `human` | 输出格式:`human`、`codequality` (GitLab Code Quality JSON) 或 `sarif` (SARIF 2.1.0)。机器格式独占 stdout;进度信息移动到 stderr。 | | `SHELLSCAN_BASELINE` | `.shellscanignore` | 在机器格式中被抑制的检查结果指纹基线文件 —— 每行一个,允许 `#` 注释。 | | `SHELLSCAN_SECURITY` | `0` | 设为 `1` 以在嵌入 CI YAML —— GitLab CI 和 GitHub Actions 的脚本上启用[安全规则](#security-rules)。 |
退出码
| 代码 | 含义 | | --- | --- | | `0` | 扫描成功,无检查结果。 | | `1` | 报告了检查结果。 | | `2` | 用法错误、被拒绝的选项或发现失败。失败的发现会中止操作 —— 它永远不会显示为扫描了零个文件的成功扫描。 |
示例
``` shellscan gitlab-ci ``` ``` SHELLCHECK_OPTS="--severity=info --color=never" \ shellscan gitlab-ci '--exclude *.yaml --exclude .gitlab-ci.yml' ``` ``` SHELLSCAN_SECURITY=1 SHELLSCAN_FORMAT=codequality \ shellscan gitlab-ci > gl-code-quality-report.json ``` ``` SHELLSCAN_SECURITY=1 SHELLSCAN_FORMAT=sarif \ shellscan github-actions > shellscan.sarif ```
## 运行
GitLab CI
``` check-sh-files: image: name: registry.gitlab.com/coroboros/security/infrastructure/shellscan: entrypoint: [""] stage: check variables: SHELLCHECK_OPTS: >- --severity=warning --color=never script: - shellscan .sh ``` ``` check-ci-yaml-files: image: name: registry.gitlab.com/coroboros/security/infrastructure/shellscan: entrypoint: [""] stage: check script: - shellscan gitlab-ci ``` ``` check-files-with-shebang: image: name: registry.gitlab.com/coroboros/security/infrastructure/shellscan: entrypoint: [""] stage: check script: - cd .. - shellscan shebang '--exclude *.sh' ``` ``` parallel-scan: image: name: registry.gitlab.com/coroboros/security/infrastructure/shellscan: entrypoint: [""] stage: check variables: SHELLSCAN_JOBS: "4" script: - shellscan all ```
CI/CD 组件
一个 include 即可运行扫描并为合并请求小部件发布 Code Quality 报告。默认情况下,检查结果是非阻塞的;设置 `fail_on_findings: true` 以对流水线进行门控。 ``` include: - component: gitlab.com/coroboros/security/infrastructure/shellscan/shellscan@ inputs: security: true ``` 最后的 `/shellscan` 片段是来自 `templates/shellscan.yml` 的组件名称;GitLab 组件的引用格式为 `//@`。 输入项:`job_name`, `stage`, `mode`, `fd_options`, `security`, `fail_on_findings`, `jobs`, `shellcheck_opts`, `baseline`, `image` —— 参见 [`templates/shellscan.yml`](templates/shellscan.yml)。
GitHub Actions
同一个镜像扫描 GitHub 代码库 —— `github-actions` 模式会 lint workflows 本身,并且 SARIF 报告会汇入代码扫描: ``` jobs: shellscan: runs-on: ubuntu-latest permissions: security-events: write steps: - uses: actions/checkout@v4 - name: scan run: | docker run --rm -v "$PWD:/shellscan" \ -e SHELLSCAN_SECURITY=1 -e SHELLSCAN_FORMAT=sarif \ ghcr.io/coroboros/shellscan: all > shellscan.sarif || [ $? -eq 1 ] - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: shellscan.sarif ```
pre-commit
该 hook 基于固定的代码库 `rev` 构建 —— Docker 是唯一的本地 runtime 依赖。 ``` repos: - repo: https://gitlab.com/coroboros/security/infrastructure/shellscan rev: hooks: - id: shellscan ```
Docker
将项目挂载为 `/shellscan` 卷并运行任意模式: ``` docker run --rm \ -v "$PWD:/shellscan" \ registry.gitlab.com/coroboros/security/infrastructure/shellscan: ``` ``` docker run --rm \ -v "$PWD:/shellscan" \ registry.gitlab.com/coroboros/security/infrastructure/shellscan: \ gitlab-ci '--exclude "*.yaml"' ```
## 报告 `SHELLSCAN_FORMAT=codequality` 输出 [GitLab Code Quality JSON](https://docs.gitlab.com/ci/testing/code_quality/);`SHELLSCAN_FORMAT=sarif` 输出用于 GitHub 代码扫描的 [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)。这两种格式都将报告写入 stdout,并将进度信息移动到 stderr。来自嵌入在 CI YAML 中脚本的检查结果带有 YAML 源代码行及其选择器 —— GitLab CI 的 `before_script` / `script` / `after_script`,GitHub Actions 的 `jobs..steps[].run` —— 因此标注会落在审查者实际阅读的 YAML 行上。 将 Code Quality 报告接入到作业中,检查结果就会显示在合并请求小部件中: ``` shellscan: image: name: registry.gitlab.com/coroboros/security/infrastructure/shellscan: entrypoint: [""] stage: check variables: SHELLSCAN_FORMAT: codequality script: - shellscan gitlab-ci > gl-code-quality-report.json || [ $? -eq 1 ] artifacts: when: always reports: codequality: gl-code-quality-report.json ``` `|| [ $? -eq 1 ]` 保持检查结果处于非阻塞状态,而发现失败(退出码 `2`)仍会导致作业失败。去掉它可以在发现检查结果时对流水线进行门控。 每个检查结果都带有其文件、规则和消息的稳定 SHA-256 指纹。在 `.shellscanignore` 中列出指纹(每行一个,允许 `#` 注释)以抑制已知的检查结果 —— 在不首先修复遗留树中数十年历史的 shell 的情况下采用 shellscan,并让门控仅捕获新内容。 ## 安全规则 `SHELLSCAN_SECURITY=1` 添加了针对 CI YAML —— GitLab CI 和 GitHub Actions —— 内部脚本的规则,这是 shellcheck 无法提供意见的注入面。 | 规则 | 严重性 | 标记内容 | | --- | --- | --- | | `SHELLSCAN-CURL-PIPE` | critical | 通过管道传递给 shell 的 `curl` 或 `wget` —— 未经验证即执行远程代码。 | | `SHELLSCAN-EVAL` | major | 对展开的值使用 `eval` —— 动态输入作为代码运行。 | | `SHELLSCAN-SECRET-ECHO` | major | 对以 secret 命名的变量使用 `echo` / `printf` —— 密钥会出现在作业日志中。 | | `SHELLSCAN-CI-INJECTION` | major | 未加引号且受攻击者控制的 CI 变量(`CI_COMMIT_MESSAGE`、`CI_MERGE_REQUEST_TITLE`、分支和标签名称) —— 精心构造的提交会将 shell 语法注入到作业中。 | | `SHELLSCAN-GHA-INJECTION` | critical | `run` 脚本内包含受攻击者控制数据(`github.event.pull_request.title`、`github.head_ref`、提交消息)的 `${{ }}` 表达式 —— 在任何 shell 解析之前被替换,因此引号无济于事;请通过环境变量传递它。 | 每个检查结果都会报告 YAML 源代码行。这些规则仅在提取的脚本上运行 —— `.sh` 和 shebang 文件已经获得了完整的 shellcheck 处理。注入规则是针对特定平台的:`SHELLSCAN-CI-INJECTION` 在 GitLab CI 脚本上触发,`SHELLSCAN-G-INJECTION` 在 GitHub Actions `run` 脚本上触发 —— GitLab 脚本中的字面量 `${{ }}` 是惰性文本,永远不会被标记。 ## 代理 shellscan 为编码代理提供了一个代理技能(agent skill)—— 它自己的扫描和分流指南。将其安装到代理中: ``` npx skills add https://gitlab.com/coroboros/security/infrastructure/shellscan ``` 或者在不安装的情况下阅读:[`skills/shellscan/SKILL.md`](skills/shellscan/SKILL.md)。 ## 包 在所有 shellscan 标签中相同。 | 包 | 用途 | | --- | --- | | `shellcheck` | linter —— 由 `koalaman/shellcheck-alpine` 基础镜像提供。 | | `bash` | shell —— `src/shellscan.sh` 在 bash 上运行。 | | `fd` | 快速文件发现 —— 用于枚举要扫描的文件。 | | `yq` | YAML 处理器 —— 从 `before_script` / `script` / `after_script` 和 `run` 键中提取脚本并展开锚点。 | | `jq` | JSON 处理器 —— 根据 shellcheck `json1` 输出渲染 Code Quality 和 SARIF 报告。 | | `ca-certificates` | TLS 证书包。 | ## 出处 通过共享的 [`coroboros/ci`](https://gitlab.com/coroboros/ci) `container-images` 模板发布的每个镜像都: - **多架构** —— BuildKit,`linux/amd64,linux/arm64`; - **门控** —— 通过 `gitleaks` 获取源代码密钥,通过 Trivy 在已发布的 `:sha` 上检查镜像 CVE,并在 tag 提升前对镜像进行冒烟测试; - **签名** —— 在不可变的 digest 上进行 cosign 无密钥签名,并带有 **CycloneDX SBOM** 证明。 签名的 digest 发布到 `ghcr.io/coroboros/shellscan` 并在版本标签上镜像到 `docker.io/coroboros/shellscan`。在下游固定 `@sha256` digest 以实现字节可重现的扫描。 cosign 签名绑定到 digest,而不是 tag —— 验证固定的 digest: ``` cosign verify \ --certificate-identity-regexp 'https://gitlab.com/coroboros/security/infrastructure/shellscan//.*' \ --certificate-oidc-issuer https://gitlab.com \ ghcr.io/coroboros/shellscan@sha256: ``` ## 与替代方案比较 | 功能 | 直接使用 `shellcheck` | `yamllint` | `pre-commit` + shellcheck | GitLab CI Lint | `super-linter` | `actionlint` | **`shellscan`** | | --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | | Lint `.sh` 文件 | 是 | 否 | 是 | 否 | 是 | 否 | 是 | | 通过 shebang 检测 Lint 文件 | 否 | 否 | 否 | 否 | 是 | 否 | 是 | | 提取 GitLab CI YAML 中嵌入的 shell | 否 | 否 | 否 | 否 | 否 | 否 | 是 | | 提取 GitHub Actions YAML 中嵌入的 shell | 否 | 否 | 否 | 否 | 通过 `actionlint` | 是 | 是 | | 扫描前展开 YAML 锚点 | 否 | 否 | 否 | 否 | 否 | 是 (workflows) | 是 | | 单个二进制文件 / 无需编排设置 | 是 | 是 | 否 | 是 | 否 | 是 | 是 (镜像) | | 可配置的文件发现(`fd` 选项) | 否 | 否 | 否 | 否 | 否 | 否 | 是 | | 并行扫描(可配置 worker) | 否 | 否 | 否 | 否 | 否 | 自动 | 是 | | GitLab Code Quality + SARIF 输出 | 否 | 否 | 否 | 否 | 否 | 通过模板输出 SARIF | 是 | | 针对嵌入 CI 的 shell 的安全规则 | 否 | 否 | 否 | 否 | 否 | GH 不受信任的输入 | 是 | | 用于增量采用的基线文件 | 否 | 否 | 否 | 否 | 否 | 否 | 是 | 独特之处在于:shellscan 能找到 shell 在项目中的**任何藏身之处** —— 包括隐藏在 CI YAML 中经常被忽视的脚本 —— 在展开 YAML 锚点的情况下通过标准的 `shellcheck` linter 运行它,并在审查者查看的地方报告检查结果:GitLab Code Quality、GitHub 代码扫描或终端。[`actionlint`](https://github.com/rhysd/actionlint) 证明了对 GitHub Actions 上的 CI 配置内的 shell 进行 lint 的需求;shellscan 在一个扫描器中涵盖了两个平台,并在顶层添加了具备平台感知能力的安全规则。 ## 安全 通过[安全策略](SECURITY.md) 私下报告漏洞 —— **ob@coroboros.com**,请勿使用公开 issue。 ## 贡献 欢迎提交 Bug 报告和合并请求。 - 在提交非简单的合并请求之前,请先创建一个 issue。 - 提交遵循 [Conventional Commits](https://www.conventionalcommits.org/)。 - 签署每个提交 (DCO):`git commit -s`。 - 目标分支为 `main`。 在本地运行单元测试: ``` bash test/unit.sh ``` 覆盖率报告(需要 Ruby + Bundler): ``` bundle install bundle exec bashcov --command-name shellscan --mute test/coverage.sh ``` ## 许可证 [Apache 2.0](LICENSE.md)
标签:Cutter, DevSecOps, Shell, 上游代理, 自动化检查, 请求拦截, 错误基检测, 静态代码分析