ShellCheckによる静的解析と監査
ShellCheckをセキュリティゲートに組み込み、その指摘を読み解いてすべてのスクリプトを強化します。
「ShellCheckによる静的解析と監査」はCoddyKit上の無料Linux Command Line & Bash Scripting Masteryレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLinux Command Line & Bash Scripting Mastery学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。
ShellCheck とは何か、なぜ重要なのか
ShellCheck は、シェルスクリプト用のオープンソース静的解析ツールです。Bash(および POSIX sh、dash、ksh)のソースを実行せずに解析し、バグ、安全でない構文、移植性の問題、スタイル上の問題を報告します。各指摘には SC2086 のような一意のルールコードが付けられます。
セキュリティを強化したパイプラインでは、ShellCheck を必須ゲートとして扱います。つまり、ShellCheck を通過するまでスクリプトをリリースしません。これは次の理由から重要です。
- シェルの脆弱性の多く(ワード分割、インジェクション、引用符のない展開)は、正常系のテストでは見つからず、攻撃者が制御する入力で初めて発生します。
- ShellCheck は、これらの種類のバグを実行時より前に、コストをかけずに検出します。
- 各パターンがなぜ危険なのかを説明するため、時間とともにチームの意識も高まります。
任意のシステムにインストールできます。
# Debian / Ubuntu
sudo apt-get install shellcheck
# macOS (Homebrew)
brew install shellcheck
# From source via Cabal (any platform)
cabal update && cabal install ShellCheck
# Verify
shellcheck --versionShellCheck を初めて実行する
最も単純な呼び出し方は shellcheck <script> です。ShellCheck は shebang 行を読み取ってシェルの種類を判定し、指摘事項を stdout に出力します。
各指摘には次の情報が含まれます。
- ファイル名と行番号 — 正確な場所
- 重大度 —
error、warning、info、またはstyle - SC コード — 参照や抑制に使える安定したルール識別子
- 人間向けの説明 — 何が問題なのか、また多くの場合はどのように修正するのかを説明します
以下のスクリプトを実行し、ShellCheck が出力する内容を確認してみましょう。
#!/usr/bin/env bash
# demo_bad.sh — intentionally flawed for ShellCheck demonstration
FILE=$1
if [ $FILE == '' ]; then
echo "No file given"
fi
cat $FILE | grep 'error' | wc -lShellCheck の出力と SC コードを読み解く
前のシーンのスクリプトに対して、ShellCheck は次のような指摘を出力します。
SC2086(warning)— グロブ展開とワード分割を防ぐために二重引用符で囲んでください —[ $FILE == '' ]内の$FILEとcat $FILE内の$FILEが対象です。SC2039/SC3010(info)— [ ] 内の == は bashism です。POSIX では = を使用してください。SC2002(style)— 不要な cat です。cat file | cmd ではなく、cmd < file の使用を検討してください。
各 SC コードには https://www.shellcheck.net/wiki/SCxxxx の wiki ページが対応しており、理由と修正版の例を確認できます。
このスクリプトの修正版は次のとおりです。
#!/usr/bin/env bash
# demo_fixed.sh — ShellCheck-clean
FILE="$1"
if [ -z "$FILE" ]; then
echo "No file given" >&2
exit 1
fi
grep -c 'error' "$FILE"重大度レベルと対応すべき指摘
ShellCheck はすべての指摘を重大度で分類します。セキュリティゲートでは、次のように扱います。
- error — ほぼ確実にバグまたはセキュリティホールです。ビルドを止め、すぐに修正します。 例:shebang がない
SC2148、引用符のない$?のSC2070。 - warning — 悪用される可能性が高いパターンです。ビルドを止め、修正するか、抑制する理由を明示します。 例:引用符のない変数の
SC2086。 - info — 現時点では正しくても、壊れやすい、または移植性のないコードです。対象外と明確に判断できない限り、同じ PR で修正します。
- style — 見た目や POSIX 上の推奨事項に関する指摘です。純粋な Bash のコードベースでは推奨されますが、必須ではありません。
--severity=warning を使うと、warning 以上の指摘がある場合だけ非ゼロで終了します。これは標準的なセキュリティゲートのしきい値です。
#!/usr/bin/env bash
# gate.sh — fail CI on errors and warnings only
shellcheck --severity=warning scripts/*.sh
echo "ShellCheck exit code: $?"ShellCheck を CI セキュリティゲートに統合する
セキュリティゲートは、必須かつ自動化されている場合にのみ役立ちます。以下のパターンは ShellCheck を CI ステップで実行し、次の処理を行います。
- リポジトリ内のすべての
.shファイルを検索します。 --severity=warningと機械可読な JSON 出力を指定して ShellCheck を実行します。- 指摘が 1 件でもあればパイプラインを失敗させます(
exit 1)。 - CI ログを離れずにエンジニアが指摘へ対応できるよう、概要を出力します。
このファイルをリポジトリに配置し、CI パイプライン(GitHub Actions、Jenkins、GitLab CI など)から呼び出してください。
#!/usr/bin/env bash
# ci/shellcheck_gate.sh
set -euo pipefail
SCRIPTS=$(find . -name '*.sh' -not -path './.git/*')
FAILED=0
for script in $SCRIPTS; do
echo "==> Checking: $script"
if ! shellcheck --severity=warning --format=tty "$script"; then
FAILED=1
fi
done
if [ "$FAILED" -eq 1 ]; then
echo "[GATE] ShellCheck found warnings or errors. Build blocked." >&2
exit 1
fi
echo "[GATE] All scripts passed ShellCheck."SC2086 ファミリー:引用符のない変数展開
SC2086 は ShellCheck で最もよく検出される指摘であり、最も悪用されているシェルの脆弱性の 1 つです。それは引用符のない変数展開です。
変数を二重引用符で囲まない場合、シェルはその値に対してワード分割(IFS に基づく分割)とグロブ展開を行います。攻撃者が変数を制御できると、追加の引数を注入したり、ファイルシステムのトラバーサルを発生させたり、コマンドに予期しないオペランドを渡したりできます。
典型的な危険パターンは次のとおりです。
#!/usr/bin/env bash
# Attacker sets: FILENAME="important.txt /etc/passwd"
FILENAME="$1"
# UNSAFE — word splitting turns this into two args
rm $FILENAME
# SAFE — double quotes prevent splitting
rm "$FILENAME"
# Arrays are the right tool for lists
FILES=("$@")
rm -- "${FILES[@]}"SC2046 と SC2035 でコマンドインジェクションのリスクを検出する
あまり知られていませんが、次の 2 つの重要なルールは、サブシェルの出力を介したコマンドインジェクションに対処します。
SC2046—$(…)内では、ワード分割やグロブを防ぐために引用符で囲んでください。サブシェルの出力を引用符なしで使用すると、出力内の空白やグロブ文字がシェルのトークンになります。SC2035—*.shの代わりに./*.shを使用してください。-で始まるファイル名がオプションとして解釈されるのを防ぎます。これは典型的な引数インジェクションの経路です。
具体的な悪用シナリオと修正方法を見てみましょう。
#!/usr/bin/env bash
# SC2046 example — output of find fed unquoted to chmod
# If a filename contains spaces, extra arguments appear
# UNSAFE
chmod 600 $(find /secrets -name '*.key')
# SAFE — use a while-read loop or xargs with -0
find /secrets -name '*.key' -print0 \
| xargs -0 chmod 600
# SC2035 example
# UNSAFE — a file named '-rf' would be passed as an option
rm *.sh
# SAFE
rm -- ./*.sh自動化で JSON 出力形式を使う
ShellCheck は --format によって複数の出力形式をサポートします。
tty(デフォルト)— 人間が読みやすいターミナル出力json— 機械可読形式。ダッシュボード、カスタムブロッカー、SAST プラットフォームへのアップロードに適していますgcc— GCC のエラー形式を解析するツール(IDE、Vim/Emacs など)と互換性がありますcheckstyle— Jenkins Checkstyle プラグインが使用する XML 形式
JSON 形式を使うと、特定の SC コードだけを基準にブロックしたり、大規模なコードベース全体の指摘を集約してセキュリティレポートを作成したりするなど、自動化されたポリシーを記述できます。
#!/usr/bin/env bash
# Emit JSON and filter for only error-severity findings using jq
shellcheck --format=json scripts/deploy.sh \
| jq '[.[] | select(.level == "error")]'
# Count distinct SC codes across all scripts
find . -name '*.sh' -print0 \
| xargs -0 shellcheck --format=json 2>/dev/null \
| jq '[.[] | .code] | group_by(.) | map({code: .[0], count: length}) | sort_by(-.count)'誤検知を正しく抑制する
ShellCheck を一括で無効にすると、その目的が失われます。正しい方法は、指摘が本当に該当しない特定の行またはブロックだけを対象にした、限定的で文書化された抑制です。
抑制には 3 つの方法があります。
- インライン抑制 — 問題のコードの 1 行上に
# shellcheck disable=SC2086を記述します。その行だけに適用されます。 - ブロック単位の無効化/有効化 —
# shellcheck disable=…と# shellcheck enable=…でセクションを囲みます。 - ファイル単位のディレクティブ — ファイルの先頭に
# shellcheck disable=…を置きます。正当化できるケースはまれなので、理由を記録してください。
すべての抑制には、その指摘が誤検知である理由を説明するコメントを必ず付けます。
#!/usr/bin/env bash
# deploy.sh
# Legitimate suppression: $DEPLOY_ARGS is intentionally word-split
# because it is a pre-validated list of flags from a trusted config file.
# shellcheck disable=SC2086
exec deploy-tool $DEPLOY_ARGS
# Block suppression for a section that generates dynamic code
# shellcheck disable=SC2016
VARS='$HOME $PATH $USER'
echo "Unexpanded vars: $VARS"
# shellcheck enable=SC2016.shellcheckrc で ShellCheck を設定する
プロジェクト全体の設定について、ShellCheck はスクリプトのディレクトリから / まで親ディレクトリをたどり、.shellcheckrc を読み取ります。これにより、実行のたびにフラグを繰り返し指定する必要がなくなり、ゲート用スクリプトもシンプルに保てます。
.shellcheckrc で便利なディレクティブは次のとおりです。
shell=bash— シェルの種類の検出結果を上書きします(shebang のないファイルで便利です)enable=all— オプションのチェックを有効にします(例:avoid-nullary-conditions、require-variable-braces)disable=SC2059— 正当な例外に対するプロジェクト全体の抑制を設定しますexternal-sources=true—sourceや.のディレクティブをたどってチェックします
# .shellcheckrc — project root
shell=bash
enable=all
external-sources=true
# SC2312: consider invoking this command separately to avoid masking its
# return value — suppressed project-wide because we use set -e.
# Rationale: errexit already aborts on failure; masking risk is mitigated.
disable=SC2312エンドツーエンドで堅牢化したスクリプト:修正前と修正後
ShellCheck の指摘を身につける最も効果的な方法は、現実的なスクリプトを、失敗する状態から問題のない堅牢な状態へリファクタリングすることです。以下のスクリプトはディレクトリをバックアップするものですが、セキュリティを考慮せずに作成されています。少なくとも 5 つの異なるルールで ShellCheck に失敗します。
両方のバージョンを確認してください。修正後のバージョンは抑制ディレクティブなしで shellcheck --severity=warning を通過し、攻撃者が制御する入力に対して大幅に安全になっています。
#!/usr/bin/env bash
# BEFORE — multiple ShellCheck violations
DEST=$1
SRC=$2
DATE=`date +%Y%m%d`
if [ ! -d $DEST ]; then
mkdir $DEST
fi
cp -r $SRC $DEST/$DATE
echo Done
#!/usr/bin/env bash
# AFTER — ShellCheck-clean and hardened
set -euo pipefail
DEST="${1:?Usage: backup.sh <dest> <src>}"
SRC="${2:?Usage: backup.sh <dest> <src>}"
DATE=$(date +%Y%m%d)
if [ ! -d "$DEST" ]; then
mkdir -p -- "$DEST"
fi
cp -r -- "$SRC" "$DEST/$DATE"
echo 'Done' >&2理解度チェック:セキュリティゲートとしての ShellCheck
セキュリティゲートとしての ShellCheck の役割について、理解度を確認しましょう。
まとめ:セキュリティゲートとしての静的解析
このレッスンでは、Bash のワークフローにおいて ShellCheck を必須のセキュリティゲートにする方法を学びました。
- ShellCheck はスクリプトを実行せずに静的解析を行い、引用符の誤り、インジェクションのリスク、安全でないパターンを実行前に検出します。
- すべての指摘にはSC コード(例:
SC2086)が付けられ、詳細なドキュメントと修正方法にリンクされています。 - 重大度の段階である
error、warning、info、styleによってゲートの厳しさを調整できます。推奨されるセキュリティのしきい値は--severity=warningです。 - 機械可読な出力(
--format=json)を使うと、レポート作成、傾向の追跡、SAST との統合を自動化できます。 - 抑制は慎重に行います。常に 1 行だけを対象にし、コメントで理由を記録し、
.shellcheckrcで正当化できる場合を除いてグローバルには抑制しません。 - ShellCheck に
set -euo pipefail、明示的な引用符、-- argumentの終端、入力検証を組み合わせ、多層防御を実現します。
ShellCheck を通過したスクリプトが自動的に安全になるわけではありません。しかし、ShellCheck に失敗するスクリプトを本番環境に投入しては決していけません。
よくある質問
「ShellCheckによる静的解析と監査」レッスンは無料ですか?
はい。「ShellCheckによる静的解析と監査」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Linux Command Line & Bash Scripting Masteryコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。
「ShellCheckによる静的解析と監査」で何を学びますか?
ShellCheckをセキュリティゲートに組み込み、その指摘を読み解いてすべてのスクリプトを強化します。 ブラウザで直接実行するハンズオンコードでLinux Command Line & Bash Scripting Masteryを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Linux Command Line & Bash Scripting Masteryを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのLinux Command Line & Bash Scripting Masteryは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「ShellCheckによる静的解析と監査」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このLinux Command Line & Bash Scripting Masteryレッスンでコードを書いて実行できますか?
はい。すべてのLinux Command Line & Bash Scripting Masteryレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- コマンドインジェクションと引数インジェクションの防止
- シークレットの安全な取り扱いと環境の衛生管理
- 最小権限実行とsudoの適切な運用
- ShellCheckによる静的解析と監査