T-CREATOR

ESLint でセキュリティホールを防ぐ!脆弱性検知ルール完全ガイド

ESLint でセキュリティホールを防ぐ!脆弱性検知ルール完全ガイド

現代のWeb開発において、セキュリティは最も重要な課題の1つです。特にJavaScriptやTypeScriptで開発されるアプリケーションでは、コーディング段階でのセキュリティ対策が欠かせません。

ESLintというと、コードの品質管理やスタイルチェックのイメージが強いかもしれませんが、実はセキュリティ脆弱性の検知にも非常に強力なツールなのです。本記事では、ESLintを活用してセキュリティホールを未然に防ぐ方法について、実践的な設定方法から具体的なルールまで詳しく解説いたします。

背景

ESLintとセキュリティの関係性

ESLint(ECMAScript Linter)は、JavaScriptコードの静的解析ツールとして広く利用されています。従来はコードの一貫性やバグの早期発見を目的として使用されてきましたが、近年はセキュリティ分野での活用も注目されています。

静的解析によるセキュリティチェックには、以下のような利点があります。

  • 開発段階での早期発見:コードを書いた瞬間に脆弱性を検知
  • 自動化による効率性:人的ミスを防ぎ、レビュー工数を削減
  • 教育効果:開発者がセキュアコーディングを自然と身に着けられる

セキュリティルールが重要な理由

従来のセキュリティ対策は、アプリケーション完成後のペネトレーションテストや脆弱性診断に依存していました。しかし、この方法では以下のような課題があります。

#従来の課題ESLintによる改善
1発見が遅く修正コストが高い開発時点で即座に検知
2網羅的なチェックが困難全コードを自動スキャン
3開発者のスキルに依存ルール化により均一な品質
4継続的な監視が困難CI/CDパイプラインで自動化

課題

JavaScript/TypeScriptアプリケーションで発生しやすいセキュリティホール

モダンなWeb開発では、JavaScript/TypeScriptが中心的な役割を果たしています。しかし、これらの言語特有のセキュリティリスクが存在します。

動的実行による脆弱性

JavaScriptの柔軟性は、同時にセキュリティリスクも生み出します。

javascript// 危険なコードの例
function processUserInput(userCode) {
    // ユーザー入力をそのまま実行(非常に危険)
    return eval(userCode);
}

// DOM操作での脆弱性
function displayMessage(message) {
    // XSS攻撃の可能性
    document.getElementById('content').innerHTML = message;
}

型システムの穴

TypeScriptを使用していても、以下のような問題が発生する可能性があります。

typescript// any型の乱用
function handleApiResponse(response: any) {
    // 型チェックをすり抜けるリスク
    console.log(response.secretKey);
    return response.data.toString();
}

// 型アサーションの危険な使用
const userInput = getUserInput() as string;
document.write(userInput); // XSSリスク

非同期処理でのセキュリティリスク

Promise やasync/awaitでの適切でないエラーハンドリングは、情報漏洩につながる恐れがあります。

javascriptasync function fetchUserData(userId) {
    try {
        const response = await fetch(`/api/users/${userId}`);
        return response.json();
    } catch (error) {
        // エラー詳細を露出(情報漏洩リスク)
        console.error('Database error:', error.stack);
        throw error;
    }
}

第三者ライブラリの依存関係

npm生態系では、深い依存関係により予期しない脆弱性が混入するリスクがあります。

  • プロトタイプ汚染:Objectプロトタイプの改変
  • ReDoS攻撃:正規表現による処理時間の異常延長
  • 機密情報の意図しない送信:デバッグ情報や環境変数の露出

これらの課題に対して、ESLintのセキュリティプラグインは効果的な解決策を提供します。

解決策

ESLintのセキュリティプラグインとルール設定による予防策

ESLintエコシステムには、セキュリティに特化した複数のプラグインが存在します。これらを適切に設定することで、開発段階でのセキュリティ脆弱性を大幅に減少させることができます。

主要なセキュリティプラグイン

#プラグイン名特徴検知対象
1eslint-plugin-security汎用的なセキュリティルールeval、正規表現DoS、ファイルパス操作など
2@typescript-eslint/eslint-pluginTypeScript特化型安全性、any型の制限など
3eslint-plugin-reactReact固有の脆弱性dangerouslySetInnerHTML、イベント処理など
4eslint-plugin-nodeNode.js環境ファイルシステム操作、プロセス実行など

基本的な設定戦略

セキュリティESLintルールの導入は、段階的に行うことを推奨します。

javascript// .eslintrc.js の基本設定
module.exports = {
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:security/recommended'
  ],
  plugins: [
    'security',
    '@typescript-eslint'
  ],
  rules: {
    // 段階的にルールを有効化
    'security/detect-eval-with-expression': 'error',
    'security/detect-non-literal-fs-filename': 'warn',
    'security/detect-unsafe-regex': 'error'
  }
};

CI/CDパイプラインでの自動化

セキュリティチェックを継続的に実行するため、CI/CDパイプラインに組み込みます。

yaml# .github/workflows/security-check.yml
name: Security Check
on: [push, pull_request]

jobs:
  security-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: yarn install
      - name: Run security linting
        run: yarn eslint --ext .js,.ts,.jsx,.tsx --config .eslintrc.security.js src/

この基盤の上に、具体的なセキュリティルールを実装していきます。

具体例

eslint-plugin-securityの導入と設定

eslint-plugin-securityは、最も包括的なセキュリティルールセットを提供するプラグインです。まずは基本的な導入手順から始めましょう。

インストールと基本設定

bash# パッケージのインストール
yarn add -D eslint-plugin-security
javascript// .eslintrc.js での基本設定
module.exports = {
  plugins: ['security'],
  extends: ['plugin:security/recommended'],
  rules: {
    // 推奨設定を有効化
    'security/detect-buffer-noassert': 'error',
    'security/detect-child-process': 'error',
    'security/detect-disable-mustache-escape': 'error',
    'security/detect-eval-with-expression': 'error',
    'security/detect-no-csrf-before-method-override': 'error',
    'security/detect-non-literal-fs-filename': 'error',
    'security/detect-non-literal-regexp': 'error',
    'security/detect-non-literal-require': 'error',
    'security/detect-object-injection': 'error',
    'security/detect-possible-timing-attacks': 'error',
    'security/detect-pseudoRandomBytes': 'error',
    'security/detect-unsafe-regex': 'error'
  }
};

カスタムルール設定

プロジェクトの特性に応じて、ルールの重要度を調整します。

javascript// プロジェクト固有の設定例
const securityConfig = {
  rules: {
    // 本番環境では絶対に許可しない
    'security/detect-eval-with-expression': 'error',
    'security/detect-unsafe-regex': 'error',
    
    // 開発段階では警告レベル
    'security/detect-non-literal-fs-filename': 'warn',
    'security/detect-object-injection': 'warn',
    
    // 特定の条件下で許可(設定で無効化)
    'security/detect-child-process': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  }
};

module.exports = securityConfig;

危険なevalやinnerHTMLの検知

eval関数やinnerHTMLプロパティは、動的コード実行による深刻なセキュリティリスクを引き起こします。

eval関数の検知と対策

javascript// ❌ 危険なコード(ESLintが検知)
function calculateExpression(formula) {
    // security/detect-eval-with-expression が警告
    return eval(formula);
}

// ❌ 間接的なeval使用も検知
const executeCode = new Function('code', 'return eval(code)');
javascript// ✅ 安全な代替実装
function calculateExpression(formula) {
    // 数式パーサーライブラリを使用
    const math = require('mathjs');
    try {
        return math.evaluate(formula);
    } catch (error) {
        throw new Error('Invalid mathematical expression');
    }
}

// ✅ ホワイトリスト方式での安全な実装
function safeCalculate(operation, a, b) {
    const allowedOperations = {
        add: (x, y) => x + y,
        subtract: (x, y) => x - y,
        multiply: (x, y) => x * y,
        divide: (x, y) => y !== 0 ? x / y : null
    };
    
    return allowedOperations[operation]?.(a, b);
}

innerHTML の安全な使用

javascript// ❌ XSS脆弱性のあるコード
function displayUserContent(content) {
    // 生のHTMLを直接挿入(危険)
    document.getElementById('user-content').innerHTML = content;
}
javascript// ✅ 安全な代替方法
function displayUserContent(content) {
    const container = document.getElementById('user-content');
    
    // テキストとして安全に挿入
    container.textContent = content;
}

// ✅ サニタイゼーションを使用した方法
import DOMPurify from 'dompurify';

function displaySanitizedHTML(htmlContent) {
    const container = document.getElementById('user-content');
    const cleanHTML = DOMPurify.sanitize(htmlContent);
    container.innerHTML = cleanHTML;
}

XSS脆弱性の防止ルール

Cross-Site Scripting(XSS)は、Webアプリケーションで最も頻繁に発生する脆弱性の1つです。ESLintルールで効果的に防げます。

React環境でのXSS対策

jsx// ❌ dangerouslySetInnerHTMLの不適切な使用
function UserComment({ comment }) {
    return (
        <div 
            dangerouslySetInnerHTML={{ __html: comment }} 
        />
    );
}
javascript// ESLint設定でReact固有のルールを追加
module.exports = {
  plugins: ['react', 'security'],
  rules: {
    // dangerouslySetInnerHTMLの使用を制限
    'react/no-danger': 'error',
    'react/no-danger-with-children': 'error',
    
    // セキュリティプラグインとの連携
    'security/detect-object-injection': 'error'
  }
};
jsx// ✅ 安全なReact実装
import DOMPurify from 'dompurify';

function UserComment({ comment, allowHTML = false }) {
    if (allowHTML) {
        // 管理者承認済みのHTMLのみ許可
        const sanitized = DOMPurify.sanitize(comment, {
            ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
            ALLOWED_ATTR: []
        });
        
        return (
            <div dangerouslySetInnerHTML={{ __html: sanitized }} />
        );
    }
    
    // 通常はテキストとして処理
    return <div>{comment}</div>;
}

URL処理でのXSS防止

javascript// ❌ 危険なURL処理
function redirectUser(url) {
    // javascript:スキームによるXSS攻撃が可能
    window.location.href = url;
}
javascript// ✅ 安全なURL処理
function redirectUser(url) {
    try {
        const parsedUrl = new URL(url);
        
        // 許可されたプロトコルのみ受け入れ
        const allowedProtocols = ['http:', 'https:'];
        if (!allowedProtocols.includes(parsedUrl.protocol)) {
            throw new Error('Invalid protocol');
        }
        
        // 許可されたドメインのみ受け入れ(必要に応じて)
        const allowedDomains = ['example.com', 'subdomain.example.com'];
        if (!allowedDomains.includes(parsedUrl.hostname)) {
            throw new Error('Unauthorized domain');
        }
        
        window.location.href = parsedUrl.href;
    } catch (error) {
        console.error('Invalid redirect URL:', error.message);
        // 安全なフォールバック先へ
        window.location.href = '/dashboard';
    }
}

SQLインジェクション対策ルール

Node.js環境でのデータベース操作において、SQLインジェクション攻撃を防ぐルールを設定します。

危険なクエリ構築パターンの検知

javascript// ❌ SQLインジェクション脆弱性のあるコード
function getUserByEmail(email) {
    const query = `SELECT * FROM users WHERE email = '${email}'`;
    return database.execute(query);
}
javascript// ESLint カスタムルールでの検知設定
module.exports = {
  rules: {
    // 文字列結合でのSQL構築を禁止
    'security/detect-sql-injection': 'error',
    'security/detect-non-literal-regexp': 'error'
  },
  
  // カスタムルールの追加
  overrides: [
    {
      files: ['**/*db*.js', '**/*database*.js', '**/*model*.js'],
      rules: {
        // データベース関連ファイルでより厳格なチェック
        'security/detect-object-injection': 'error',
        'no-template-curly-in-string': 'error'
      }
    }
  ]
};

安全なクエリ実装

javascript// ✅ プリペアドステートメントを使用
async function getUserByEmail(email) {
    const query = 'SELECT * FROM users WHERE email = ?';
    return await database.execute(query, [email]);
}

// ✅ ORMを使用した安全な実装
import { User } from './models/User';

async function getUserByEmail(email) {
    // ORMが自動的にエスケープ処理
    return await User.findOne({ 
        where: { email: email } 
    });
}
javascript// ✅ 入力値検証との組み合わせ
import Joi from 'joi';

const emailSchema = Joi.string().email().required();

async function getUserByEmail(email) {
    // 入力値の事前検証
    const { error, value } = emailSchema.validate(email);
    if (error) {
        throw new Error('Invalid email format');
    }
    
    // パラメータ化クエリで安全に実行
    const query = 'SELECT id, name, email FROM users WHERE email = ?';
    return await database.execute(query, [value]);
}

正規表現DoS攻撃の防止

Regular Expression Denial of Service(ReDoS)攻撃は、計算量の多い正規表現によってサーバーリソースを枯渇させる攻撃です。

危険な正規表現パターンの検知

javascript// ❌ ReDoS脆弱性のある正規表現
const emailRegex = /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;

function validateEmail(email) {
    // 複雑な入力で処理時間が指数的に増加
    return emailRegex.test(email);
}
javascript// ESLint設定でReDoS検知を強化
module.exports = {
  rules: {
    'security/detect-unsafe-regex': 'error',
    
    // Node.js環境でのタイムアウト設定チェック
    'security/detect-possible-timing-attacks': 'warn'
  }
};

安全な正規表現実装

javascript// ✅ シンプルで効率的な正規表現
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function validateEmail(email) {
    return emailRegex.test(email);
}

// ✅ より厳密だが安全な検証
function validateEmailSafely(email) {
    // 長さ制限による保護
    if (email.length > 254) {
        return false;
    }
    
    // 基本的な形式チェック
    const basicEmailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
    
    return basicEmailRegex.test(email);
}
javascript// ✅ 外部ライブラリを使用した安全な検証
import validator from 'validator';

function validateEmailWithLibrary(email) {
    return validator.isEmail(email, {
        // 追加のセキュリティオプション
        allow_utf8_local_part: false,
        require_tld: true
    });
}

// ✅ タイムアウト機能付きの検証
function validateWithTimeout(input, regex, timeoutMs = 100) {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
            reject(new Error('Regex validation timeout'));
        }, timeoutMs);
        
        try {
            const result = regex.test(input);
            clearTimeout(timeout);
            resolve(result);
        } catch (error) {
            clearTimeout(timeout);
            reject(error);
        }
    });
}

まとめ

ESLintを活用したセキュリティ強化は、現代のWeb開発において不可欠な手法となっています。本記事で紹介した設定やルールを実装することで、以下のメリットが得られます。

得られる主な効果

#効果具体的な改善点
1早期発見開発段階でのセキュリティホール検知
2自動化人的チェックミスの削減
3教育効果開発者のセキュリティ意識向上
4継続性CI/CDパイプラインでの継続的監視

継続的な改善のために

ESLintセキュリティルールの運用は、一度設定して終わりではありません。以下の点を継続的に見直しましょう。

定期的な見直しポイント

  • 新しいセキュリティプラグインやルールの調査
  • プロジェクト固有のセキュリティリスクに対応したカスタムルールの作成
  • チーム内でのセキュリティ知識共有とルール調整
  • 外部セキュリティ診断結果との照合と設定更新

チーム開発での運用

  • 新規参入メンバーへのセキュリティESLint設定の説明
  • コードレビュー時のセキュリティ観点でのチェックポイント共有
  • セキュリティインシデント発生時の設定見直しプロセス

ESLintによるセキュリティ強化は、技術的な対策を超えて、開発チーム全体のセキュリティ文化醸成にも寄与します。ぜひ今回紹介した手法を実践し、より安全なWebアプリケーション開発を実現してください。

関連リンク

公式ドキュメント・リソース

セキュリティガイドライン・参考資料

セキュリティツール・ライブラリ

  • DOMPurify - HTMLサニタイゼーション
  • helmet.js - Express.jsセキュリティミドルウェア
  • validator.js - 文字列バリデーション
  • semver - セマンティックバージョニング