#!/usr/bin/env python3 """Synthetic browser/server boundary and key-rotation exercise. Python 3.10+. SPDX-License-Identifier: MIT No Lovable/Supabase service or real provider is contacted. No remote target accepted. """ from datetime import datetime, timezone import hashlib from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json from pathlib import Path import platform import secrets import sys import threading from urllib.error import HTTPError from urllib.request import build_opener, ProxyHandler, Request def main(): if len(sys.argv) != 1: raise SystemExit('Usage: python3 run.py > results.json; local fixture only') # All credentials are synthetic, created only for this run and never printed. old_key, new_key = secrets.token_urlsafe(32), secrets.token_urlsafe(32) session_alpha, session_beta = secrets.token_urlsafe(24), secrets.token_urlsafe(24) provider = {'accepted_keys': {old_key}, 'calls': 0} backend = {'key': old_key} sessions = {session_alpha: 'alpha', session_beta: 'beta'} checks = [] public_marker = 'demo_publishable_key_not_a_real_credential' client_js = ( f'const projectKey = "{public_marker}";\n' 'document.querySelector("output").textContent = "Synthetisches Browserbeispiel";\n' '// Session handling is intentionally not implemented in this HTTP fixture.\n' '// No provider key is shipped in this file.\n' ).encode() html = b'Secrets-Fixture' def provider_call(key): if key not in provider['accepted_keys']: return 401 provider['calls'] += 1 return 200 class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass def reply(self, status, data, content_type='application/json'): payload = data if isinstance(data, bytes) else json.dumps(data).encode() self.send_response(status) self.send_header('Content-Type', content_type) self.send_header('Cache-Control', 'no-store') self.send_header('Content-Length', str(len(payload))) self.end_headers() self.wfile.write(payload) def do_GET(self): if self.path == '/': return self.reply(200, html, 'text/html; charset=utf-8') if self.path == '/app.js': return self.reply(200, client_js, 'text/javascript; charset=utf-8') return self.reply(404, {'code': 'not_found'}) def do_POST(self): if self.path != '/api/summary': return self.reply(404, {'code': 'not_found'}) tenant = sessions.get(self.headers.get('Authorization', '').removeprefix('Bearer ')) if tenant is None: return self.reply(401, {'code': 'authentication_required'}) lengths = self.headers.get_all('Content-Length', []) if self.headers.get('Transfer-Encoding') or len(lengths) != 1 or not lengths[0].isdigit(): return self.reply(400, {'code': 'invalid_length'}) length = int(lengths[0]) if length > 1024: return self.reply(413, {'code': 'payload_too_large'}) try: body = json.loads(self.rfile.read(length)) except (ValueError, UnicodeError): return self.reply(400, {'code': 'invalid_json'}) if not isinstance(body, dict) or set(body) != {'contact_id'}: return self.reply(400, {'code': 'invalid_input'}) # The object-level check happens BEFORE privileged provider work. if body['contact_id'] != tenant + '-contact': return self.reply(403, {'code': 'contact_forbidden'}) if provider_call(backend['key']) != 200: return self.reply(502, {'code': 'provider_unavailable'}) return self.reply(200, {'summary': 'Synthetische Zusammenfassung'}) server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() base = f'http://127.0.0.1:{server.server_port}' def request(path, token=None, body=None): headers = {'Content-Type': 'application/json'} if token: headers['Authorization'] = 'Bearer ' + token req = Request(base + path, data=None if body is None else json.dumps(body).encode(), headers=headers) try: response = build_opener(ProxyHandler({})).open(req, timeout=10) except HTTPError as error: response = error with response: payload = response.read() if old_key.encode() in payload or new_key.encode() in payload: raise RuntimeError('Privileged secret leaked in response') return response.status, payload def check(label, condition): if not condition: raise RuntimeError(label) checks.append({'case': label, 'passed': True}) try: check('HTML served without privileged key', request('/')[0] == 200) status, javascript = request('/app.js') check('public marker is visible in JavaScript', status == 200 and public_marker.encode() in javascript) # Positive control proves that a simple byte scan can find this exact fake secret. unsafe_sample = b'const key="' + old_key.encode() + b'";' check('scan detects intentionally exposed synthetic secret', old_key.encode() in unsafe_sample) check('served JavaScript contains neither provider key', old_key.encode() not in javascript and new_key.encode() not in javascript) check('anonymous denied', request('/api/summary', body={'contact_id': 'alpha-contact'})[0] == 401) check('public key is not a user session', request('/api/summary', public_marker, {'contact_id': 'alpha-contact'})[0] == 401) check('foreign tenant denied', request('/api/summary', session_beta, {'contact_id': 'alpha-contact'})[0] == 403) check('arbitrary provider URL rejected', request('/api/summary', session_alpha, {'contact_id': 'alpha-contact', 'url': 'https://example.invalid'})[0] == 400) check('denied requests did not reach provider', provider['calls'] == 0) check('authorized request succeeds', request('/api/summary', session_alpha, {'contact_id': 'alpha-contact'})[0] == 200) provider['accepted_keys'].add(new_key) check('creating a second key does not revoke the first', provider_call(old_key) == 200) backend['key'] = new_key check('backend works with replacement', request('/api/summary', session_alpha, {'contact_id': 'alpha-contact'})[0] == 200) provider['accepted_keys'].remove(old_key) check('old key fails after explicit revocation', provider_call(old_key) == 401) check('backend remains available after revocation', request('/api/summary', session_alpha, {'contact_id': 'alpha-contact'})[0] == 200) backend['key'] = old_key check('stale backend fails without leaking credential', request('/api/summary', session_alpha, {'contact_id': 'alpha-contact'})[0] == 502) print(json.dumps({ 'recorded_at': datetime.now(timezone.utc).isoformat(), 'python': platform.python_version(), 'scope': 'Local HTTP example with synthetic sessions and in-memory provider stub. Not a Lovable export, Vite build, Supabase Auth test, browser execution or real provider rotation.', 'source_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), 'passed': len(checks), 'checks': checks, }, indent=2)) finally: server.shutdown() server.server_close() thread.join(timeout=10) if __name__ == '__main__': main()