-
Notifications
You must be signed in to change notification settings - Fork 1
test(security): add comprehensive test suite for security and complia… #23
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
Changes from all commits
775e03c
20b532f
6332610
89121c5
3f501ce
a7c9f80
d549039
fc38f67
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,80 @@ | ||
| import json | ||
|
|
||
| # Updated import path to match the network_auditing folder | ||
| from gitgalaxy.tools.network_auditing.full_api_network_map import run_api_audit | ||
|
|
||
| def test_shadow_and_ghost_api_detection(tmp_path): | ||
| """ | ||
| End-to-End test for the API Network Map. | ||
| Verifies the multi-language regex traps, Swagger auto-discovery (and test exclusion), | ||
| and the strict Shadow/Ghost API set-difference math. | ||
| """ | ||
| # 1. Construct the Mock Repository Space | ||
| repo_dir = tmp_path / "mock_api_repo" | ||
| repo_dir.mkdir() | ||
|
|
||
| # ========================================================================== | ||
| # A. Mock Source Code (The Physical Endpoints) | ||
| # ========================================================================== | ||
| # Python FastAPI | ||
| (repo_dir / "main.py").write_text('@app.get("/api/health")', encoding="utf-8") | ||
|
|
||
| # Node Express | ||
| (repo_dir / "server.js").write_text('router.post("/api/users")', encoding="utf-8") | ||
|
|
||
| # Java Spring Boot | ||
| (repo_dir / "UserController.java").write_text('@DeleteMapping("/api/users/{id}")', encoding="utf-8") | ||
|
|
||
| # Ruby Rails (THE SHADOW API - Intentionally omitted from Swagger docs) | ||
| (repo_dir / "admin.rb").write_text('get "/api/secret_debug"', encoding="utf-8") | ||
|
|
||
| # ========================================================================== | ||
| # B. Mock Official Documentation (The Approved Endpoints) | ||
| # ========================================================================== | ||
| official_swagger = { | ||
| "openapi": "3.0.0", | ||
| "paths": { | ||
| "/api/health": {"get": {}}, | ||
| "/api/users": {"post": {}}, | ||
| "/api/users/{id}": {"delete": {}}, | ||
| "/api/legacy_v1_sync": {"put": {}} # THE GHOST API (In docs, but deleted from code) | ||
| } | ||
| } | ||
| (repo_dir / "openapi.json").write_text(json.dumps(official_swagger), encoding="utf-8") | ||
|
|
||
| # ========================================================================== | ||
| # C. Mock Test Swagger (Must be ignored by auto-discovery!) | ||
| # ========================================================================== | ||
| # We name this exactly "test" to trigger the programmatic filter inside run_api_audit | ||
| test_dir = repo_dir / "test" | ||
| test_dir.mkdir() | ||
| test_swagger = {"openapi": "3.0.0", "paths": {"/test/mock": {"get": {}}}} | ||
| (test_dir / "swagger.json").write_text(json.dumps(test_swagger), encoding="utf-8") | ||
|
|
||
| # ========================================================================== | ||
| # 2. Execute the Engine | ||
| # ========================================================================== | ||
| result = run_api_audit(repo_dir) | ||
|
|
||
| # ========================================================================== | ||
| # 3. The Invariant Assertions | ||
| # ========================================================================== | ||
| assert result["status"] == "success", f"Failed to audit: status was {result['status']}" | ||
|
|
||
| # A) Prove the Multi-Language Regex Traps worked | ||
| frameworks = result["frameworks"] | ||
| assert "Python (FastAPI/Flask/Django)" in frameworks | ||
| assert "Node.js (Express/Fastify/Koa)" in frameworks | ||
| assert "Java (Spring Boot)" in frameworks | ||
| assert "Ruby (Rails/Sinatra)" in frameworks | ||
|
|
||
| # B) Prove Shadow API detection (Physical code without Documentation) | ||
| assert result["shadow_count"] == 1 | ||
| assert "GET /api/secret_debug" in result["shadow_apis"], "Engine failed to flag the undocumented Ruby Shadow API!" | ||
|
|
||
| # C) Prove Ghost API detection (Documentation without Physical code) | ||
| assert result["ghost_count"] == 1 | ||
|
|
||
| # D) Prove Auto-Discovery segregation (It ignored the test directory) | ||
| # If it read the test swagger, the ghost count would be 2 (because /test/mock isn't in the code). | ||
| assert "GET /test/mock" not in result["shadow_apis"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import os | ||
Check noticeCode scanning / CodeQL Unused import Note test
Import of 'os' is not used.
|
||
|
|
||
| from unittest.mock import patch, MagicMock | ||
Check noticeCode scanning / CodeQL Unused import Note test
Import of 'MagicMock' is not used.
|
||
|
|
||
|
|
||
| import gitgalaxy.tools.supply_chain_security.binary_anomaly_detector as xray_module | ||
|
|
||
| # ============================================================================== | ||
| # TEST 1: The Routing Matrix (Denylist vs Allowlist vs Test Folders) | ||
| # ============================================================================== | ||
| @patch("gitgalaxy.tools.supply_chain_security.binary_anomaly_detector.SecurityLens") | ||
| @patch("gitgalaxy.tools.supply_chain_security.binary_anomaly_detector.ApertureFilter") | ||
| def test_xray_routing_matrix(mock_aperture_class, mock_security_class, tmp_path, monkeypatch): | ||
| monkeypatch.setattr(xray_module, "DENYLIST_PATTERNS", ["*.key", "*.pem", "id_rsa*"]) | ||
| monkeypatch.setattr(xray_module, "XRAY_BYPASS_EXTENSIONS", [".gz", ".zip"]) | ||
| monkeypatch.setattr(xray_module, "ALLOWLIST_PATHS", ["approved_keys/"]) | ||
|
|
||
| mock_aperture = mock_aperture_class.return_value | ||
| mock_aperture._check_solar_shield.return_value = True | ||
|
|
||
| mock_security = mock_security_class.return_value | ||
| mock_security.scan_content.return_value = {"counts": {"entropy": 6.5, "bitwise_hits": 0}} | ||
| mock_security.scan_binary.return_value = {} | ||
|
|
||
| repo_dir = tmp_path / "routing_repo" | ||
| repo_dir.mkdir() | ||
|
|
||
| # File A (Anomaly) | ||
| (repo_dir / "private.key").write_text("FAKE_PRIVATE_KEY", encoding="utf-8") | ||
|
|
||
| # File B (Bypass Allowlist) | ||
| approved_dir = repo_dir / "approved_keys" | ||
| approved_dir.mkdir() | ||
| (approved_dir / "service.pem").write_text("FAKE_CERT", encoding="utf-8") | ||
|
|
||
| # File C (Bypass Extension) | ||
| (repo_dir / "compressed.zip").write_text("FAKE_ZIP_DATA", encoding="utf-8") | ||
|
|
||
| # File D (Bypass Test Folder) | ||
| # Nested inside 'src' so the string matching for "/tests/" perfectly aligns | ||
| src_dir = repo_dir / "src" | ||
| src_dir.mkdir() | ||
| test_dir = src_dir / "tests" | ||
| test_dir.mkdir() | ||
| (test_dir / "mock_payload.dat").write_text("FAKE_HIGH_ENTROPY_DATA", encoding="utf-8") | ||
|
|
||
| result = xray_module.run_xray_audit(repo_dir) | ||
| assert result["anomalies_found"] == 1, "The routing matrix failed! Check Denylist/Allowlist math." | ||
|
|
||
| # ============================================================================== | ||
| # TEST 2: The Deep Scan Threat Identification | ||
| # ============================================================================== | ||
| @patch("gitgalaxy.tools.supply_chain_security.binary_anomaly_detector.SecurityLens") | ||
| @patch("gitgalaxy.tools.supply_chain_security.binary_anomaly_detector.ApertureFilter") | ||
| def test_xray_deep_scan_threats(mock_aperture_class, mock_security_class, tmp_path): | ||
| mock_aperture = mock_aperture_class.return_value | ||
| mock_aperture._check_solar_shield.return_value = True | ||
|
|
||
| repo_dir = tmp_path / "deep_scan_repo" | ||
| repo_dir.mkdir() | ||
|
|
||
| clean_file = repo_dir / "clean.txt" | ||
| clean_file.write_text("Hello world", encoding="utf-8") | ||
|
|
||
| spoofed_file = repo_dir / "hidden_exe.jpg" | ||
| spoofed_file.write_text("MZ\x90\x00...", encoding="utf-8") | ||
|
|
||
| mock_security = mock_security_class.return_value | ||
|
|
||
| def mock_scan_binary(head_bytes, ext): | ||
| if ext == ".jpg": return {"threat_snippet": "Magic Byte Mismatch: Expected JPEG, got PE32 Executable"} | ||
| return {} | ||
|
|
||
| def mock_scan_content(content, limit): | ||
| if "MZ" in content: return {"counts": {"entropy": 6.8, "bitwise_hits": 2}} | ||
| return {"counts": {"entropy": 1.2, "bitwise_hits": 0}} | ||
|
|
||
| mock_security.scan_binary.side_effect = mock_scan_binary | ||
| mock_security.scan_content.side_effect = mock_scan_content | ||
|
|
||
| result = xray_module.run_xray_audit(repo_dir) | ||
| assert result["anomalies_found"] == 1, "Failed to flag magic byte mismatch or high entropy!" | ||
|
|
||
| # ============================================================================== | ||
| # TEST 3: The Shebang Shield | ||
| # ============================================================================== | ||
| @patch("gitgalaxy.tools.supply_chain_security.binary_anomaly_detector.SecurityLens") | ||
| @patch("gitgalaxy.tools.supply_chain_security.binary_anomaly_detector.ApertureFilter") | ||
| def test_xray_shebang_shield(mock_aperture_class, mock_security_class, tmp_path): | ||
| mock_aperture = mock_aperture_class.return_value | ||
| mock_aperture._check_solar_shield.return_value = True | ||
|
|
||
| repo_dir = tmp_path / "shebang_repo" | ||
| repo_dir.mkdir() | ||
|
|
||
| sh_file = repo_dir / "deploy.sh" | ||
| sh_file.write_text("#!/bin/bash\necho 'Deploying...'", encoding="utf-8") | ||
|
|
||
| mock_security = mock_security_class.return_value | ||
| mock_security.scan_content.return_value = {"counts": {"entropy": 0, "bitwise_hits": 0}} | ||
| mock_security.scan_binary.return_value = {"threat_snippet": "Suspicious execution header: #!/bin/bash"} | ||
|
|
||
| result = xray_module.run_xray_audit(repo_dir) | ||
| assert result["anomalies_found"] == 0, "The Shebang Shield failed!" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import pytest | ||
Check noticeCode scanning / CodeQL Unused import Note test
Import of 'pytest' is not used.
|
||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| import json | ||
| import sys | ||
| from unittest.mock import patch, MagicMock | ||
Check noticeCode scanning / CodeQL Unused import Note test
Import of 'MagicMock' is not used.
|
||
|
|
||
|
|
||
| # IMPORTANT: Adjust this import path depending on where sbom_generator.py lives | ||
| from gitgalaxy.tools.compliance.sbom_generator import UniversalManifestSlicer, main | ||
|
|
||
| # ============================================================================== | ||
| # TEST 1: The Multi-Ecosystem Slicer Guard | ||
| # ============================================================================== | ||
| def test_universal_manifest_slicer(tmp_path): | ||
| """ | ||
| Proves that the regex and JSON parsing can perfectly extract dependencies | ||
| across diverse language ecosystems without dropping packages. | ||
| """ | ||
| slicer = UniversalManifestSlicer() | ||
|
|
||
| # 1. Test NPM (JSON Parsing) | ||
| pkg_json = tmp_path / "package.json" | ||
| pkg_json.write_text('{"dependencies": {"express": "^4.17.1"}, "devDependencies": {"jest": "27.0.0"}}', encoding='utf-8') | ||
| eco1, deps1 = slicer.slice_manifest(pkg_json) | ||
|
|
||
| assert eco1 == "npm" | ||
| assert deps1["express"] == "^4.17.1" | ||
| assert deps1["jest"] == "27.0.0" | ||
|
|
||
| # 2. Test PyPI (Text / Operator Regex) | ||
| req_txt = tmp_path / "requirements.txt" | ||
| req_txt.write_text("requests==2.26.0\n# comment ignored\nurllib3>=1.26.0", encoding='utf-8') | ||
| eco2, deps2 = slicer.slice_manifest(req_txt) | ||
|
|
||
| assert eco2 == "pypi" | ||
| assert deps2["requests"] == "2.26.0" | ||
| assert deps2["urllib3"] == "latest" # The slicer defaults to 'latest' for non '==' operators | ||
|
|
||
| # 3. Test Rust / Cargo (Toml Block Regex) | ||
| cargo_toml = tmp_path / "Cargo.toml" | ||
| cargo_toml.write_text("[package]\nname=\"test\"\n\n[dependencies]\ntokio = \"1.0\"\nserde = \"1.0\"", encoding='utf-8') | ||
| eco3, deps3 = slicer.slice_manifest(cargo_toml) | ||
|
|
||
| assert eco3 == "cargo" | ||
| assert deps3["tokio"] == "latest" | ||
| assert deps3["serde"] == "latest" | ||
|
|
||
| # ============================================================================== | ||
| # TEST 2: The Zero-Trust CycloneDX Matrix | ||
| # ============================================================================== | ||
| @patch("gitgalaxy.tools.compliance.sbom_generator.SecurityLens") | ||
| @patch("gitgalaxy.tools.compliance.sbom_generator.LanguageDetector") | ||
| def test_zero_trust_sbom_generation(mock_detector_class, mock_security_class, tmp_path): | ||
| """ | ||
| Proves that the physical audit matrix correctly translates missing packages | ||
| and spoofed files into strict CycloneDX JSON properties. | ||
| """ | ||
| # 1. Setup Mock Workspace | ||
| project_dir = tmp_path / "target_project" | ||
| project_dir.mkdir() | ||
|
|
||
| # Create a package.json with two dependencies | ||
| pkg_json = project_dir / "package.json" | ||
| pkg_json.write_text('{"dependencies": {"safe-lib": "1.0", "ghost-lib": "2.0"}}', encoding='utf-8') | ||
|
|
||
| # Simulate "safe-lib" existing physically on disk, but "ghost-lib" is missing | ||
| safe_lib_dir = project_dir / "node_modules" / "safe-lib" | ||
| safe_lib_dir.mkdir(parents=True) | ||
| (safe_lib_dir / "index.js").write_text("console.log('hello');", encoding='utf-8') | ||
|
|
||
| # 2. Configure the Mocks to simulate a Spoof Detection | ||
| mock_sec_instance = mock_security_class.return_value | ||
| mock_det_instance = mock_detector_class.return_value | ||
|
|
||
| # We will pretend the SecurityLens found high entropy malware in safe-lib! | ||
| mock_sec_instance.scan_content.return_value = {"counts": {"entropy": 5.2}} | ||
| mock_det_instance.inspect.return_value = {"anomaly_flags": ["Disguised Executable"]} | ||
|
|
||
| # 3. Execute the Generator | ||
| test_args = ["sbom_generator.py", str(project_dir), "--out", "test_bom.json"] | ||
| with patch.object(sys, 'argv', test_args): | ||
| main() | ||
|
|
||
| # 4. Verify the CycloneDX Output | ||
| bom_file = tmp_path / "target_project_test_bom.json" | ||
| assert bom_file.exists(), "SBOM generator failed to create the output JSON." | ||
|
|
||
| bom_data = json.loads(bom_file.read_text(encoding='utf-8')) | ||
|
|
||
| assert bom_data["bomFormat"] == "CycloneDX" | ||
| assert len(bom_data["components"]) == 2 | ||
|
|
||
| # Extract components by name for easy assertion | ||
| components = {c["name"]: c for c in bom_data["components"]} | ||
|
|
||
| # Assert Ghost-Lib (Missing on Disk) | ||
| ghost_props = {p["name"]: p["value"] for p in components["ghost-lib"]["properties"]} | ||
| assert ghost_props["gitgalaxy:trust_status"] == "UNVERIFIED_MISSING_ON_DISK" | ||
|
|
||
| # Assert Safe-Lib (Malware Spoof Detected) | ||
| safe_props = {p["name"]: p["value"] for p in components["safe-lib"]["properties"]} | ||
| assert safe_props["gitgalaxy:trust_status"] == "SPOOF_DETECTED" | ||
| assert "High Entropy" in safe_props["gitgalaxy:anomaly_notes"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import pytest | ||
| import sys | ||
| from unittest.mock import patch | ||
|
|
||
| import gitgalaxy.tools.supply_chain_security.supply_chain_firewall as firewall_module | ||
|
|
||
| # ============================================================================== | ||
| # TEST 1: Zero-Trust Import Slicer (Regex & Bins) | ||
| # ============================================================================== | ||
| def test_zero_trust_import_slicer(tmp_path, monkeypatch): | ||
| monkeypatch.setattr(firewall_module, "APPROVED_IMPORTS", ["react", "express"]) | ||
| monkeypatch.setattr(firewall_module, "BLACKLISTED_IMPORTS", ["event-stream-malware"]) | ||
|
|
||
| repo_dir = tmp_path / "imports_repo" | ||
| repo_dir.mkdir() | ||
| (repo_dir / "app.js").write_text("import 'react'; require('event-stream-malware');", encoding="utf-8") | ||
| (repo_dir / "main.py").write_text("from 'django' import models", encoding="utf-8") | ||
|
|
||
| result = firewall_module.run_firewall_audit(repo_dir) | ||
| assert result["imports_blacklisted"] == 1, "Failed to identify blacklisted package!" | ||
| assert result["imports_unknown"] == 1, "Failed to identify unknown package!" | ||
|
|
||
| # ============================================================================== | ||
| # TEST 2: Strict Mode Enforcement | ||
| # ============================================================================== | ||
| @patch("gitgalaxy.tools.supply_chain_security.supply_chain_firewall.SecurityLens") | ||
| @patch("gitgalaxy.tools.supply_chain_security.supply_chain_firewall.ApertureFilter") | ||
| def test_strict_mode_enforcement(mock_aperture_class, mock_security_class, tmp_path, monkeypatch): | ||
| monkeypatch.setattr(firewall_module, "APPROVED_IMPORTS", ["react"]) | ||
| monkeypatch.setattr(firewall_module, "BLACKLISTED_IMPORTS", []) | ||
| monkeypatch.setattr(firewall_module, "STRICT_IMPORT_MODE", True) | ||
|
|
||
| mock_aperture = mock_aperture_class.return_value | ||
| mock_aperture._check_solar_shield.return_value = True | ||
| mock_aperture.evaluate_path_integrity.return_value = (True, 100, "OK") | ||
|
|
||
| mock_security = mock_security_class.return_value | ||
| mock_security.scan_content.return_value = {"counts": {}, "snippets": {}} | ||
| mock_security.evaluate_risk.return_value = {} | ||
|
|
||
| repo_dir = tmp_path / "strict_repo" | ||
| repo_dir.mkdir() | ||
| (repo_dir / "server.js").write_text("import 'shadow-library';", encoding="utf-8") | ||
|
|
||
| test_args = ["supply_chain_firewall.py", str(repo_dir)] | ||
| with patch.object(sys, 'argv', test_args): | ||
| with pytest.raises(SystemExit) as exc: | ||
| firewall_module.main() | ||
| assert exc.value.code == 1, "STRICT_IMPORT_MODE failed!" | ||
|
|
||
| # ============================================================================== | ||
| # TEST 3: The Inert Data Shield (Minified File Bypass) | ||
| # ============================================================================== | ||
| @patch("gitgalaxy.tools.supply_chain_security.supply_chain_firewall.SecurityLens") | ||
| @patch("gitgalaxy.tools.supply_chain_security.supply_chain_firewall.ApertureFilter") | ||
| def test_inert_data_shield_minified_bypass(mock_aperture_class, mock_security_class, tmp_path, monkeypatch): | ||
| monkeypatch.setattr(firewall_module, "STRICT_IMPORT_MODE", False) | ||
| monkeypatch.setattr(firewall_module, "BLACKLISTED_IMPORTS", []) | ||
|
|
||
| mock_aperture = mock_aperture_class.return_value | ||
| mock_aperture._check_solar_shield.return_value = True | ||
| mock_aperture.evaluate_path_integrity.return_value = (True, 100, "OK") | ||
|
|
||
| mock_security = mock_security_class.return_value | ||
| mock_security.scan_content.return_value = {"counts": {"homoglyphs": 500, "danger": 50}, "snippets": {}} | ||
|
|
||
| def mock_eval_risk(counts, loc): | ||
| if counts.get("homoglyphs", 0) > 10: return {"Hidden Malware Risk": 0.99} | ||
| return {} | ||
|
|
||
| mock_security.evaluate_risk.side_effect = mock_eval_risk | ||
|
|
||
| repo_dir = tmp_path / "inert_repo" | ||
| repo_dir.mkdir() | ||
| test_args = ["supply_chain_firewall.py", str(repo_dir)] | ||
|
|
||
| normal_file = repo_dir / "logic.js" | ||
| normal_file.write_text("var a = 'fake malware';", encoding="utf-8") | ||
|
|
||
| with patch.object(sys, 'argv', test_args): | ||
| with pytest.raises(SystemExit) as exc: | ||
| firewall_module.main() | ||
| assert exc.value.code == 1 | ||
|
|
||
| normal_file.unlink() | ||
| min_file = repo_dir / "logic.min.js" | ||
| min_file.write_text("var a = 'fake malware';", encoding="utf-8") | ||
|
|
||
| try: | ||
| with patch.object(sys, 'argv', test_args): | ||
| firewall_module.main() | ||
| except SystemExit: | ||
| pytest.fail("The Inert Data Shield failed!") |
Uh oh!
There was an error while loading. Please reload this page.