shell-check¶
Run packaged Gherkin shell standards for CI scripts (not the ShellCheck tool).
Validates before_script/script/after_script using builtin GLCI-SHELL-* policies.
Does not install, detect, or invoke the external ShellCheck binary.
See it in action¶
Offline demos use the fixtures under docs/demos/fixtures/shell-check/.
Regenerate with bash scripts/record-demos.sh offline (see
demos README).
Console report¶

Markdown report¶

What it checks¶
Packaged policies under src/compliance/builtin_policies/shell/ express
ShellCheck-inspired standards as BDD scenarios with GLCI-SHELL-* IDs:
- Quoting, error handling, file operations, command substitution
- Conditionals, pipelines, security, portability
- GitLab CI pinning (package managers, checksums, docker image tags), CI hygiene,
!referenceresolution
Scripts are composed from extends, YAML anchors, and !reference before
scenarios run. Hidden jobs (names starting with .) are included.
Quoting and expansions¶
Variable and command-substitution checks use a POSIX-aligned scanner
(Shell Command Language §2.2–2.3):
characters inside $(...) are not affected by enclosing double quotes, so
this is correctly treated as quoted:
check="$(curl "$HOSTNAME" | jq -r '.message')"
Bash CI scripts (no shebang, or #!/bin/bash) also recognize common bash forms
from typical cheat sheets (${foo:-default}, [[ ... ]], process substitution
<(...), $'...'). Under #!/bin/sh, those remain portability bashisms.
Quick start¶
gitlab-compliance shell-check -p .gitlab-ci.yml
Show only failures (keeps summary counts):
gitlab-compliance shell-check -p .gitlab-ci.yml --failures-only
Include the offending script value that triggered each failure:
gitlab-compliance shell-check -p .gitlab-ci.yml --failures-only -v
Run specific policies by ID, glob, or feature-file stem:
gitlab-compliance shell-check -p .gitlab-ci.yml -P GLCI-SHELL-PIN-003
gitlab-compliance shell-check -p .gitlab-ci.yml -P 'GLCI-SHELL-PIN*,GLCI-SHELL-QUOTE-001'
gitlab-compliance shell-check -p .gitlab-ci.yml -P shell-quoting
Markdown report:
gitlab-compliance shell-check -p .gitlab-ci.yml --format markdown -o SHELL-CHECK.md
JUnit report for GitLab CI test artifacts:
gitlab-compliance shell-check -p .gitlab-ci.yml --format junit -o SHELL-CHECK.xml
# .gitlab-ci.yml excerpt
script:
- gitlab-compliance shell-check -p .gitlab-ci.yml --format junit -o SHELL-CHECK.xml
artifacts:
reports:
junit: SHELL-CHECK.xml
Also available via check --with-shell-check (adds packaged GLCI-SHELL-*
policies alongside your -f directory). --with-builtin does not include
shell policies; add --with-shell-check explicitly when needed.
See Shell pinning examples for per-manager
Gherkin scenarios (apk, apt, yum/dnf, pip, npm, go, docker).
Include resolution and GitLab API¶
shell-check uses the same include and API flags as check:
| Include type | Resolved locally | Fetched with token / HTTP |
|---|---|---|
local: |
Yes (--include-nested, default on) |
N/A |
remote: |
No | Yes (--resolve-external-includes, default auto) |
project: |
No | Yes (requires --token or GITLAB_TOKEN) |
component: |
No | Not yet (reported as coverage gap) |
template: |
No | Not yet (reported as coverage gap) |
Flags:
--include-nested/--no-include-nested— walk nested localinclude:files (default: on)--max-include-depth— limit include nesting depth--resolve-external-includes/--no-resolve-external-includes— fetchremote:andproject:YAML (default: auto — remote always, project when a token is available)--gitlab-url,--token,--project,--group— GitLab API connection (same env fallbacks ascheck:GITLAB_TOKEN,CI_JOB_TOKEN,CI_PROJECT_PATH)--strict— fail API-backed scenarios when credentials are missing (default: skip)--failures-only— show only failed policies (summary counts are kept; passed/skipped sections are omitted)--verbose/-v— append the offending script value (found: …) to each failure message--policy/-P— run only matching policies (repeatable or comma-separated; matches policy IDs or feature stems; supports globs such asGLCI-SHELL-PIN*)
Tokens are only sent on remote: HTTP fetches when the remote URL host matches
--gitlab-url (or CI_SERVER_URL / GITLAB_URL). Arbitrary third-party remotes
are fetched without authentication.
Local includes are resolved on disk without a token. Nested local: includes
inside fetched remote: / project: YAML are resolved relative to that origin
(URL directory or same project/ref). When an include cannot be resolved,
shell-check prints an Include coverage gap warning and lists the entry in
markdown/HTML reports under Coverage gaps.
gitlab-compliance shell-check -p .gitlab-ci.yml \
--include-nested \
--resolve-external-includes \
--token "$GITLAB_TOKEN" \
--project "$CI_PROJECT_PATH"
GitLab CI example with external project includes:
shell-check:
image: python:3.12
script:
- pip install gitlab-compliance
- gitlab-compliance shell-check -p .gitlab-ci.yml
--resolve-external-includes
--token "$GITLAB_TOKEN"
--format markdown -o SHELL-CHECK.md
artifacts:
paths:
- SHELL-CHECK.md
Built-in Gherkin policies¶
Packaged scenarios live under src/compliance/builtin_policies/shell/:
| File | Focus |
|---|---|
shell-quoting.feature |
Variable and word splitting |
shell-error-handling.feature |
set -e, exit codes, traps |
shell-file-operations.feature |
Paths, redirects, temp files |
shell-command-substitution.feature |
` and $() usage |
shell-conditionals.feature |
if/test/[ patterns |
shell-pipelines.feature |
Pipes and pipeline exit status |
shell-security.feature |
Curl pipes, secrets, unsafe commands |
shell-portability.feature |
Bashisms and POSIX portability |
shell-ci-conventions.feature |
CI script hygiene |
shell-references.feature |
!reference resolution |
shell-pinning.feature |
Package manager and download pinning |
Pinning scenarios (GLCI-SHELL-PIN-*) include per-manager Gherkin, bad/good CI
snippets, and policy IDs in Shell pinning examples.
Custom policies¶
Write org-specific Gherkin using the same script steps, for example requiring
--tags on AWS CLI commands, then run:
gitlab-compliance check -f policies/ -p .gitlab-ci.yml
See Shell check examples.
Usage¶
Usage: gitlab-compliance shell-check [OPTIONS]
Options¶
pipeline_file:- Type: STRING
- Default:
.gitlab-ci.yml - Usage:
--pipeline -p
Path to the GitLab CI pipeline YAML file.
output_format:- Type: Choice(['console', 'markdown', 'html', 'mr-comment', 'codequality', 'junit'])
- Default:
console - Usage:
--format
Output format for the script validation report.
output_file:- Type: STRING
- Default:
none - Usage:
--output-file -o
Write rendered report to this file (markdown, html, mr-comment, junit).
include_nested:- Type: BOOL
- Default:
true - Usage:
--include-nested
Resolve nested local include files into the compliance stash.
max_include_depth:- Type: INT
- Default:
none - Usage:
--max-include-depth
Max local include nesting depth from the root file (omit for unlimited).
resolve_external_includes:- Type: BOOL
- Default:
none - Usage:
--resolve-external-includes
Fetch remote and project include YAML (default: auto — remote always, project when a token is available).
gitlab_url:- Type: STRING
- Default:
none - Usage:
--gitlab-url
GitLab instance URL (default: CI_SERVER_URL or https://gitlab.com).
token:- Type: STRING
- Default:
none - Usage:
--token
GitLab API token (default: GITLAB_TOKEN or CI_JOB_TOKEN).
project:- Type: STRING
- Default:
none - Usage:
--project
GitLab project path or ID for API-backed policy checks.
group:- Type: STRING
- Default:
none - Usage:
--group
GitLab group path or ID for API-backed policy checks.
strict:- Type: BOOL
- Default:
false - Usage:
--strict
Fail API-backed scenarios when connection info is missing (default: skip).
features_dir:- Type: STRING
- Default:
none - Usage:
--features -f
Policy directory to run instead of packaged shell standards. Defaults to packaged GLCI-SHELL policies.
help:- Type: BOOL
- Default:
false - Usage:
--help
Show this message and exit.
CLI Help¶
Usage: gitlab-compliance shell-check [OPTIONS]
Run packaged Gherkin shell standards for CI scripts (not the ShellCheck
tool).
Validates before_script/script/after_script using builtin GLCI-SHELL-*
policies. Does not install, detect, or invoke the external ShellCheck
binary.
Options:
-p, --pipeline TEXT Path to the GitLab CI pipeline YAML file.
--format [console|markdown|html|mr-comment|codequality|junit]
Output format for the script validation
report.
-o, --output-file TEXT Write rendered report to this file
(markdown, html, mr-comment, junit).
--include-nested / --no-include-nested
Resolve nested local include files into the
compliance stash.
--max-include-depth INTEGER Max local include nesting depth from the
root file (omit for unlimited).
--resolve-external-includes / --no-resolve-external-includes
Fetch remote and project include YAML
(default: auto — remote always, project when
a token is available).
--gitlab-url TEXT GitLab instance URL (default: CI_SERVER_URL
or https://gitlab.com).
--token TEXT GitLab API token (default: GITLAB_TOKEN or
CI_JOB_TOKEN).
--project TEXT GitLab project path or ID for API-backed
policy checks.
--group TEXT GitLab group path or ID for API-backed
policy checks.
--strict Fail API-backed scenarios when connection
info is missing (default: skip).
-f, --features TEXT Policy directory to run instead of packaged
shell standards. Defaults to packaged GLCI-
SHELL policies.
--help Show this message and exit.