diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 579c6a2d..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: "CodeQL Advanced" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: '22 22 * * *' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - security-events: write - packages: read - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: python - build-mode: none - - language: rust - build-mode: manual - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - - name: Run manual build steps - if: matrix.build-mode == 'manual' - shell: bash - run: | - if [ "${{ matrix.language }}" = "rust" ]; then - echo "Building Rust core for CodeQL tracing..." - cargo build --verbose - else - echo "No manual build configured for ${{ matrix.language }}" - exit 1 - fi - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{matrix.language}}" - - - name: Upload SARIF as Artifact (Debug) - uses: actions/upload-artifact@v4 - if: failure() - with: - name: codeql-sarif-debug-${{ matrix.language }} - path: ${{ runner.temp }}/**/*.sarif diff --git a/tests/unit/test_ai_rules.py b/tests/unit/test_ai_rules.py index 1173f9e2..066e967f 100644 --- a/tests/unit/test_ai_rules.py +++ b/tests/unit/test_ai_rules.py @@ -1,6 +1,7 @@ import ast import json import os +import re import sys import tempfile import textwrap @@ -24,9 +25,9 @@ def _wrap(code: str) -> str: return f"def _load_model():\n{indented}\n" -def _ai206_rule() -> dict: +def _ai_rule(rule_id: str) -> dict: rules = toml.loads(RULES_PATH.read_text(encoding="utf-8")) - return next(rule for rule in rules["rule"] if rule["id"] == "AI206") + return next(rule for rule in rules["rule"] if rule["id"] == rule_id) def _ast_node(node: ast.AST) -> dict: @@ -104,7 +105,7 @@ def fires(code: str, rule_id: str) -> bool: class TestAI206: def test_rule_metadata(self): - rule = _ai206_rule() + rule = _ai_rule("AI206") assert rule["severity"] == "High" assert rule["cwe"] == "CWE-94" assert rule["ast_match"] == AI206_MATCHER @@ -132,3 +133,37 @@ def test_trust_remote_code_false_safe(self): ) """ assert not fires(code, "AI206") + + +class TestAI202: + def test_rule_metadata(self): + rule = _ai_rule("AI202") + assert rule["severity"] == "High" + assert rule["cwe"] == "CWE-502" + assert rule["pattern"] == r"torch\.load\s*\(" + assert rule["exclude_pattern"] == r"^\s*#|weights_only\s*=\s*True" + + @pytest.mark.parametrize( + "code", + [ + 'model = torch.load("model.pt")', + "checkpoint = torch.load(path, map_location='cpu')", + ], + ) + def test_pattern_matches_torch_load_calls(self, code): + rule = _ai_rule("AI202") + assert re.search(rule["pattern"], code) + assert not re.search(rule["exclude_pattern"], code) + + @pytest.mark.parametrize( + "code", + [ + '# model = torch.load("model.pt")', + 'model = torch.load("model.pt", weights_only=True)', + 'model = torch.load("model.pt", weights_only = True)', + ], + ) + def test_exclude_pattern_suppresses_safe_or_comment_cases(self, code): + rule = _ai_rule("AI202") + assert re.search(rule["pattern"], code) + assert re.search(rule["exclude_pattern"], code)