-
Notifications
You must be signed in to change notification settings - Fork 60
ci(github-actions): integrate bandit vulnerability scanner and coverage guard #528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| name: PR Coverage & Security Guard (ELUSoC_2026) | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [ main ] | ||
|
|
||
| jobs: | ||
| coverage_security_guard: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.12' | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| python -m pip install .[dev] | ||
|
|
||
| - name: Run Bandit Security Scan | ||
| run: | | ||
| bandit -r agentwatch/ -ll | ||
|
|
||
| - name: Run Tests and Measure Coverage | ||
| run: | | ||
| pytest --cov=agentwatch/ tests/ | ||
|
|
||
| - name: Verify Minimum Coverage Thresholds | ||
| run: | | ||
| python scripts/verify_coverage.py | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Automated CI Code Quality and Security Guard (ELUSoC_2026) | ||
|
|
||
| ## Overview | ||
| To maintain security compliance and avoid regression, the repository implements a PR linting workflow. | ||
|
|
||
| ## Steps Performed | ||
| 1. **Security Vulnerability Scan**: Invokes `Bandit` to inspect files for security issues. | ||
| 2. **Coverage Gate**: Validates that test suites execute and code coverage remains above the minimum threshold (default: 70%). | ||
|
|
||
| ## Files Involved | ||
| - `.github/workflows/coverage_security_guard.yml` - CI runner setup. | ||
| - `scripts/verify_coverage.py` - Coverage limit checker. | ||
| - `tests/test_coverage_guard.py` - Unit test verifying threshold limits. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """ | ||
| ELUSoC_2026 - Test Coverage Verification Script. | ||
| Ensures overall code coverage does not drop below required project thresholds. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import subprocess | ||
| import sys | ||
|
|
||
|
|
||
| def main() -> None: | ||
| # Run coverage report in JSON format | ||
| subprocess.run(["coverage", "json"], check=True) | ||
|
|
||
| with open("coverage.json", "r") as f: | ||
| data = json.load(f) | ||
|
|
||
| percent = data["totals"]["percent_covered"] | ||
| required_threshold = 70.0 | ||
|
|
||
| print(f"Current Code Coverage: {percent:.2f}%") | ||
| print(f"Required Coverage Threshold: {required_threshold}%") | ||
|
|
||
| if percent < required_threshold: | ||
| print("Code coverage is below the required threshold! PR blocked.") | ||
| sys.exit(1) | ||
|
|
||
| print("Code coverage thresholds verified successfully.") | ||
| sys.exit(0) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,35 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Unit tests for verify_coverage.py script.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_coverage_script_passes(tmp_path, monkeypatch): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Mock coverage.json file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json_path = tmp_path / "coverage.json" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dummy_data = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "totals": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "percent_covered": 75.5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(json_path, "w") as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json.dump(dummy_data, f) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.chdir(tmp_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Monkeypatch subprocess to mock the raw coverage call | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(subprocess, "run", lambda *args, **kwargs: None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Ensure root path is in sys.path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if root_path not in sys.path: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.path.insert(0, root_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from scripts.verify_coverage import main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with pytest.raises(SystemExit) as excinfo: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| main() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert excinfo.value.code == 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Add a test for the failing-threshold branch. Only the passing case (coverage ≥ 70%) is tested. The ✅ Suggested addition+def test_coverage_script_fails(tmp_path, monkeypatch):
+ json_path = tmp_path / "coverage.json"
+ dummy_data = {"totals": {"percent_covered": 50.0}}
+ with open(json_path, "w") as f:
+ json.dump(dummy_data, f)
+
+ monkeypatch.chdir(tmp_path)
+ monkeypatch.setattr(subprocess, "run", lambda *args, **kwargs: None)
+
+ root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+ if root_path not in sys.path:
+ sys.path.insert(0, root_path)
+
+ from scripts.verify_coverage import main
+ with pytest.raises(SystemExit) as excinfo:
+ main()
+ assert excinfo.value.code == 1📝 Committable suggestion
Suggested change
🧰 Tools🪛 ast-grep (0.44.0)[warning] 19-19: File path is request-/variable-derived; validate and normalize to prevent path traversal. (open-filename-from-request) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Consider using pytest's native
--cov-fail-under=70instead of a separate script.The coding guidelines state coverage is Maintain minimum test coverage of 70% (enforced in CI via pytest with --cov-fail-under=70). This workflow instead reimplements the threshold check via
scripts/verify_coverage.py, adding a custom script, extra CI step, and test to maintain, whenpytest --cov=agentwatch/ --cov-fail-under=70 tests/would achieve the same gate natively.If the custom script is intentional (e.g., for richer output/logging), consider ignoring this; otherwise consolidating removes duplicate logic.
As per coding guidelines,
tests/**/*.py: "Maintain minimum test coverage of 70% (enforced in CI via pytest with --cov-fail-under=70)".🤖 Prompt for AI Agents
Source: Coding guidelines