from mitmproxy import http from urllib.parse import urlparse, parse_qs class ClientSecretChecker: def is_oauth_uri(self, uri: str) -> bool: qs = parse_qs(urlparse(uri).query) keys = qs.keys() return "client_id" in keys and ("client_secret" in keys or "client_secret" in uri) def has_client_secret_in_uri(self, uri: str) -> bool: qs = parse_qs(urlparse(uri).query) return "client_secret" in qs def is_post_with_client_secret(self, flow: http.HTTPFlow) -> bool: if flow.request.method != "POST": return False content_type = flow.request.headers.get("Content-Type", "") if "application/x-www-form-urlencoded" in content_type: body = parse_qs(flow.request.get_text()) return "client_secret" in body return False def is_exposed_in_referer(self, flow: http.HTTPFlow) -> bool: referer = flow.request.headers.get("Referer", "") return "client_secret" in referer def check_client_secret_leak(self, flow: http.HTTPFlow) -> list[str]: messages = [] if self.has_client_secret_in_uri(flow.request.url): messages.append("client_secret found in URL query string") if self.is_post_with_client_secret(flow): messages.append("client_secret found in POST body") if self.is_exposed_in_referer(flow): messages.append("client_secret exposed in Referer header") return messages async def request(self, flow: http.HTTPFlow) -> None: try: if not self.is_oauth_uri(flow.request.url): return issues = self.check_client_secret_leak(flow) if issues: print(f"[HIGH] OAuth Client Secret Exposure: {' | '.join(issues)}") print(f"[URL] {flow.request.url}") except Exception as e: print(f"[ERROR] Client Secret Check failed: {e}")