|
1 | 1 | #!/usr/bin/env python3 |
2 | | -"""Parse a CodeQL code-quality SARIF file, report findings, and gate on them. |
| 2 | +"""Parse a CodeQL SARIF file, report findings, and gate on them. |
3 | 3 |
|
4 | 4 | CodeQL's Code Quality preview (the GitHub Settings → Security → Code quality |
5 | 5 | page) is only available to Team/Enterprise Cloud org plans, so it cannot be |
6 | 6 | enabled on this repo's free org. This script provides an equivalent gate by |
7 | | -running the ``python-code-quality.qls`` suite via the CodeQL CLI in CI and |
8 | | -failing the job when any finding remains. |
| 7 | +running the ``<language>-code-quality.qls`` suite via the CodeQL CLI in CI |
| 8 | +and failing the job when any finding remains. The workflow also feeds the |
| 9 | +default ``<language>-code-scanning.qls`` security suite through the same |
| 10 | +gate, because GitHub default setup only analyzes master post-merge and does |
| 11 | +not block PRs (see the header of codeql-quality.yml). |
9 | 12 |
|
10 | 13 | Usage: |
11 | 14 | codeql_quality_gate.py <quality.sarif> |
|
160 | 163 | "heuristic to have correctly caught. mypy already confirms this file is " |
161 | 164 | "clean under that typing.", |
162 | 165 | ), |
| 166 | + # ---- Security-suite entries (the workflow also gates the default |
| 167 | + # <language>-code-scanning suites; see codeql-quality.yml header). Entries |
| 168 | + # whose message substring is generic ("as clear text", "hashing algorithm") |
| 169 | + # are PATH-WIDE for that rule+file — a future finding of the same rule in |
| 170 | + # the same file would be suppressed too. Accepted for the same reason as |
| 171 | + # the quality entries above: the files are small and each reason names the |
| 172 | + # exact intended pattern. Re-audit when one of these files grows. |
| 173 | + ( |
| 174 | + "py/clear-text-logging-sensitive-data", |
| 175 | + "custom_components/ha_mcp_tools/embedded_setup.py", |
| 176 | + "as clear text", |
| 177 | + "Deliberate admin-only connect instructions: the startup log prints the " |
| 178 | + "legacy-OAuth Client ID/Secret so the admin can paste them into an MCP " |
| 179 | + "client. The SECURITY note from the #1880 review in this file governs " |
| 180 | + "the pattern: credentials are withheld while a rotation is pending and " |
| 181 | + "never placed in the persistent notification all users can see.", |
| 182 | + ), |
| 183 | + ( |
| 184 | + "py/clear-text-logging-sensitive-data", |
| 185 | + "homeassistant-addon-webhook-proxy/mcp_proxy/__init__.py", |
| 186 | + "as clear text", |
| 187 | + "False positive: the log line emits oauth_provider.client_id_masked(), " |
| 188 | + "not the raw value; CodeQL tracks taint through the masking helper.", |
| 189 | + ), |
| 190 | + ( |
| 191 | + "py/clear-text-logging-sensitive-data", |
| 192 | + "homeassistant-addon-webhook-proxy-dev/mcp_proxy_dev/__init__.py", |
| 193 | + "as clear text", |
| 194 | + "False positive (dev flavor, identical code to stable): the log line " |
| 195 | + "emits client_id_masked(), not the raw value; CodeQL tracks taint " |
| 196 | + "through the masking helper.", |
| 197 | + ), |
| 198 | + ( |
| 199 | + "py/clear-text-logging-sensitive-data", |
| 200 | + "homeassistant-addon-webhook-proxy/start.py", |
| 201 | + "as clear text", |
| 202 | + "Deliberate add-on startup log: prints the connect URL and legacy-OAuth " |
| 203 | + "credentials to the Supervisor add-on log (admin-only) as the user's " |
| 204 | + "setup instructions — the add-on-side mirror of embedded_setup.py's " |
| 205 | + "admin-only connect log.", |
| 206 | + ), |
| 207 | + ( |
| 208 | + "py/clear-text-logging-sensitive-data", |
| 209 | + "homeassistant-addon-webhook-proxy-dev/start.py", |
| 210 | + "as clear text", |
| 211 | + "Deliberate add-on startup log (dev flavor, identical code to stable): " |
| 212 | + "prints the connect URL and legacy-OAuth credentials to the Supervisor " |
| 213 | + "add-on log (admin-only) as the user's setup instructions.", |
| 214 | + ), |
| 215 | + ( |
| 216 | + "py/clear-text-logging-sensitive-data", |
| 217 | + "src/ha_mcp/stdio_settings_sidecar.py", |
| 218 | + "as clear text", |
| 219 | + "Deliberate: logs the sidecar's own loopback settings URL " |
| 220 | + "(http://127.0.0.1:<port><secret_path>/settings) so the local operator " |
| 221 | + "can open it. Local-process log/stderr only; the URL is unreachable off " |
| 222 | + "the host.", |
| 223 | + ), |
| 224 | + ( |
| 225 | + "py/clear-text-logging-sensitive-data", |
| 226 | + "tests/test_env_manager.py", |
| 227 | + "as clear text", |
| 228 | + "Interactive test-environment helper printing the seeded throwaway " |
| 229 | + "credentials of the disposable HA test container (tests/test_constants.py). " |
| 230 | + "Printing them for copy-paste is the tool's purpose.", |
| 231 | + ), |
| 232 | + ( |
| 233 | + "py/clear-text-storage-sensitive-data", |
| 234 | + "homeassistant-addon-webhook-proxy/mcp_proxy/oauth.py", |
| 235 | + "as clear text", |
| 236 | + "Warned fallback: the primary path writes the signing key via " |
| 237 | + "_atomic_write_0600; the flagged plain write only runs when the " |
| 238 | + "filesystem cannot honor 0600 and it logs a warning. Persisting the key " |
| 239 | + "is the feature (it must survive restarts).", |
| 240 | + ), |
| 241 | + ( |
| 242 | + "py/clear-text-storage-sensitive-data", |
| 243 | + "homeassistant-addon-webhook-proxy-dev/mcp_proxy_dev/oauth.py", |
| 244 | + "as clear text", |
| 245 | + "Warned fallback (dev flavor, identical code to stable): plain write of " |
| 246 | + "the signing key only when the filesystem cannot honor 0600, with a " |
| 247 | + "warning. Persistence is the feature.", |
| 248 | + ), |
| 249 | + ( |
| 250 | + "py/clear-text-storage-sensitive-data", |
| 251 | + "homeassistant-addon-webhook-proxy/start.py", |
| 252 | + "as clear text", |
| 253 | + "Stable flavor pending promote: the creds file uses _atomic_write_0600 " |
| 254 | + "with a warned plain-write fallback; the proxy-config handoff write " |
| 255 | + "gained the same 0600-first treatment on the dev flavor (v2.0.3.dev1) " |
| 256 | + "and reaches this tree via the promote workflow — the stable-guard " |
| 257 | + "blocks editing it directly here. Remove the plain-write half of this " |
| 258 | + "reason after the next promote.", |
| 259 | + ), |
| 260 | + ( |
| 261 | + "py/clear-text-storage-sensitive-data", |
| 262 | + "homeassistant-addon-webhook-proxy-dev/start.py", |
| 263 | + "as clear text", |
| 264 | + "Warned fallbacks (dev flavor): both the creds file and the " |
| 265 | + "proxy-config handoff file write via _atomic_write_0600; the flagged " |
| 266 | + "plain writes only run when the filesystem cannot honor 0600 and each " |
| 267 | + "logs a warning. Persistence is the feature.", |
| 268 | + ), |
| 269 | + ( |
| 270 | + "py/weak-sensitive-data-hashing", |
| 271 | + "custom_components/ha_mcp_tools/oauth_legacy.py", |
| 272 | + "hashing algorithm (SHA256)", |
| 273 | + "Not password storage: SHA256 builds a change-detection fingerprint of " |
| 274 | + "the OAuth identity bound to the root views (machine-generated client " |
| 275 | + "secret + 256-bit signing key) to decide when routes must be rebound. " |
| 276 | + "KDFs exist to slow guessing of low-entropy human passwords; a " |
| 277 | + "fingerprint of high-entropy random material has no guessing surface.", |
| 278 | + ), |
| 279 | + ( |
| 280 | + "py/weak-sensitive-data-hashing", |
| 281 | + "homeassistant-addon-webhook-proxy/mcp_proxy/__init__.py", |
| 282 | + "hashing algorithm (SHA256)", |
| 283 | + "Not password storage: same _oauth_route_fingerprint helper as " |
| 284 | + "oauth_legacy.py — a SHA256 change-detection fingerprint of " |
| 285 | + "machine-generated high-entropy credentials, not a stored password hash.", |
| 286 | + ), |
| 287 | + ( |
| 288 | + "py/weak-sensitive-data-hashing", |
| 289 | + "homeassistant-addon-webhook-proxy-dev/mcp_proxy_dev/__init__.py", |
| 290 | + "hashing algorithm (SHA256)", |
| 291 | + "Not password storage (dev flavor, identical code to stable): SHA256 " |
| 292 | + "change-detection fingerprint of machine-generated high-entropy " |
| 293 | + "credentials, not a stored password hash.", |
| 294 | + ), |
| 295 | + ( |
| 296 | + "py/bad-tag-filter", |
| 297 | + "tests/src/unit/_js_harness.py", |
| 298 | + "does not match upper case", |
| 299 | + "Not a sanitizer: the JSDOM harness extracts <script> bodies from " |
| 300 | + "repo-authored, lowercase templates for parse/behaviour testing; " |
| 301 | + "untrusted HTML never flows through it.", |
| 302 | + ), |
| 303 | + ( |
| 304 | + "py/incomplete-url-substring-sanitization", |
| 305 | + "tests/src/unit/test_best_practice_checker.py", |
| 306 | + "may be at an arbitrary position", |
| 307 | + "Test assertion, not URL validation: checks that a warning message " |
| 308 | + "mentions the configured skill-prefix host.", |
| 309 | + ), |
| 310 | + ( |
| 311 | + "py/incomplete-url-substring-sanitization", |
| 312 | + "tests/src/unit/test_browser_landing.py", |
| 313 | + "may be at an arbitrary position", |
| 314 | + "Test assertion, not URL validation: checks that the landing page's " |
| 315 | + "help copy mentions dash.cloudflare.com.", |
| 316 | + ), |
| 317 | + ( |
| 318 | + "py/incomplete-url-substring-sanitization", |
| 319 | + "tests/src/unit/test_oauth.py", |
| 320 | + "may be at an arbitrary position", |
| 321 | + "Test assertion, not URL validation: checks that the consent HTML " |
| 322 | + "displays the redirect host to the user.", |
| 323 | + ), |
| 324 | + ( |
| 325 | + "py/incomplete-url-substring-sanitization", |
| 326 | + "tests/src/unit/test_oauth_legacy_component.py", |
| 327 | + "may be at an arbitrary position", |
| 328 | + "Test assertion, not URL validation: the XSS-escape test checks the " |
| 329 | + "redirect host appears (escaped) in the response body.", |
| 330 | + ), |
163 | 331 | ) |
164 | 332 |
|
165 | 333 |
|
|
0 commit comments