audit-labs/audit-tools
A collection of scripts, queries, and other goodies you can use in an audit.
clone: git clone https://gitbay.org/audit-labs/audit-tools.git
v1.0.0: tui/tests/test_github_collectors.py · raw
1"""Unit tests for the new GitHub security collectors, mocking the HTTP layer."""
2
3import types
4
5import pytest
6import requests
7
8from applications.github.collectors import (
9 deploy_keys,
10 org_settings,
11 security_alerts,
12 webhooks,
13)
14
15CFG = {"headers": {}, "timeout": 30}
16
17
18class _Resp:
19 def __init__(self, payload):
20 self._payload = payload
21
22 def raise_for_status(self):
23 pass
24
25 def json(self):
26 return self._payload
27
28
29def _http_error(status):
30 err = requests.HTTPError()
31 err.response = types.SimpleNamespace(status_code=status)
32 return err
33
34
35# --- org_security -----------------------------------------------------------
36
37
38def test_org_security_row(monkeypatch):
39 payload = {
40 "two_factor_requirement_enabled": True,
41 "default_repository_permission": "read",
42 "members_can_create_repositories": False,
43 }
44 monkeypatch.setattr(org_settings.requests, "get", lambda *a, **k: _Resp(payload))
45 rows = org_settings.org_security("acme", CFG)
46 assert len(rows) == 1
47 assert rows[0]["two_factor_required"] is True
48 assert rows[0]["default_repo_permission"] == "read"
49 assert rows[0]["members_can_create_repos"] is False
50
51
52# --- webhooks ---------------------------------------------------------------
53
54
55def test_webhooks_flags_insecure(monkeypatch):
56 def fake_paginate(url, cfg, params=None):
57 if url.endswith("/orgs/acme/hooks"):
58 return [
59 {
60 "config": {"url": "http://hook.example", "insecure_ssl": "1"},
61 "events": ["push"],
62 "active": True,
63 }
64 ]
65 if url.endswith("/orgs/acme/repos"):
66 return [{"name": "repo1"}]
67 if url.endswith("/repos/acme/repo1/hooks"):
68 return [
69 {
70 "config": {"url": "https://secure.example", "insecure_ssl": "0"},
71 "events": ["pull_request"],
72 "active": True,
73 }
74 ]
75 return []
76
77 monkeypatch.setattr(webhooks, "paginate", fake_paginate)
78 rows = webhooks.webhooks("acme", CFG)
79
80 org_hook = next(r for r in rows if r["scope"] == "org")
81 assert org_hook["insecure_url"] is True
82 assert org_hook["ssl_verification"] == "disabled"
83
84 repo_hook = next(r for r in rows if r["scope"] == "repo:repo1")
85 assert repo_hook["insecure_url"] is False
86 assert repo_hook["ssl_verification"] == "enabled"
87
88
89def test_webhooks_skips_forbidden_repo(monkeypatch):
90 def fake_paginate(url, cfg, params=None):
91 if url.endswith("/orgs/acme/hooks"):
92 return []
93 if url.endswith("/orgs/acme/repos"):
94 return [{"name": "locked"}]
95 raise _http_error(403)
96
97 monkeypatch.setattr(webhooks, "paginate", fake_paginate)
98 assert webhooks.webhooks("acme", CFG) == []
99
100
101# --- deploy_keys ------------------------------------------------------------
102
103
104def test_deploy_keys_rows(monkeypatch):
105 def fake_paginate(url, cfg, params=None):
106 if url.endswith("/orgs/acme/repos"):
107 return [{"name": "repo1"}]
108 if url.endswith("/repos/acme/repo1/keys"):
109 return [{"title": "ci", "read_only": False, "created_at": "2026-01-01"}]
110 return []
111
112 monkeypatch.setattr(deploy_keys, "paginate", fake_paginate)
113 rows = deploy_keys.deploy_keys("acme", CFG)
114 assert rows == [
115 {
116 "repo": "repo1",
117 "title": "ci",
118 "read_only": False,
119 "created_at": "2026-01-01",
120 "last_used": "",
121 "added_by": "",
122 }
123 ]
124
125
126# --- security alerts --------------------------------------------------------
127
128
129def test_secret_scanning_rows(monkeypatch):
130 monkeypatch.setattr(
131 security_alerts,
132 "paginate",
133 lambda url, cfg, params=None: [
134 {
135 "repository": {"full_name": "acme/repo1"},
136 "secret_type_display_name": "AWS Key",
137 "state": "open",
138 }
139 ],
140 )
141 rows = security_alerts.secret_scanning("acme", CFG)
142 assert rows[0]["repo"] == "acme/repo1"
143 assert rows[0]["secret_type"] == "AWS Key"
144
145
146def test_dependabot_rows(monkeypatch):
147 monkeypatch.setattr(
148 security_alerts,
149 "paginate",
150 lambda url, cfg, params=None: [
151 {
152 "repository": {"full_name": "acme/repo1"},
153 "dependency": {"package": {"name": "requests"}},
154 "security_advisory": {"severity": "high", "summary": "RCE"},
155 "state": "open",
156 }
157 ],
158 )
159 rows = security_alerts.dependabot_alerts("acme", CFG)
160 assert rows[0]["package"] == "requests"
161 assert rows[0]["severity"] == "high"
162
163
164@pytest.mark.parametrize(
165 "fn", [security_alerts.secret_scanning, security_alerts.dependabot_alerts]
166)
167def test_alerts_skip_without_advanced_security(monkeypatch, fn):
168 def raise_403(url, cfg, params=None):
169 raise _http_error(403)
170
171 monkeypatch.setattr(security_alerts, "paginate", raise_403)
172 assert fn("acme", CFG) == []