mirror of
https://github.com/trailofbits/algo.git
synced 2026-08-22 23:52:27 +02:00
* ci: modernize tooling with prek, ty, and security scanning Migrate from pre-commit to prek (Rust-native, faster hooks) and add comprehensive CI improvements for code quality and security. Changes: - Replace pre-commit with prek for git hooks - Add ty type checker (Rust-based, replaces mypy) - Expand ruff rules: security (S), simplify (SIM), commented code (ERA) - Add pip-audit workflow for Python dependency CVE scanning - Add actionlint and zizmor for GitHub Actions linting/security - Add ruff format check to CI - Enable stricter ansible-lint rules (no-changed-when, risky-file-permissions) - Remove obsolete Claude workflow files - Apply ruff formatting fixes to test files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): resolve actionlint install and ty type errors - Use actionlint's official install script instead of broken URL pattern - Exclude test mock modules from ty type checking - Run workflows on push only for main/master to avoid duplicate PR runs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): use glob pattern for actionlint, exclude all tests from ty - actionlint requires *.yml glob, not directory path - Exclude all tests from ty type checking (test code has looser typing) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): quote shell variables to fix shellcheck warnings Fix SC2046/SC2086 warnings in workflow scripts: - Quote $(uname -r) in apt-get install - Quote $(pwd) in docker volume mount - Quote $existing in gh issue comment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): move key-order[task] to warn_list Too many existing violations in the codebase to enable as error. Move to warn_list for gradual fixes over time. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
142 lines
4.8 KiB
Python
142 lines
4.8 KiB
Python
"""
|
|
Test to detect double Jinja2 templating issues in YAML files.
|
|
|
|
This test prevents Ansible 12+ errors from embedded templates in Jinja2 expressions.
|
|
The pattern `{{ lookup('file', '{{ var }}') }}` is invalid and must be
|
|
`{{ lookup('file', var) }}` instead.
|
|
|
|
Issue: https://github.com/trailofbits/algo/issues/14835
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def find_yaml_files() -> list[Path]:
|
|
"""Find all YAML files in the repository."""
|
|
repo_root = Path(__file__).parent.parent.parent
|
|
yaml_files = []
|
|
|
|
# Include all .yml and .yaml files
|
|
for pattern in ["**/*.yml", "**/*.yaml"]:
|
|
yaml_files.extend(repo_root.glob(pattern))
|
|
|
|
# Exclude test files and vendor directories
|
|
excluded_dirs = {"venv", ".venv", "env", ".git", "__pycache__", ".pytest_cache"}
|
|
yaml_files = [f for f in yaml_files if not any(excluded in f.parts for excluded in excluded_dirs)]
|
|
|
|
return sorted(yaml_files)
|
|
|
|
|
|
def detect_double_templating(content: str) -> list[tuple[int, str]]:
|
|
"""
|
|
Detect double templating patterns in file content.
|
|
|
|
Returns list of (line_number, problematic_line) tuples.
|
|
"""
|
|
issues = []
|
|
|
|
# Pattern 1: lookup() with embedded {{ }}
|
|
# Matches: lookup('file', '{{ var }}') or lookup("file", "{{ var }}")
|
|
pattern1 = r"lookup\s*\([^)]*['\"]{{[^}]*}}['\"][^)]*\)"
|
|
|
|
# Pattern 2: Direct nested {{ {{ }} }}
|
|
pattern2 = r"{{\s*[^}]*{{\s*[^}]*}}"
|
|
|
|
# Pattern 3: Embedded templates in quoted strings within Jinja2
|
|
# This catches cases like value: "{{ '{{ var }}' }}"
|
|
pattern3 = r"{{\s*['\"][^'\"]*{{[^}]*}}[^'\"]*['\"]"
|
|
|
|
lines = content.split("\n")
|
|
for i, line in enumerate(lines, 1):
|
|
# Skip comments
|
|
stripped = line.split("#")[0]
|
|
if not stripped.strip():
|
|
continue
|
|
|
|
if re.search(pattern1, stripped) or re.search(pattern2, stripped) or re.search(pattern3, stripped):
|
|
issues.append((i, line))
|
|
|
|
return issues
|
|
|
|
|
|
def test_no_double_templating():
|
|
"""Test that no YAML files contain double templating patterns."""
|
|
yaml_files = find_yaml_files()
|
|
all_issues = {}
|
|
|
|
for yaml_file in yaml_files:
|
|
try:
|
|
content = yaml_file.read_text()
|
|
issues = detect_double_templating(content)
|
|
if issues:
|
|
# Store relative path for cleaner output
|
|
rel_path = yaml_file.relative_to(Path(__file__).parent.parent.parent)
|
|
all_issues[str(rel_path)] = issues
|
|
except Exception:
|
|
# Skip binary files or files we can't read
|
|
continue
|
|
|
|
if all_issues:
|
|
# Format error message for clarity
|
|
error_msg = "\n\nDouble templating issues found:\n"
|
|
error_msg += "=" * 60 + "\n"
|
|
|
|
for file_path, issues in all_issues.items():
|
|
error_msg += f"\n{file_path}:\n"
|
|
for line_num, line in issues:
|
|
error_msg += f" Line {line_num}: {line.strip()}\n"
|
|
|
|
error_msg += "\n" + "=" * 60 + "\n"
|
|
error_msg += "Fix: Replace '{{ var }}' with var inside lookup() calls\n"
|
|
error_msg += "Example: lookup('file', '{{ SSH_keys.public }}') → lookup('file', SSH_keys.public)\n"
|
|
|
|
pytest.fail(error_msg)
|
|
|
|
|
|
def test_specific_known_issues():
|
|
"""
|
|
Test for specific known double-templating issues.
|
|
This ensures our detection catches the actual bugs from issue #14835.
|
|
"""
|
|
# These are the actual problematic patterns from the codebase
|
|
known_bad_patterns = [
|
|
"{{ lookup('file', '{{ SSH_keys.public }}') }}",
|
|
'{{ lookup("file", "{{ credentials_file_path }}") }}',
|
|
"value: \"{{ lookup('file', '{{ SSH_keys.public }}') }}\"",
|
|
"PayloadContentCA: \"{{ lookup('file' , '{{ ipsec_pki_path }}/cacert.pem')|b64encode }}\"",
|
|
]
|
|
|
|
for pattern in known_bad_patterns:
|
|
issues = detect_double_templating(pattern)
|
|
assert issues, f"Failed to detect known bad pattern: {pattern}"
|
|
|
|
|
|
def test_valid_patterns_not_flagged():
|
|
"""
|
|
Test that valid templating patterns are not flagged as errors.
|
|
"""
|
|
valid_patterns = [
|
|
"{{ lookup('file', SSH_keys.public) }}",
|
|
"{{ lookup('file', credentials_file_path) }}",
|
|
"value: \"{{ lookup('file', SSH_keys.public) }}\"",
|
|
"{{ item.1 }}.mobileconfig",
|
|
"{{ loop.index }}. {{ r.server }} ({{ r.IP_subject_alt_name }})",
|
|
"PayloadContentCA: \"{{ lookup('file', ipsec_pki_path + '/cacert.pem')|b64encode }}\"",
|
|
"ssh_pub_key: \"{{ lookup('file', SSH_keys.public) }}\"",
|
|
]
|
|
|
|
for pattern in valid_patterns:
|
|
issues = detect_double_templating(pattern)
|
|
assert not issues, f"Valid pattern incorrectly flagged: {pattern}"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Run the test directly for debugging
|
|
test_specific_known_issues()
|
|
test_valid_patterns_not_flagged()
|
|
test_no_double_templating()
|
|
print("All tests passed!")
|