{"id":51529586,"url":"https://github.com/coroboros/shellscan","last_synced_at":"2026-07-09T01:30:37.352Z","repository":{"id":365864636,"uuid":"1273421630","full_name":"coroboros/shellscan","owner":"coroboros","description":"GitLab mirror of shellscan — finds shell in files, shebangs, GitLab CI, and GitHub Actions.","archived":false,"fork":false,"pushed_at":"2026-06-19T07:44:19.000Z","size":158,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T09:23:18.591Z","etag":null,"topics":["alpine","ci","code-quality","container-image","coroboros","devsecops","github-actions","gitlab-ci","linter","sarif","shell","shell-script","shellcheck","static-analysis"],"latest_commit_sha":null,"homepage":"https://coroboros.com","language":"Shell","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coroboros.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-18T14:02:45.000Z","updated_at":"2026-06-19T07:44:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/coroboros/shellscan","commit_stats":null,"previous_names":["coroboros/shellscan"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/coroboros/shellscan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coroboros%2Fshellscan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coroboros%2Fshellscan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coroboros%2Fshellscan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coroboros%2Fshellscan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coroboros","download_url":"https://codeload.github.com/coroboros/shellscan/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coroboros%2Fshellscan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35283905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alpine","ci","code-quality","container-image","coroboros","devsecops","github-actions","gitlab-ci","linter","sarif","shell","shell-script","shellcheck","static-analysis"],"created_at":"2026-07-09T01:30:36.773Z","updated_at":"2026-07-09T01:30:37.342Z","avatar_url":"https://github.com/coroboros.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/logo.png\" width=\"288\" height=\"288\" alt=\"shellscan\"/\u003e\n\n\u003c!-- omit in toc --\u003e\n# shellscan\n\n**Find and lint every shell in a project — `.sh` files, shebangs, and scripts embedded in GitLab CI and GitHub Actions YAML.**\n\nAn Alpine-based image wraps [`shellcheck`](https://github.com/koalaman/shellcheck) with four discovery modes: `.sh` files, files with a shell shebang (`/bin/bash` and `env bash` forms), and shell scripts embedded in CI YAML — GitLab CI `before_script` / `script` / `after_script` keys and GitHub Actions `run` steps — extracted via [`yq`](https://github.com/mikefarah/yq), with YAML anchors expanded. File discovery uses [`fd`](https://github.com/sharkdp/fd). Findings render as terminal output, [GitLab Code Quality JSON for the merge request widget, or SARIF 2.1.0 for GitHub code scanning](#reports); embedded-script findings map back to their YAML source lines. Opt-in [security rules](#security-rules) flag remote code piped into a shell, secrets echoed to job logs, unquoted attacker-controllable CI variables, and GitHub expressions injecting untrusted data into `run` scripts.\n\n[![latest](https://img.shields.io/gitlab/v/release/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?style=flat-square\u0026label=latest\u0026color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan/-/releases)\n[![pipeline](https://img.shields.io/gitlab/pipeline-status/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?branch=main\u0026style=flat-square\u0026label=pipeline\u0026color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan/-/pipelines)\n[![ghcr.io](https://img.shields.io/badge/ghcr.io-shellscan-000000?style=flat-square\u0026logo=github)](https://github.com/orgs/coroboros/packages/container/package/shellscan)\n[![Docker Hub](https://img.shields.io/badge/docker_hub-shellscan-000000?style=flat-square\u0026logo=docker\u0026logoColor=white)](https://hub.docker.com/r/coroboros/shellscan)\n[![license](https://img.shields.io/badge/license-Apache_2.0-000000?style=flat-square)](LICENSE.md)\n[![stars](https://img.shields.io/gitlab/stars/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?style=flat-square\u0026label=stars\u0026color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan)\n[![coverage](https://img.shields.io/gitlab/pipeline-coverage/coroboros%2Fsecurity%2Finfrastructure%2Fshellscan?branch=main\u0026job=test-shell-coverage\u0026style=flat-square\u0026label=coverage\u0026color=000000)](https://gitlab.com/coroboros/security/infrastructure/shellscan/-/jobs/artifacts/main/file/coverage/index.html?job=test-shell-coverage)\n[![skills](https://img.shields.io/badge/skills-000000?style=flat-square\u0026logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2IiBmaWxsPSJ3aGl0ZSI+PHBvbHlnb24gcG9pbnRzPSI4LDAgMTAsNiAxNiw4IDEwLDEwIDgsMTYgNiwxMCAwLDggNiw2Ii8+PC9zdmc+)](https://github.com/coroboros/agent-skills)\n[![coroboros.com](https://img.shields.io/badge/coroboros.com-000000?style=flat-square\u0026logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48cGF0aCBkPSJNMiAxMmgyME0xMiAyYTE1LjMgMTUuMyAwIDAgMSA0IDEwIDE1LjMgMTUuMyAwIDAgMS00IDEwIDE1LjMgMTUuMyAwIDAgMS00LTEwIDE1LjMgMTUuMyAwIDAgMSA0LTEweiIvPjwvc3ZnPg==)](https://coroboros.com)\n\n\u003c/div\u003e\n\n\u003c!-- omit in toc --\u003e\n## Contents\n\n- [Requirements](#requirements)\n- [Image](#image)\n- [Tags](#tags)\n- [Commands](#commands)\n- [Run](#run)\n- [Reports](#reports)\n- [Security rules](#security-rules)\n- [Agents](#agents)\n- [Packages](#packages)\n- [Provenance](#provenance)\n- [Compared to alternatives](#compared-to-alternatives)\n- [Security](#security)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Requirements\n\nDocker, BuildKit, or any OCI runtime able to pull from the GitHub Container Registry (or the Docker Hub mirror).\n\n## Image\n\n`ghcr.io/coroboros/shellscan:{tag}` — mirrored to `docker.io/coroboros/shellscan:{tag}`, and in the GitLab Container Registry at `registry.gitlab.com/coroboros/security/infrastructure/shellscan:{tag}`.\n\n- **Architectures**: `linux/amd64`, `linux/arm64`\n- **Working directory**: `/shellscan`\n- **Entrypoint**: `shellscan`\n- **User**: non-root `shellscan` (uid 10000)\n\n## Tags\n\n| Tag | Base | Architectures | Size | Source | Mutability |\n| --- | --- | --- | --- | --- | --- |\n| `\u003cversion\u003e` | [`koalaman/shellcheck-alpine:v0.11.0`](Dockerfile#L3) | `amd64`, `arm64` | 18.6 MB (amd64), 27.0 MB (arm64) | SemVer git tag | immutable |\n| `main` | same as `\u003cversion\u003e` | same as `\u003cversion\u003e` | varies by build | latest green `main` build | rolling |\n| `\u003csha\u003e` | same as `\u003cversion\u003e` | same as `\u003cversion\u003e` | varies by build | every build | immutable |\n\nSizes are compressed layer sums from GHCR. The base image source of truth is the `FROM` line in the `Dockerfile`. Pin a digest for reproducible builds; see [Provenance](#provenance).\n\n## Commands\n\n`shellscan [mode] [fd_options]`\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003emode\u003c/code\u003e\u003c/summary\u003e\n\n\u003cbr\u003e\n\nDiscovery mode passed as the first positional argument.\n\n| Mode | Behavior |\n| --- | --- |\n| `all` | Default. Scan every situation below combined. |\n| `gitlab-ci` | Scan scripts embedded in `.yml` / `.yaml` files under `before_script` / `script` / `after_script` keys. YAML anchors are expanded. |\n| `github-actions` | Scan `run` scripts embedded in `.yml` / `.yaml` files under `jobs.\u003cid\u003e.steps[].run` (workflows) and `runs.steps[].run` (composite actions). `${{ }}` expressions are neutralized before linting; shellcheck runs with the step's bash/sh dialect and skips steps whose effective `shell:` resolves to pwsh, python, cmd, or node — Windows runners and windows-only matrices default to pwsh. The [security rules](#security-rules) run on every `run` script regardless. YAML anchors are expanded. |\n| `shebang` | Scan files with a shell shebang (`sh`, `bash`, `dash`, `ksh`). |\n| `.sh` | Scan `.sh` files. |\n| `-h` | Print help and exit. |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003efd_options\u003c/code\u003e\u003c/summary\u003e\n\n\u003cbr\u003e\n\nExtra options for the `fd` invocation, passed as one quoted argument ([reference](https://github.com/sharkdp/fd#how-to-use)). Options are whitespace-split and passed verbatim — `fd` matches its own glob patterns, the shell never expands them. Command-execution flags (`-x`, `-X`, `--exec`, `--exec-batch`) are refused.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eEnvironment variables\u003c/code\u003e\u003c/summary\u003e\n\n\u003cbr\u003e\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `SHELLCHECK_OPTS` | `--color=always` | Options passed to [`shellcheck`](https://github.com/koalaman/shellcheck/blob/master/shellcheck.1.md#environment-variables). |\n| `SHELLSCAN_JOBS` | `1` | Parallel scan workers. Above `1` speeds up large scans; shellcheck output interleaves across files, final counts and exit code stay correct. |\n| `SHELLSCAN_FORMAT` | `human` | Output format: `human`, `codequality` (GitLab Code Quality JSON), or `sarif` (SARIF 2.1.0). Machine formats own stdout; progress moves to stderr. |\n| `SHELLSCAN_BASELINE` | `.shellscanignore` | Baseline file of finding fingerprints suppressed in machine formats — one per line, `#` comments allowed. |\n| `SHELLSCAN_SECURITY` | `0` | Set to `1` to enable the [security rules](#security-rules) on scripts embedded in CI YAML — GitLab CI and GitHub Actions. |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eExit codes\u003c/code\u003e\u003c/summary\u003e\n\n\u003cbr\u003e\n\n| Code | Meaning |\n| --- | --- |\n| `0` | Scan succeeded, no findings. |\n| `1` | Findings reported. |\n| `2` | Usage error, refused option, or discovery failure. A failed discovery aborts — it never reads as a clean scan of zero files. |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eExamples\u003c/code\u003e\u003c/summary\u003e\n\n\u003cbr\u003e\n\n```shell\nshellscan gitlab-ci\n```\n\n```shell\nSHELLCHECK_OPTS=\"--severity=info --color=never\" \\\nshellscan gitlab-ci '--exclude *.yaml --exclude .gitlab-ci.yml'\n```\n\n```shell\nSHELLSCAN_SECURITY=1 SHELLSCAN_FORMAT=codequality \\\nshellscan gitlab-ci \u003e gl-code-quality-report.json\n```\n\n```shell\nSHELLSCAN_SECURITY=1 SHELLSCAN_FORMAT=sarif \\\nshellscan github-actions \u003e shellscan.sarif\n```\n\n\u003c/details\u003e\n\n## Run\n\n\u003cdetails\u003e\n\u003csummary\u003eGitLab CI\u003c/summary\u003e\n\n\u003cbr\u003e\n\n```yaml\ncheck-sh-files:\n  image:\n    name: registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e\n    entrypoint: [\"\"]\n  stage: check\n  variables:\n    SHELLCHECK_OPTS: \u003e-\n      --severity=warning\n      --color=never\n  script:\n    - shellscan .sh\n```\n\n```yaml\ncheck-ci-yaml-files:\n  image:\n    name: registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e\n    entrypoint: [\"\"]\n  stage: check\n  script:\n    - shellscan gitlab-ci\n```\n\n```yaml\ncheck-files-with-shebang:\n  image:\n    name: registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e\n    entrypoint: [\"\"]\n  stage: check\n  script:\n    - cd ..\n    - shellscan shebang '--exclude *.sh'\n```\n\n```yaml\nparallel-scan:\n  image:\n    name: registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e\n    entrypoint: [\"\"]\n  stage: check\n  variables:\n    SHELLSCAN_JOBS: \"4\"\n  script:\n    - shellscan all\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCI/CD component\u003c/summary\u003e\n\n\u003cbr\u003e\n\nOne include runs the scan and publishes a Code Quality report for the merge request widget. Findings are non-blocking by default; set `fail_on_findings: true` to gate the pipeline.\n\n```yaml\ninclude:\n  - component: gitlab.com/coroboros/security/infrastructure/shellscan/shellscan@\u003cversion\u003e\n    inputs:\n      security: true\n```\n\nThe final `/shellscan` segment is the component name from `templates/shellscan.yml`; GitLab component refs are `\u003cfqdn\u003e/\u003cproject-path\u003e/\u003ccomponent-name\u003e@\u003cversion\u003e`.\n\nInputs: `job_name`, `stage`, `mode`, `fd_options`, `security`, `fail_on_findings`, `jobs`, `shellcheck_opts`, `baseline`, `image` — see [`templates/shellscan.yml`](templates/shellscan.yml).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eGitHub Actions\u003c/summary\u003e\n\n\u003cbr\u003e\n\nThe same image scans a GitHub repository — `github-actions` mode lints the workflows themselves, and the SARIF report lands in code scanning:\n\n```yaml\njobs:\n  shellscan:\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n    steps:\n      - uses: actions/checkout@v4\n      - name: scan\n        run: |\n          docker run --rm -v \"$PWD:/shellscan\" \\\n            -e SHELLSCAN_SECURITY=1 -e SHELLSCAN_FORMAT=sarif \\\n            ghcr.io/coroboros/shellscan:\u003ctag\u003e all \u003e shellscan.sarif || [ $? -eq 1 ]\n      - uses: github/codeql-action/upload-sarif@v3\n        with:\n          sarif_file: shellscan.sarif\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003epre-commit\u003c/summary\u003e\n\n\u003cbr\u003e\n\nThe hook builds from the pinned repo `rev` — Docker is the only local runtime dependency.\n\n```yaml\nrepos:\n  - repo: https://gitlab.com/coroboros/security/infrastructure/shellscan\n    rev: \u003cversion\u003e\n    hooks:\n      - id: shellscan\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eDocker\u003c/summary\u003e\n\n\u003cbr\u003e\n\nMount the project as the `/shellscan` volume and run any mode:\n\n```shell\ndocker run --rm \\\n  -v \"$PWD:/shellscan\" \\\n  registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e\n```\n\n```shell\ndocker run --rm \\\n  -v \"$PWD:/shellscan\" \\\n  registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e \\\n  gitlab-ci '--exclude \"*.yaml\"'\n```\n\n\u003c/details\u003e\n\n## Reports\n\n`SHELLSCAN_FORMAT=codequality` emits [GitLab Code Quality JSON](https://docs.gitlab.com/ci/testing/code_quality/); `SHELLSCAN_FORMAT=sarif` emits [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html) for GitHub code scanning. Both formats write the report to stdout and move progress to stderr. Findings from scripts embedded in CI YAML carry the YAML source line and their selector — `before_script` / `script` / `after_script` for GitLab CI, `jobs.\u003cid\u003e.steps[].run` for GitHub Actions — so annotations land on the YAML line a reviewer actually reads.\n\nWire the Code Quality report into a job and findings surface in the merge request widget:\n\n```yaml\nshellscan:\n  image:\n    name: registry.gitlab.com/coroboros/security/infrastructure/shellscan:\u003ctag\u003e\n    entrypoint: [\"\"]\n  stage: check\n  variables:\n    SHELLSCAN_FORMAT: codequality\n  script:\n    - shellscan gitlab-ci \u003e gl-code-quality-report.json || [ $? -eq 1 ]\n  artifacts:\n    when: always\n    reports:\n      codequality: gl-code-quality-report.json\n```\n\n`|| [ $? -eq 1 ]` keeps findings non-blocking while discovery failures (exit `2`) still fail the job. Drop it to gate the pipeline on findings.\n\nEvery finding carries a stable SHA-256 fingerprint of its file, rule, and message. List fingerprints in `.shellscanignore` (one per line, `#` comments allowed) to suppress known findings — adopt shellscan on a legacy tree without fixing decades of shell first, and let the gate catch only what is new.\n\n## Security rules\n\n`SHELLSCAN_SECURITY=1` adds rules that target the scripts inside CI YAML — GitLab CI and GitHub Actions — the injection surface shellcheck has no opinion on.\n\n| Rule | Severity | Flags |\n| --- | --- | --- |\n| `SHELLSCAN-CURL-PIPE` | critical | `curl` or `wget` piped into a shell — remote code executed unverified. |\n| `SHELLSCAN-EVAL` | major | `eval` on an expanded value — dynamic input runs as code. |\n| `SHELLSCAN-SECRET-ECHO` | major | `echo` / `printf` of a secret-named variable — the secret lands in job logs. |\n| `SHELLSCAN-CI-INJECTION` | major | Unquoted attacker-controllable CI variable (`CI_COMMIT_MESSAGE`, `CI_MERGE_REQUEST_TITLE`, branch and tag names) — a crafted commit injects shell syntax into the job. |\n| `SHELLSCAN-GHA-INJECTION` | critical | `${{ }}` expression of attacker-controllable data (`github.event.pull_request.title`, `github.head_ref`, commit messages) inside a `run` script — substituted before any shell parses, so quoting cannot help; pass it through an environment variable. |\n\nEach finding reports the YAML source line. The rules run on extracted scripts only — `.sh` and shebang files already get the full shellcheck treatment. The injection rules are platform-scoped: `SHELLSCAN-CI-INJECTION` fires on GitLab CI scripts, `SHELLSCAN-GHA-INJECTION` on GitHub Actions `run` scripts — a literal `${{ }}` in a GitLab script is inert text and never flagged.\n\n## Agents\n\nshellscan ships an agent skill — its own scan-and-triage guide — for coding agents. Install it into an agent:\n\n```sh\nnpx skills add coroboros/shellscan\n```\n\nOr read it without installing: [`skills/shellscan/SKILL.md`](skills/shellscan/SKILL.md).\n\n## Packages\n\nSame across all shellscan tags.\n\n| Package | Purpose |\n| --- | --- |\n| `shellcheck` | The linter — provided by the `koalaman/shellcheck-alpine` base image. |\n| `bash` | Shell — `src/shellscan.sh` runs on bash. |\n| `fd` | Fast file discovery — used to enumerate files to scan. |\n| `yq` | YAML processor — extracts scripts from `before_script` / `script` / `after_script` and `run` keys and expands anchors. |\n| `jq` | JSON processor — renders Code Quality and SARIF reports from shellcheck `json1` output. |\n| `ca-certificates` | TLS certificate bundle. |\n\n## Provenance\n\nEvery published image, via the shared [`coroboros/ci`](https://gitlab.com/coroboros/ci) `container-images` template, is:\n\n- **multi-arch** — BuildKit, `linux/amd64,linux/arm64`;\n- **gated** — source secrets via `gitleaks`, image CVEs via Trivy on the published `:sha`, and the image smoke before tag promotion;\n- **signed** — cosign keyless on the immutable digest, with a **CycloneDX SBOM** attestation.\n\nOn version tags, the promoted digest is published to the GitLab Registry, GitHub Container Registry, and Docker Hub. Each registry-local digest is signed and attested after copy. Pin the `@sha256` digest downstream for byte-reproducible scans.\n\nA cosign signature is bound to the digest, not the tag — verify the pinned digest:\n\n```sh\ncosign verify \\\n  --certificate-identity-regexp 'https://gitlab.com/coroboros/security/infrastructure/shellscan//.*' \\\n  --certificate-oidc-issuer https://gitlab.com \\\n  ghcr.io/coroboros/shellscan@sha256:\u003cmanifest-list-digest\u003e\n```\n\n## Compared to alternatives\n\n| Capability | `shellcheck` direct | `yamllint` | `pre-commit` + shellcheck | GitLab CI Lint | `super-linter` | `actionlint` | **`shellscan`** |\n| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |\n| Lint `.sh` files | yes | no | yes | no | yes | no | yes |\n| Lint files via shebang detection | no | no | no | no | yes | no | yes |\n| Extract shells embedded in GitLab CI YAML | no | no | no | no | no | no | yes |\n| Extract shells embedded in GitHub Actions YAML | no | no | no | no | via `actionlint` | yes | yes |\n| Expand YAML anchors before scanning | no | no | no | no | no | yes (workflows) | yes |\n| Single binary / no orchestration setup | yes | yes | no | yes | no | yes | yes (image) |\n| Configurable file discovery (`fd` options) | no | no | no | no | no | no | yes |\n| Parallel scan (configurable workers) | no | no | no | no | no | auto | yes |\n| GitLab Code Quality + SARIF output | no | no | no | no | no | SARIF via template | yes |\n| Security rules on CI-embedded shell | no | no | no | no | no | GH untrusted inputs | yes |\n| Baseline file for incremental adoption | no | no | no | no | no | no | yes |\n\nThe unique angle: shellscan finds shell **wherever it lives** in a project — including the often-overlooked scripts hidden inside CI YAML — runs it through the canonical `shellcheck` linter with YAML anchors expanded, and reports findings where reviewers look: GitLab Code Quality, GitHub code scanning, or the terminal. [`actionlint`](https://github.com/rhysd/actionlint) proved the demand for linting shell inside CI config on GitHub Actions; shellscan covers both platforms in one scanner, with platform-aware security rules on top.\n\n## Security\n\nReport a vulnerability privately via the [security policy](SECURITY.md) — **ob@coroboros.com**, never a public issue.\n\n## Contributing\n\nBug reports and merge requests welcome.\n\n- Open an issue before submitting non-trivial merge requests.\n- Commits follow [Conventional Commits](https://www.conventionalcommits.org/).\n- Sign off each commit (DCO): `git commit -s`.\n- Target the `main` branch.\n\nRun the unit tests locally:\n\n```shell\nbash test/unit.sh\n```\n\nCoverage report (requires Ruby + Bundler):\n\n```shell\nbundle install\nbundle exec bashcov --command-name shellscan --mute test/coverage.sh\n```\n\n## License\n\n[Apache 2.0](LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoroboros%2Fshellscan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoroboros%2Fshellscan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoroboros%2Fshellscan/lists"}