フック

regex フック:ReDoS パターンを検出する

コマンドラインから、ReDoS を起こしやすい正規表現をコードベース全体でスキャンしたり、1 つのパターンを判定したりできます。

regex フックは、regex 専門知識 が実行する、正規表現の安全性を判定する決定論的なエンジンで、自分で実行することもできます。1 つのコマンドで 2 つのモードが使えます:scan モードはコードベースをスキャンして、ReDoS を起こしやすいパターンや実行時に組み立てられるパターンを探し、check モードは渡された 1 つのパターンを判定します。

コードベースをスキャンする​

フラグを付けなければ、プロジェクト全体をスキャンします。--dir はスキャン対象をディレクトリに、--file は 1 つのファイルに絞ります。どちらも繰り返し指定でき、組み合わせると 1 つのレポートにまとまります。複数の対象に含まれるファイルも、表示されるのは 1 回だけです。

npx lagune read regex

出力はプレーンテキストで、各セクションの見出しがそのセクションの読み方を示しています。セクションは、検出結果があるときだけ表示されます。

Vulnerable regular expressions found:

cmd/server/main.go
([a-z]+)+$ (exponential, search)

Dynamically built regular expressions (review manually by simulating a constructed regex):

app/payments.php
crates/parser/lexer.rs
lib/validate.py

Static regex wrapped in a constructor (use a literal instead):

config/token.rb

何も見つからなければ、1 行だけ出力します。

npx lagune read regex --dir src/clean
# => no unsafe patterns found

セクションの読み方​

セクション意味
Vulnerable regular expressions foundReDoS を起こしやすいパターン。ファイルごとにまとめて各パターンをその下にインデントして示し、続く括弧内に安全でない理由と、ある場合はスコープとフラグを記載する。どの行も安全でないため、判定は繰り返さない。
Regular expressions the analysis could not settle (review manually)解析が作業量の上限内で結論を出せなかったパターン。表示形式は同じ。
Dynamically built regular expressions (review manually by simulating a constructed regex)変数、連結、文字列補間から正規表現を組み立てているファイル。
Static regex wrapped in a constructor (use a literal instead)正規表現リテラルのほうがシンプルで実行時のコンパイルも省けるのに、正規表現のコンストラクターにただの文字列リテラルを渡しているファイル。

1 つ目のセクションは、セキュリティ上の検出事項です。2 つ目と 3 つ目はそれ自体では検出事項ではなく、静的なチェックでは結論を出せないコードを示しているため、人が読んで確認する必要があります。4 つ目はコードをわかりやすくするためのヒントで、セキュリティ上の検出事項ではありません。

CLI オプション​

オプション値説明
--pattern正規表現のソース1 つのパターンをチェックする。繰り返し指定すると複数をチェックし、指定した順に 1 パターンにつき 1 つずつ判定を出力する。
--flags正規表現のフラグすべての --pattern をこのフラグ(im など)付きでチェックする。check モードにのみ適用。
--dirディレクトリスキャン対象をディレクトリに絞る。繰り返し指定でき、--file と併用できる。
--fileファイルスキャン対象を 1 つのファイルに絞る。繰り返し指定でき、--dir と併用できる。
--limit0 以上の整数ReDoS の繰り返し回数の上限(デフォルトは 25)を設定する。両方のモードに適用。値が低いほど厳しい。

オプションを指定しなければ、プロジェクト全体をスキャンします。--pattern は check モードで、--dir や --file とは併用できません。

対応言語​

スキャンはファイル拡張子をもとに次の言語を読み取り、それぞれの言語のルールで扱うため、ある言語のパターンが別の言語のルールでテストされることはありません。それ以外のファイルは、誤ったルールでスキャンせずにスキップします。

#言語拡張子
1JavaScript / TypeScript.js、.jsx、.mjs、.cjs、.ts、.tsx、.mts、.cts、.astro、.vue、.svelte、.marko、.riot
2Python.py、.pyi
3Ruby.rb
4Go.go
5PHP.php
6Rust.rs
7Java.java
8C#.cs
9C.c、.h
10C++.cc、.cpp、.cxx、.hpp、.hh、.hxx
11Kotlin.kt、.kts
12Swift.swift
13Scala.scala、.sc
14Dart.dart
15PowerShell.ps1、.psm1、.psd1
16Elixir.ex、.exs
17Objective-C.m、.mm
18R.r
19Julia.jl
20Clojure.clj、.cljs、.cljc、.edn
21Crystal.cr
22Nim.nim、.nims
23V.v
24D.d
25Perl.pl、.pm
ベストエフォートであり、網羅的ではない

ソースコードをテキストとして読み取るため、実行時に組み立てられるパターンは判定できず、複数行にまたがるパターンや、エイリアスの陰に隠れたもの、認識できない書き方のものは見逃すことがあります。各セクションは完全な一覧ではなく確かな出発点として扱い、セクションで拾えない部分はコードを読んで確認し続けてください。

1 つのパターンをチェックする​

パターンのソースを --pattern で渡すと判定できます。フックが出力するのは、safe、unsafe、undetermined(解析が結論を出す前に作業量の上限に達した場合)、invalid regex のいずれか 1 語です。--pattern を繰り返し指定すると、複数を一度に判定し、指定した順に 1 パターンにつき 1 つずつ判定を出力します。

npx lagune read regex --pattern '(a+)+$'
# => unsafe

npx lagune read regex --pattern '^[a-z0-9_]{3,20}$'
# => safe
引数を引用符で囲む理由

各パターンはフラグの値として渡され、コマンドに埋め込まれることはありません。引用符やバッククォートを含む値も無害なままで、シェルへのインジェクションは起こりません。シェルが先に展開してしまわないよう、パターンは必ずシングルクォートで囲んでください。

ヒント

これは regex 専門知識 が実行するのと同じエンジンで、scan モードで ReDoS の確認を始め、見つかったパターンを check モードで確かめます。

よくある質問

npx lagune read regex --pattern '<pattern>' を実行します。safe、unsafe、undetermined、invalid regex のいずれかが返ります。

コードベースに対して regex フックを scan モードで実行します。対応している 25 の言語にわたって、ReDoS を起こしやすいパターンを報告します。