H-391: bound network collection follow-ups #3

Merged
rogee merged 2 commits from agent/agent/07598cab into main 2026-08-21 13:44:16 +08:00
5 changed files with 471 additions and 103 deletions
+76 -45
View File
@@ -1,45 +1,76 @@
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response","request_id":"2473485.2","response":{"url":"http://127.0.0.1:40745/favicon.ico","status":200,"protocol":"http/1.1","mime_type":"text/html","headers":{"connection":"close","content-length":"38","content-type":"text/html","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_finished","request_id":"2473485.2","encoded_bytes":158}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"2473485.3","at":1787237512.679264,"request":{"method":"GET","url":"http://127.0.0.1:40745/ok","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request_headers","request_id":"2473485.3","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response_headers","request_id":"2473485.3","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"11","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT","set-cookie":"[REDACTED]"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response","request_id":"2473485.3","response":{"url":"http://127.0.0.1:40745/ok","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"11","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_finished","request_id":"2473485.3","encoded_bytes":291}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"2473485.4","at":1787237512.684393,"request":{"method":"GET","url":"http://127.0.0.1:40745/secret","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request_headers","request_id":"2473485.4","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response_headers","request_id":"2473485.4","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"49","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response","request_id":"2473485.4","response":{"url":"http://127.0.0.1:40745/secret","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"49","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_finished","request_id":"2473485.4","encoded_bytes":253}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"2473485.5","at":1787237512.688362,"request":{"method":"GET","url":"http://127.0.0.1:40745/large","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request_headers","request_id":"2473485.5","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response_headers","request_id":"2473485.5","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"2048","content-type":"text/plain","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response","request_id":"2473485.5","response":{"url":"http://127.0.0.1:40745/large","status":200,"protocol":"http/1.1","mime_type":"text/plain","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"2048","content-type":"text/plain","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_finished","request_id":"2473485.5","encoded_bytes":2248}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"2473485.6","at":1787237512.692057,"request":{"method":"GET","url":"https://127.0.0.1:35233/secure","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"C5A175B024C8583D1FE54757455E50C2","at":1787237512.692597,"request":{"method":"OPTIONS","url":"https://127.0.0.1:35233/secure","headers":{"accept":"*/*","access-control-request-headers":"authorization","access-control-request-method":"GET","origin":"http://127.0.0.1:40745","sec-fetch-mode":"cors","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request_headers","request_id":"C5A175B024C8583D1FE54757455E50C2","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","access-control-request-headers":"authorization","access-control-request-method":"GET","connection":"keep-alive","host":"127.0.0.1:35233","origin":"http://127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"cross-site","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response_headers","request_id":"C5A175B024C8583D1FE54757455E50C2","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response","request_id":"C5A175B024C8583D1FE54757455E50C2","response":{"url":"https://127.0.0.1:35233/secure","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_finished","request_id":"C5A175B024C8583D1FE54757455E50C2","encoded_bytes":0}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request_headers","request_id":"2473485.6","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:35233","origin":"http://127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"cross-site","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response_headers","request_id":"2473485.6","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_response","request_id":"2473485.6","response":{"url":"https://127.0.0.1:35233/secure","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Thu, 20 Aug 2026 14:51:52 GMT"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_finished","request_id":"2473485.6","encoded_bytes":219}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"websocket_created","connection_id":"2473485.7","url":"ws://127.0.0.1:40745/ws"}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"websocket_handshake_request","connection_id":"2473485.7","headers":{"accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","cache-control":"no-cache","connection":"Upgrade","cookie":"[REDACTED]","host":"127.0.0.1:40745","origin":"http://127.0.0.1:40745","pragma":"no-cache","sec-websocket-extensions":"permessage-deflate; client_max_window_bits","sec-websocket-key":"[REDACTED]","sec-websocket-version":"13","upgrade":"websocket","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"websocket_handshake_response","connection_id":"2473485.7","status":101,"headers":{"connection":"Upgrade","sec-websocket-accept":"[REDACTED]","upgrade":"websocket"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"websocket_frame","connection_id":"2473485.7","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"websocket_frame","connection_id":"2473485.7","direction":"received","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"websocket_closed","connection_id":"2473485.7"}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"2473485.8","at":1787237512.717744,"request":{"method":"GET","url":"http://127.0.0.1:40479/missing","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_request","request_id":"0A5A1C88A693E98CBD1A4317FB1311EF","at":1787237512.718296,"request":{"method":"OPTIONS","url":"http://127.0.0.1:40479/missing","headers":{"accept":"*/*","access-control-request-headers":"authorization","access-control-request-method":"GET","origin":"http://127.0.0.1:40745","sec-fetch-mode":"cors","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_failed","request_id":"0A5A1C88A693E98CBD1A4317FB1311EF","error":{"text":"net::ERR_CONNECTION_REFUSED","canceled":false}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_failed","request_id":"2473485.8","error":{"text":"net::ERR_CONNECTION_REFUSED","canceled":false}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_body","request_id":"2473485.3","body":{"state":"fetched","storage":"omitted","reason":"default_body_policy","bytes":11}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_body","request_id":"2473485.4","body":{"state":"fetched","storage":"omitted","reason":"default_body_policy","bytes":49}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_body","request_id":"2473485.5","body":{"state":"size_limit","storage":"omitted","bytes":2048}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_body","request_id":"2473485.6","body":{"state":"fetched","storage":"omitted","reason":"default_body_policy","bytes":15}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"http_body","request_id":"2473485.8","body":{"state":"unavailable","storage":"omitted","reason":"cdp_error","error":"-32000: No data found for resource with given identifier"}}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"cdp_disconnected","source":"socket","code":1006,"reason":"socket_closed","was_clean":false}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"4BC8ED5ED08A27BBC10AEEAB38A304D2","kind":"target_closed","reason":"Target.closeTarget"}
{"schema_version":"1.0","session_id":"m0-7bb3bf9e-f607-4f75-bfbb-ebd44c149245","target_id":"AC8BC0503D429618D7033F5AE0731A3C","kind":"cdp_reconnected","verified":true}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.2","response":{"url":"http://127.0.0.1:40745/favicon.ico","status":200,"protocol":"http/1.1","mime_type":"text/html","headers":{"connection":"close","content-length":"38","content-type":"text/html","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"unknown"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.2","encoded_bytes":158}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.3","at":1787290159.271732,"request":{"method":"GET","url":"http://127.0.0.1:40745/ok","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"587271.3","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"587271.3","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"11","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT","set-cookie":"[REDACTED]"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.3","response":{"url":"http://127.0.0.1:40745/ok","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"11","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.3","encoded_bytes":291}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.4","at":1787290159.276499,"request":{"method":"GET","url":"http://127.0.0.1:40745/secret","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"587271.4","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"587271.4","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"49","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.4","response":{"url":"http://127.0.0.1:40745/secret","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"49","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.4","encoded_bytes":253}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.5","at":1787290159.281042,"request":{"method":"GET","url":"http://127.0.0.1:40745/large","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"587271.5","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"587271.5","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"2048","content-type":"text/plain","date":"Fri, 21 Aug 2026 05:29:19 GMT"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.5","response":{"url":"http://127.0.0.1:40745/large","status":200,"protocol":"http/1.1","mime_type":"text/plain","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"2048","content-type":"text/plain","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.5","encoded_bytes":2248}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.6","at":1787290159.284519,"request":{"method":"GET","url":"http://127.0.0.1:40745/binary","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"587271.6","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"587271.6","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"4","content-type":"application/octet-stream","date":"Fri, 21 Aug 2026 05:29:19 GMT"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.6","response":{"url":"http://127.0.0.1:40745/binary","status":200,"protocol":"http/1.1","mime_type":"application/octet-stream","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"4","content-type":"application/octet-stream","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.6","encoded_bytes":215}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.7","at":1787290159.288707,"request":{"method":"GET","url":"http://127.0.0.1:40745/cache","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"587271.7","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-origin","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"587271.7","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","cache-control":"public, max-age=3600","connection":"close","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.7","response":{"url":"http://127.0.0.1:40745/cache","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","cache-control":"public, max-age=3600","connection":"close","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.7","encoded_bytes":256}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.8","at":1787290159.292334,"request":{"method":"GET","url":"http://127.0.0.1:40745/cache","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.8","response":{"url":"http://127.0.0.1:40745/cache","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","cache-control":"public, max-age=3600","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"hit","source":"disk_cache"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.8","encoded_bytes":0}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.9","at":1787290159.295659,"request":{"method":"GET","url":"https://127.0.0.1:33657/secure","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"74F2705CE3D08779CC1A8C8298034EBA","at":1787290159.296185,"request":{"method":"OPTIONS","url":"https://127.0.0.1:33657/secure","headers":{"accept":"*/*","access-control-request-headers":"authorization","access-control-request-method":"GET","origin":"http://127.0.0.1:40745","sec-fetch-mode":"cors","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"74F2705CE3D08779CC1A8C8298034EBA","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","access-control-request-headers":"authorization","access-control-request-method":"GET","connection":"keep-alive","host":"127.0.0.1:33657","origin":"http://127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"cross-site","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"74F2705CE3D08779CC1A8C8298034EBA","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"74F2705CE3D08779CC1A8C8298034EBA","response":{"url":"https://127.0.0.1:33657/secure","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"74F2705CE3D08779CC1A8C8298034EBA","encoded_bytes":0}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request_headers","request_id":"587271.9","headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","connection":"keep-alive","cookie":"[REDACTED]","host":"127.0.0.1:33657","origin":"http://127.0.0.1:40745","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"cross-site","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response_headers","request_id":"587271.9","status":200,"headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_response","request_id":"587271.9","response":{"url":"https://127.0.0.1:33657/secure","status":200,"protocol":"http/1.1","mime_type":"application/json","headers":{"access-control-allow-headers":"authorization","access-control-allow-origin":"*","connection":"close","content-length":"15","content-type":"application/json","date":"Fri, 21 Aug 2026 05:29:19 GMT"},"cache":{"state":"miss","source":"network"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_finished","request_id":"587271.9","encoded_bytes":219}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_created","connection_id":"587271.10","url":"ws://127.0.0.1:40745/ws"}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_handshake_request","connection_id":"587271.10","headers":{"accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","cache-control":"no-cache","connection":"Upgrade","cookie":"[REDACTED]","host":"127.0.0.1:40745","origin":"http://127.0.0.1:40745","pragma":"no-cache","sec-websocket-extensions":"permessage-deflate; client_max_window_bits","sec-websocket-key":"[REDACTED]","sec-websocket-version":"13","upgrade":"websocket","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_handshake_response","connection_id":"587271.10","status":101,"headers":{"connection":"Upgrade","sec-websocket-accept":"[REDACTED]","upgrade":"websocket"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frames_dropped","connection_id":"587271.10","dropped":{"count":27,"reasons":{"frame_size_limit":1,"rate_limit":26},"max_payload_bytes":2048,"limits":{"frame_bytes":1024,"events_per_second":16,"events_per_connection":16}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_frame","connection_id":"587271.10","direction":"sent","opcode":1,"payload_bytes":14,"payload":{"state":"omitted","reason":"frame_policy"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"websocket_closed","connection_id":"587271.10"}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"587271.11","at":1787290159.325745,"request":{"method":"GET","url":"http://127.0.0.1:44721/missing","headers":{"accept-language":"en-US,en;q=0.9","authorization":"[REDACTED]","cookie":"[REDACTED]","referer":"[REDACTED]","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_request","request_id":"ECE84A8DE1643AB660CABF3F14E80346","at":1787290159.326374,"request":{"method":"OPTIONS","url":"http://127.0.0.1:44721/missing","headers":{"accept":"*/*","access-control-request-headers":"authorization","access-control-request-method":"GET","origin":"http://127.0.0.1:40745","sec-fetch-mode":"cors","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36"}}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_failed","request_id":"ECE84A8DE1643AB660CABF3F14E80346","error":{"text":"net::ERR_CONNECTION_REFUSED","canceled":false}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_failed","request_id":"587271.11","error":{"text":"net::ERR_CONNECTION_REFUSED","canceled":false}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.3","body":{"state":"fetched","source":"network","encoding":"utf8","storage":"omitted","reason":"default_body_policy","bytes":11}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.4","body":{"state":"fetched","source":"network","encoding":"utf8","storage":"omitted","reason":"default_body_policy","bytes":49}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.5","body":{"state":"size_limit","source":"network","encoding":"utf8","storage":"omitted","reason":"size_limit","bytes":2048}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.6","body":{"state":"fetched","source":"network","encoding":"base64","storage":"omitted","reason":"default_body_policy","bytes":4}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.9","body":{"state":"fetched","source":"network","encoding":"utf8","storage":"omitted","reason":"default_body_policy","bytes":15}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.8","body":{"state":"fetched","source":"disk_cache","encoding":"utf8","storage":"omitted","reason":"default_body_policy","bytes":15}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"http_body","request_id":"587271.11","body":{"state":"unavailable","source":"network","encoding":"unknown","storage":"omitted","reason":"cdp_error","error":"-32000: No data found for resource with given identifier"}}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"cdp_disconnected","source":"socket","code":1006,"reason":"socket_closed","was_clean":false}
{"schema_version":"1.0","session_id":"m0-27579b66-06dc-41af-bdf1-903e108f9909","target_id":"8F9769D4DEDE977DC076F722088FC829","kind":"target_closed","reason":"Target.closeTarget"}
{"schema_version":"1.0","session_id":"m0-152f5177-9a3f-4df5-a489-2f78d213358d","target_id":"E1FC8181E677B2656E0B68AE80D14CD7","kind":"cdp_reconnected","verified":true}
{"schema_version":"1.0","session_id":"m0-152f5177-9a3f-4df5-a489-2f78d213358d","target_id":"E1FC8181E677B2656E0B68AE80D14CD7","kind":"cdp_disconnected","source":"socket","code":1006,"reason":"socket_closed","was_clean":false}
{"schema_version":"1.0","session_id":"m0-152f5177-9a3f-4df5-a489-2f78d213358d","target_id":"E1FC8181E677B2656E0B68AE80D14CD7","kind":"target_closed","reason":"Target.closeTarget"}
+42 -4
View File
@@ -14,17 +14,55 @@
"response_body_success": "pass",
"response_body_size_limit": "pass",
"response_body_unavailable": "pass",
"cached_body_semantics": "pass",
"binary_body_semantics": "pass",
"websocket_limits": "pass",
"multi_session_isolation": "pass",
"target_close_survives": "pass",
"cdp_disconnect_reconnect": "pass",
"sensitive_header_redaction": "pass",
"sensitive_body_redaction": "pass"
},
"resources": {
"process_count": 9,
"rss_kib": 803048,
"cpu_percent_snapshot": 10.3
"sample_count": 5,
"interval_ms": 200,
"process_count": {
"min": 9,
"max": 9
},
"rss_kib": {
"min": 779396,
"max": 779620,
"mean": 779528.8,
"p95": 779620
},
"cpu_percent": {
"min": 10.3,
"max": 10.7,
"mean": 10.5,
"p95": 10.7
}
},
"event_count": 45,
"resource_thresholds": {
"max_rss_kib": 1500000,
"max_process_count": 20,
"conclusion": "within_threshold"
},
"startup": {
"requested_samples": 3,
"successful_samples": 3,
"max_renderer_failure_rate": 0.34,
"attempts": 3,
"renderer_failures": 0,
"renderer_failure_rate": 0,
"conclusion": "within_threshold"
},
"event_count": 76,
"max_body_bytes": 1024,
"websocket_limits": {
"frame_bytes": 1024,
"events_per_second": 16,
"events_per_connection": 16
},
"attempt_failures": []
}
+12 -8
View File
@@ -11,7 +11,7 @@ M0_CDP_PORT=40746 M0_HTTP_PORT=40745 \
npm test
```
The two explicit loopback ports make the recorded run directly comparable; omit them to select free ports automatically, or change them if already occupied.
The two explicit loopback ports make the recorded run directly comparable; omit them to select free ports automatically, or change them if already occupied. One command performs three independent startup/full-traffic samples by default. Override `M0_STARTUP_SAMPLES` only when gathering a larger baseline; values below two are rejected.
The spike starts only loopback listeners and uses generated, one-day self-signed TLS material in a temporary directory. Chromium accepts that local certificate with `--ignore-certificate-errors`; no proxy, CA installation, traffic interception, VNC, or external fixture is involved. `artifacts/m0-events.sample.jsonl` is the sanitized event output and `artifacts/m0-report.json` is the run result/resource snapshot.
@@ -19,7 +19,11 @@ The spike starts only loopback listeners and uses generated, one-day self-signed
Clark is launched directly with `--remote-debugging-address=127.0.0.1`. The runner waits on `/json/version`, discovers the startup page through `/json/list`, connects to its `webSocketDebuggerUrl`, and navigates to the already-listening loopback fixture before triggering test traffic. This avoids `clarkserve`'s current `0.0.0.0` default; a protected gateway and short-lived credentials remain required outside a local Spike.
Every JSONL line has `schema_version`, `session_id`, `target_id`, and `kind`. HTTP records use `request_id`; WebSocket records use the CDP `requestId` as `connection_id`. Kind-specific fields are nested under `request`, `response`, `headers`, `body`, or `error`. Query strings are removed, sensitive headers are replaced by `[REDACTED]`, and WebSocket payloads are never persisted. Response bodies are safe by default: a fetched body is recorded as `{state:"fetched",storage:"omitted",reason:"default_body_policy",bytes:N}` with no content; oversize and unavailable bodies retain distinct structured states.
Every JSONL line has `schema_version`, `session_id`, `target_id`, and `kind`. HTTP records use `request_id`; WebSocket records use the CDP `requestId` as `connection_id`. Kind-specific fields are nested under `request`, `response`, `headers`, `body`, or `error`. Query strings are removed, sensitive headers are replaced by `[REDACTED]`, and WebSocket payloads are never persisted.
Response bodies are safe by default and never contain content. Their stable metadata is `{state,source,encoding,storage,reason,bytes}`: `source` is `network`, `memory_cache`, `disk_cache`, `prefetch_cache`, or `service_worker`; `encoding` is `utf8`, `base64`, or `unknown`; and `storage` remains `omitted`. Oversize and CDP-unavailable bodies keep `size_limit` and `unavailable` states. The local fixture fetches a cacheable response twice and an `application/octet-stream` response to assert cached and binary semantics.
Session filtering happens before pagination and export serialization. The validation closes two distinct CDP targets bound to two generated session IDs, then asserts that queries, exports, and disconnect records contain only the requested session/target.
### Coverage and limits
@@ -29,18 +33,18 @@ Every JSONL line has `schema_version`, `session_id`, `target_id`, and `kind`. HT
| Failed HTTP request | Pass | Unused loopback port produces `loadingFailed`; error text retained. |
| HTTPS without MITM | Pass | Direct browser connection to local TLS fixture; no network intermediary. |
| WebSocket lifecycle and frames | Pass | Created, handshake, sent, received, and closed share one `connection_id`; payload omitted. |
| WebSocket pressure limits | Pass | Defaults: 1,024 bytes/frame, 16 events/second, and 16 emitted events/connection. Drops are aggregated into one payload-free record with `frame_size_limit`, `rate_limit`, or `event_limit` counts. |
| `getResponseBody` success | Pass | Called after `loadingFinished`; fetched bytes are recorded but content is omitted by default. |
| Cached and binary body | Pass | Repeated `force-cache` response records its cache source; octet-stream records `encoding=base64`; neither persists content. |
| Sensitive response body | Pass | Random secret fixture is fetched; serialized JSONL is asserted not to contain its value. |
| Body size limit | Pass | 2 KiB fixture is reported as `size_limit` and not persisted with the 1 KiB default. |
| Body unavailable | Pass | Calling on the failed request returns the original CDP error as structured `cdp_error`. |
| Target close | Pass | Target connection closes; browser process remains alive. |
| CDP disconnect/reconnect | Pass | Browser-side target close triggers the real socket `close` callback; the browser stays alive and a new target accepts a verified CDP command. |
| Cookie / Authorization / Set-Cookie | Pass | `Network.*ExtraInfo` values are redacted before JSONL serialization. |
| Cache-specific body availability | Not covered | Cache behavior varies by response/cache mode; retain optional-body semantics in M1. |
| Sustained high-rate WebSocket | Not covered | M0 stores metadata only; add frame/event caps before M1 load testing. |
| Multi-target/session isolation | Not covered | Explicitly M1 scope. |
| Startup `Page.navigate` | Intermittent | This build can report `Inspector.detached: Render process gone`; the exact startup failure is bounded and recorded. |
| Multi-target/session isolation | Pass | Two target/session pairs keep query, export, and real socket-close records isolated. |
| Startup `Page.navigate` | Sampled | Three successful startup/full-traffic samples are required; exact `Render process gone.` retries are recorded and must stay at or below a 0.34 failure rate. Other discovery, WebSocket, timeout, and post-startup errors fail immediately. |
The resource numbers are a one-time `ps` snapshot of the browser process tree, not a benchmark. Repeat under the intended container limits before setting capacity targets.
Each full sample takes five `ps` samples 200 ms apart from the browser process tree and reports min/max/mean/p95. The local guardrails are RSS max 1,500,000 KiB and at most 20 processes; exceeding either fails the run. These are single-instance development thresholds, not production capacity claims. Tune with `M0_RESOURCE_SAMPLES`, `M0_RESOURCE_SAMPLE_INTERVAL_MS`, `M0_MAX_RSS_KIB`, and `M0_MAX_PROCESS_COUNT` under the intended container limits.
The runner restarts Clark at most three times only from the local startup navigation/readiness block, and only after CDP reports the exact detached reason `Render process gone.`. Every failed attempt is retained in `attempt_failures`; a timeout without that reason and every post-startup error fail immediately with the original message.
The runner restarts Clark at most three times per startup sample only from the local navigation/readiness block, and only after CDP reports the exact detached reason `Render process gone.`. Every failed attempt is retained with sample, attempt, error, and detached reason. A timeout without that reason and every discovery, connection, or post-startup error fail immediately with the original message. The default renderer failure-rate guardrail is 0.34 and can be tightened with `M0_MAX_RENDERER_FAILURE_RATE` after more samples.
+273 -43
View File
@@ -45,26 +45,45 @@ export function safeUrl(raw) {
}
}
export function boundedBody(body, base64Encoded, maxBytes) {
export function boundedBody(body, base64Encoded, maxBytes, source = "network") {
const bytes = Buffer.from(body, base64Encoded ? "base64" : "utf8");
if (bytes.length > maxBytes) {
return { state: "size_limit", storage: "omitted", bytes: bytes.length };
return { state: "size_limit", source, encoding: base64Encoded ? "base64" : "utf8", storage: "omitted", reason: "size_limit", bytes: bytes.length };
}
return {
state: "fetched",
source,
encoding: base64Encoded ? "base64" : "utf8",
storage: "omitted",
reason: "default_body_policy",
bytes: bytes.length,
};
}
export function queryRecords(records, sessionId, { cursor = 0, limit = 100 } = {}) {
assert(sessionId, "sessionId is required");
assert(Number.isSafeInteger(cursor) && cursor >= 0, "cursor must be a non-negative integer");
assert(Number.isSafeInteger(limit) && limit > 0, "limit must be a positive integer");
// ponytail: linear scan fits the single-instance Spike; index by session if retained volume grows.
const matching = records.filter((record) => record.session_id === sessionId);
const items = matching.slice(cursor, cursor + limit);
return { items, next_cursor: cursor + items.length < matching.length ? cursor + items.length : null };
}
export function exportSession(records, sessionId) {
return queryRecords(records, sessionId, { limit: Number.MAX_SAFE_INTEGER }).items
.map((record) => JSON.stringify(record))
.join("\n");
}
export function shouldRetryStartup(detachedReason) {
return detachedReason === "Render process gone.";
}
class StartupNavigateFailure extends Error {
constructor(cause) {
constructor(cause, detachedReason) {
super(cause.message, { cause });
this.detachedReason = detachedReason;
}
}
@@ -79,11 +98,15 @@ class CDP {
this.detached = new Promise((resolve) => (this.resolveDetached = resolve));
}
async connect() {
async connect(timeoutMs = 20_000) {
this.ws = new WebSocket(this.url);
await new Promise((resolve, reject) => {
this.ws.onopen = resolve;
this.ws.onerror = () => reject(new Error("CDP WebSocket connection failed"));
const timer = setTimeout(() => {
this.ws.close();
reject(new Error("CDP WebSocket connection timed out"));
}, timeoutMs);
this.ws.onopen = () => { clearTimeout(timer); resolve(); };
this.ws.onerror = () => { clearTimeout(timer); reject(new Error("CDP WebSocket connection failed")); };
});
this.ws.onmessage = ({ data }) => this.onMessage(JSON.parse(data));
this.ws.onclose = ({ code, reason, wasClean }) => {
@@ -173,8 +196,11 @@ async function unusedPort() {
function websocketFrame(opcode, payload = "") {
const body = Buffer.from(payload);
assert(body.length < 126, "fixture frame must stay small");
return Buffer.concat([Buffer.from([0x80 | opcode, body.length]), body]);
assert(body.length <= 65_535, "fixture frame must fit a 16-bit length");
const header = body.length < 126
? Buffer.from([0x80 | opcode, body.length])
: Buffer.from([0x80 | opcode, 126, body.length >> 8, body.length & 0xff]);
return Buffer.concat([header, body]);
}
function acceptWebsocket(request, socket) {
@@ -276,6 +302,14 @@ async function startFixtures(tempDir) {
send(response, { ...cors, "content-type": "text/plain" }, "x".repeat(2_048));
return;
}
if (request.url === "/binary") {
send(response, { ...cors, "content-type": "application/octet-stream" }, Buffer.from([0, 255, 1, 254]));
return;
}
if (request.url === "/cache") {
send(response, { ...cors, "cache-control": "public, max-age=3600", "content-type": "application/json" }, '{"cached":true}');
return;
}
if (request.url === "/secret") {
send(response, { ...cors, "content-type": "application/json" }, JSON.stringify({ secret: secrets.body }));
return;
@@ -295,11 +329,18 @@ async function startFixtures(tempDir) {
await (await fetch("http://127.0.0.1:${httpPort}/ok")).text();
await (await fetch("http://127.0.0.1:${httpPort}/secret")).text();
await (await fetch("http://127.0.0.1:${httpPort}/large")).text();
await (await fetch("http://127.0.0.1:${httpPort}/binary")).arrayBuffer();
await (await fetch("http://127.0.0.1:${httpPort}/cache", { cache: "force-cache" })).text();
await (await fetch("http://127.0.0.1:${httpPort}/cache", { cache: "force-cache" })).text();
await (await fetch("https://127.0.0.1:${httpsPort}/secure")).text();
await new Promise((resolve, reject) => {
const ws = new WebSocket("ws://127.0.0.1:${httpPort}/ws");
ws.onopen = () => ws.send("client-message");
ws.onmessage = () => ws.close();
let received = 0;
ws.onopen = () => {
ws.send("x".repeat(2048));
for (let index = 0; index < 20; index++) ws.send("client-message");
};
ws.onmessage = () => { if (++received === 21) ws.close(); };
ws.onclose = resolve;
ws.onerror = reject;
});
@@ -400,6 +441,35 @@ function processSnapshot(rootPid) {
};
}
export function summarizeProcessSamples(samples, intervalMs) {
assert(samples.length > 1, "resource baseline requires multiple samples");
const summary = (name) => {
const values = samples.map((sample) => sample[name]).sort((a, b) => a - b);
return {
min: values[0],
max: values.at(-1),
mean: Number((values.reduce((total, value) => total + value, 0) / values.length).toFixed(1)),
p95: values[Math.ceil(values.length * 0.95) - 1],
};
};
return {
sample_count: samples.length,
interval_ms: intervalMs,
process_count: { min: Math.min(...samples.map((sample) => sample.process_count)), max: Math.max(...samples.map((sample) => sample.process_count)) },
rss_kib: summary("rss_kib"),
cpu_percent: summary("cpu_percent_snapshot"),
};
}
async function processBaseline(rootPid, sampleCount, intervalMs) {
const samples = [];
for (let index = 0; index < sampleCount; index++) {
if (index) await new Promise((resolve) => setTimeout(resolve, intervalMs));
samples.push(processSnapshot(rootPid));
}
return summarizeProcessSamples(samples, intervalMs);
}
function isRunning(pid) {
try {
process.kill(pid, 0);
@@ -409,16 +479,54 @@ function isRunning(pid) {
}
}
function eventCollector(sessionId, targetId) {
export function eventCollector(sessionId, targetId, {
maxWebSocketFrameBytes = 1_024,
maxWebSocketEventsPerSecond = 16,
maxWebSocketEventsPerConnection = 16,
} = {}) {
for (const [name, value] of Object.entries({ maxWebSocketFrameBytes, maxWebSocketEventsPerSecond, maxWebSocketEventsPerConnection })) {
assert(Number.isSafeInteger(value) && value > 0, `${name} must be a positive integer`);
}
const records = [];
const requestState = new Map();
const push = (kind, fields = {}) => records.push({
schema_version: "1.0",
session_id: sessionId,
target_id: targetId,
kind,
...fields,
});
const bodySources = new Map();
const webSocketLimits = new Map();
const push = (kind, fields = {}) => {
const record = {
schema_version: "1.0",
session_id: sessionId,
target_id: targetId,
kind,
...fields,
};
records.push(record);
return record;
};
const dropWebSocketFrame = (connectionId, reason, payloadBytes) => {
let state = webSocketLimits.get(connectionId);
if (!state) {
state = { window: null, windowEvents: 0, emitted: 0 };
webSocketLimits.set(connectionId, state);
}
if (!state.dropRecord) state.dropRecord = push("websocket_frames_dropped", {
connection_id: connectionId,
dropped: {
count: 0,
reasons: {},
max_payload_bytes: 0,
limits: {
frame_bytes: maxWebSocketFrameBytes,
events_per_second: maxWebSocketEventsPerSecond,
events_per_connection: maxWebSocketEventsPerConnection,
},
},
});
const dropped = state.dropRecord.dropped;
dropped.count++;
dropped.reasons[reason] = (dropped.reasons[reason] ?? 0) + 1;
dropped.max_payload_bytes = Math.max(dropped.max_payload_bytes, payloadBytes);
};
const listener = (method, params) => {
const requestId = params.requestId;
@@ -427,6 +535,7 @@ function eventCollector(sessionId, targetId) {
switch (method) {
case "Network.requestWillBeSent":
mark("request");
bodySources.set(requestId, "network");
push("http_request", {
request_id: requestId,
at: params.wallTime,
@@ -437,11 +546,18 @@ function eventCollector(sessionId, targetId) {
},
});
break;
case "Network.requestServedFromCache":
bodySources.set(requestId, "memory_cache");
push("http_cache_hit", { request_id: requestId, source: "memory_cache" });
break;
case "Network.requestWillBeSentExtraInfo":
push("http_request_headers", { request_id: requestId, headers: redactHeaders(params.headers) });
break;
case "Network.responseReceived":
mark("response");
if (params.response.fromServiceWorker) bodySources.set(requestId, "service_worker");
else if (params.response.fromDiskCache) bodySources.set(requestId, "disk_cache");
else if (params.response.fromPrefetchCache) bodySources.set(requestId, "prefetch_cache");
push("http_response", {
request_id: requestId,
response: {
@@ -450,6 +566,10 @@ function eventCollector(sessionId, targetId) {
protocol: params.response.protocol,
mime_type: params.response.mimeType,
headers: redactHeaders(params.response.headers),
cache: bodySources.has(requestId) ? {
state: bodySources.get(requestId) === "network" ? "miss" : "hit",
source: bodySources.get(requestId),
} : { state: "unknown" },
},
});
break;
@@ -481,11 +601,34 @@ function eventCollector(sessionId, targetId) {
case "Network.webSocketFrameReceived": {
const direction = method.endsWith("Sent") ? "sent" : "received";
mark(`ws_${direction}`);
const payloadBytes = params.response.opcode === 2
? Buffer.from(params.response.payloadData, "base64").length
: Buffer.byteLength(params.response.payloadData);
let state = webSocketLimits.get(requestId);
if (!state) {
state = { window: null, windowEvents: 0, emitted: 0 };
webSocketLimits.set(requestId, state);
}
const window = Math.floor(params.timestamp ?? 0);
if (state.window !== window) {
state.window = window;
state.windowEvents = 0;
}
state.windowEvents++;
let dropReason;
if (payloadBytes > maxWebSocketFrameBytes) dropReason = "frame_size_limit";
else if (state.windowEvents > maxWebSocketEventsPerSecond) dropReason = "rate_limit";
else if (state.emitted >= maxWebSocketEventsPerConnection) dropReason = "event_limit";
if (dropReason) {
dropWebSocketFrame(requestId, dropReason, payloadBytes);
break;
}
state.emitted++;
push("websocket_frame", {
connection_id: requestId,
direction,
opcode: params.response.opcode,
payload_bytes: Buffer.byteLength(params.response.payloadData),
payload_bytes: payloadBytes,
payload: { state: "omitted", reason: "frame_policy" },
});
break;
@@ -496,28 +639,33 @@ function eventCollector(sessionId, targetId) {
break;
}
};
return { records, requestState, push, listener };
return { records, requestState, bodySources, push, listener };
}
function findRequest(records, pathname) {
return records.find((record) => record.kind === "http_request" && new URL(record.request.url.replace("?<redacted>", "")).pathname === pathname)?.request_id;
}
function findRequests(records, pathname) {
return records.filter((record) => record.kind === "http_request" && new URL(record.request.url.replace("?<redacted>", "")).pathname === pathname).map((record) => record.request_id);
}
async function collectBody(cdp, collector, requestId, maxBodyBytes) {
const source = collector.bodySources.get(requestId) ?? "network";
try {
const result = await cdp.call("Network.getResponseBody", { requestId });
collector.push("http_body", { request_id: requestId, body: boundedBody(result.body, result.base64Encoded, maxBodyBytes) });
collector.push("http_body", { request_id: requestId, body: boundedBody(result.body, result.base64Encoded, maxBodyBytes, source) });
} catch (error) {
collector.push("http_body", {
request_id: requestId,
body: { state: "unavailable", storage: "omitted", reason: "cdp_error", error: error.message },
body: { state: "unavailable", source, encoding: "unknown", storage: "omitted", reason: "cdp_error", error: error.message },
});
}
}
function assertCoverage(collector, secrets) {
const { records, requestState } = collector;
for (const pathname of ["/ok", "/secret", "/large", "/secure"]) {
for (const pathname of ["/ok", "/secret", "/large", "/binary", "/cache", "/secure"]) {
const id = findRequest(records, pathname);
assert(id, `request observed for ${pathname}`);
assert.deepEqual([...requestState.get(id)].filter((value) => ["request", "response", "finished"].includes(value)).sort(), ["finished", "request", "response"]);
@@ -534,19 +682,47 @@ function assertCoverage(collector, secrets) {
assert(records.some((record) => record.kind === "http_request_headers" && record.headers.authorization === REDACTED), "Authorization is redacted");
assert(records.some((record) => record.kind === "http_response_headers" && record.headers["set-cookie"] === REDACTED), "Set-Cookie is redacted");
const secretBody = records.find((record) => record.kind === "http_body" && record.request_id === findRequest(records, "/secret"));
assert.deepEqual(secretBody?.body, { state: "fetched", storage: "omitted", reason: "default_body_policy", bytes: Buffer.byteLength(JSON.stringify({ secret: secrets.body })) });
assert.deepEqual(secretBody?.body, { state: "fetched", source: "network", encoding: "utf8", storage: "omitted", reason: "default_body_policy", bytes: Buffer.byteLength(JSON.stringify({ secret: secrets.body })) });
assert(records.some((record) => record.kind === "http_body" && record.body.state === "size_limit" && record.body.storage === "omitted"), "oversize body is omitted");
assert(records.some((record) => record.kind === "http_body" && record.body.state === "unavailable"), "unavailable body is structured");
assert(records.some((record) => record.kind === "http_body" && record.body.source.endsWith("_cache")), "cached body source is explicit");
assert(records.some((record) => record.kind === "http_body" && record.body.encoding === "base64"), "binary body encoding is explicit");
assert(records.some((record) => record.kind === "websocket_frames_dropped" && record.dropped.reasons.frame_size_limit), "oversize WebSocket frame drop is observable");
assert(records.some((record) => record.kind === "websocket_frames_dropped" && record.dropped.count > 0), "high-rate WebSocket drops are aggregated");
assert(records.some((record) => record.kind === "cdp_disconnected" && record.source === "socket"), "real CDP socket close is recorded");
assert(records.some((record) => record.kind === "cdp_reconnected" && record.verified), "CDP reconnect is verified");
}
async function main(attemptFailures = []) {
function assertIsolation(records, sessions) {
for (const { sessionId, targetId, otherSessionId } of sessions) {
const queried = queryRecords(records, sessionId, { limit: 1_000 }).items;
assert(queried.length > 0, `records exist for ${sessionId}`);
assert(queried.every((record) => record.session_id === sessionId && record.target_id === targetId), `query stays inside ${sessionId}`);
const exported = exportSession(records, sessionId);
assert(!exported.includes(otherSessionId), `export does not leak ${otherSessionId}`);
assert(queried.some((record) => record.kind === "cdp_disconnected"), `disconnect stays observable for ${sessionId}`);
}
}
async function main(attemptFailures = [], startup = { requested_samples: 1, successful_samples: 1, max_renderer_failure_rate: 1 }) {
const binary = process.env.CLARK_BINARY_PATH;
assert(binary, "set CLARK_BINARY_PATH to the clark-browser Chromium binary");
const outputDir = path.resolve(process.env.M0_OUTPUT_DIR ?? "artifacts");
const maxBodyBytes = Number(process.env.M0_MAX_BODY_BYTES ?? 1_024);
assert(Number.isSafeInteger(maxBodyBytes) && maxBodyBytes > 0, "M0_MAX_BODY_BYTES must be a positive integer");
const limits = {
maxWebSocketFrameBytes: Number(process.env.M0_MAX_WS_FRAME_BYTES ?? 1_024),
maxWebSocketEventsPerSecond: Number(process.env.M0_MAX_WS_EVENTS_PER_SECOND ?? 16),
maxWebSocketEventsPerConnection: Number(process.env.M0_MAX_WS_EVENTS_PER_CONNECTION ?? 16),
};
const resourceSampleCount = Number(process.env.M0_RESOURCE_SAMPLES ?? 5);
const resourceSampleIntervalMs = Number(process.env.M0_RESOURCE_SAMPLE_INTERVAL_MS ?? 200);
const maxRssKib = Number(process.env.M0_MAX_RSS_KIB ?? 1_500_000);
const maxProcessCount = Number(process.env.M0_MAX_PROCESS_COUNT ?? 20);
assert(Number.isSafeInteger(resourceSampleCount) && resourceSampleCount > 1, "M0_RESOURCE_SAMPLES must be an integer greater than one");
assert(Number.isSafeInteger(resourceSampleIntervalMs) && resourceSampleIntervalMs > 0, "M0_RESOURCE_SAMPLE_INTERVAL_MS must be a positive integer");
assert(Number.isSafeInteger(maxRssKib) && maxRssKib > 0, "M0_MAX_RSS_KIB must be a positive integer");
assert(Number.isSafeInteger(maxProcessCount) && maxProcessCount > 0, "M0_MAX_PROCESS_COUNT must be a positive integer");
const tempDir = await mkdtemp(path.join(os.tmpdir(), "lume-ctrl-m0-"));
const profile = path.join(tempDir, "profile");
@@ -583,7 +759,7 @@ async function main(attemptFailures = []) {
page = targets.find((target) => target.type === "page" && target.url === "about:blank");
assert(page, "clark must expose its startup page through /json/list");
const cdp = await new CDP(page.webSocketDebuggerUrl).connect();
const collector = eventCollector(sessionId, page.id);
const collector = eventCollector(sessionId, page.id, limits);
cdp.onClose((details) => collector.push("cdp_disconnected", { source: "socket", ...details }));
await cdp.call("Page.enable");
await cdp.call("Runtime.enable");
@@ -596,7 +772,7 @@ async function main(attemptFailures = []) {
} catch (error) {
const detachedReason = cdp.detachedReason ?? await cdp.waitForDetached(1_000);
if (DEBUG) console.error("startup failure", { detached_reason: detachedReason, error: error.message });
if (shouldRetryStartup(detachedReason)) throw new StartupNavigateFailure(error);
if (shouldRetryStartup(detachedReason)) throw new StartupNavigateFailure(error, detachedReason);
throw error;
}
cdp.onEvent(collector.listener);
@@ -613,13 +789,21 @@ async function main(attemptFailures = []) {
}, "fixture completion");
await waitFor(() => [...collector.requestState.values()].some((state) => state.has("ws_closed")), "WebSocket closure");
for (const pathname of ["/ok", "/secret", "/large", "/secure"]) {
for (const pathname of ["/ok", "/secret", "/large", "/binary", "/secure"]) {
await collectBody(cdp, collector, findRequest(collector.records, pathname), maxBodyBytes);
}
const cachedId = findRequests(collector.records, "/cache").find((requestId) => {
const source = collector.bodySources.get(requestId);
return source && source !== "network";
});
assert(cachedId, "a repeated force-cache request must be served from browser cache");
await collectBody(cdp, collector, cachedId, maxBodyBytes);
const failedId = [...collector.requestState.entries()].find(([, state]) => state.has("failed"))?.[0];
await collectBody(cdp, collector, failedId, maxBodyBytes);
const resources = processSnapshot(browserPid);
const resources = await processBaseline(browserPid, resourceSampleCount, resourceSampleIntervalMs);
assert(resources.rss_kib.max <= maxRssKib, `browser RSS ${resources.rss_kib.max} KiB exceeds ${maxRssKib} KiB`);
assert(resources.process_count.max <= maxProcessCount, `browser process count ${resources.process_count.max} exceeds ${maxProcessCount}`);
browserClient = await new CDP(version.webSocketDebuggerUrl).connect();
await browserClient.call("Target.closeTarget", { targetId: page.id });
await cdp.closed;
@@ -629,14 +813,33 @@ async function main(attemptFailures = []) {
assert(collector.records.some((record) => record.kind === "cdp_disconnected" && record.source === "socket"), "target closure emits a socket disconnect event");
const reconnectTarget = await getJson(`http://127.0.0.1:${cdpPort}/json/new?about:blank`, { method: "PUT" });
reconnectClient = await new CDP(reconnectTarget.webSocketDebuggerUrl).connect();
const reconnectSessionId = `m0-${randomUUID()}`;
const reconnectCollector = eventCollector(reconnectSessionId, reconnectTarget.id, limits);
reconnectClient.onClose((details) => reconnectCollector.push("cdp_disconnected", { source: "socket", ...details }));
const { result: reconnectResult } = await reconnectClient.call("Runtime.evaluate", { expression: "1 + 1", returnByValue: true });
assert.equal(reconnectResult.value, 2, "CDP reconnect can execute a command");
collector.push("cdp_reconnected", { target_id: reconnectTarget.id, verified: true });
reconnectCollector.push("cdp_reconnected", { verified: true });
assert(isRunning(browserPid), "disconnecting and reconnecting CDP does not exit clark-browser");
await browserClient.call("Target.closeTarget", { targetId: reconnectTarget.id });
await reconnectClient.closed;
reconnectCollector.push("target_closed", { reason: "Target.closeTarget" });
assert(isRunning(browserPid), "closing a second session target does not exit clark-browser");
assertCoverage(collector, fixtures.secrets);
const records = [...collector.records, ...reconnectCollector.records];
assertCoverage({ ...collector, records }, fixtures.secrets);
assertIsolation(records, [
{ sessionId, targetId: page.id, otherSessionId: reconnectSessionId },
{ sessionId: reconnectSessionId, targetId: reconnectTarget.id, otherSessionId: sessionId },
]);
const rendererFailures = attemptFailures.filter((failure) => failure.detached_reason === "Render process gone.").length;
const startupAttempts = startup.successful_samples + attemptFailures.length;
const rendererFailureRate = Number((rendererFailures / startupAttempts).toFixed(3));
const rendererConclusion = rendererFailureRate <= startup.max_renderer_failure_rate ? "within_threshold" : "exceeds_threshold";
if (startup.successful_samples === startup.requested_samples) {
assert.equal(rendererConclusion, "within_threshold", `renderer failure rate ${rendererFailureRate} exceeds ${startup.max_renderer_failure_rate}`);
}
await mkdir(outputDir, { recursive: true });
await writeFile(path.join(outputDir, "m0-events.sample.jsonl"), `${collector.records.map((record) => JSON.stringify(record)).join("\n")}\n`);
await writeFile(path.join(outputDir, "m0-events.sample.jsonl"), `${records.map((record) => JSON.stringify(record)).join("\n")}\n`);
await writeFile(path.join(outputDir, "m0-report.json"), `${JSON.stringify({
result: "pass",
clark: {
@@ -653,17 +856,34 @@ async function main(attemptFailures = []) {
response_body_success: "pass",
response_body_size_limit: "pass",
response_body_unavailable: "pass",
cached_body_semantics: "pass",
binary_body_semantics: "pass",
websocket_limits: "pass",
multi_session_isolation: "pass",
target_close_survives: "pass",
cdp_disconnect_reconnect: "pass",
sensitive_header_redaction: "pass",
sensitive_body_redaction: "pass",
},
resources,
event_count: collector.records.length,
resource_thresholds: { max_rss_kib: maxRssKib, max_process_count: maxProcessCount, conclusion: "within_threshold" },
startup: {
...startup,
attempts: startupAttempts,
renderer_failures: rendererFailures,
renderer_failure_rate: rendererFailureRate,
conclusion: rendererConclusion,
},
event_count: records.length,
max_body_bytes: maxBodyBytes,
websocket_limits: {
frame_bytes: limits.maxWebSocketFrameBytes,
events_per_second: limits.maxWebSocketEventsPerSecond,
events_per_connection: limits.maxWebSocketEventsPerConnection,
},
attempt_failures: attemptFailures,
}, null, 2)}\n`);
console.log(`M0 PASS: ${collector.records.length} sanitized events; RSS snapshot ${resources.rss_kib} KiB`);
console.log(`M0 PASS: ${records.length} sanitized events; RSS p95 ${resources.rss_kib.p95} KiB`);
} finally {
if (reconnectClient) await reconnectClient.close().catch(() => {});
if (browserClient) await browserClient.close().catch(() => {});
@@ -676,15 +896,25 @@ async function main(attemptFailures = []) {
async function runSpike() {
const failures = [];
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await main(failures);
return;
} catch (error) {
const original = error.cause ?? error;
failures.push(original.message);
if (!(error instanceof StartupNavigateFailure) || attempt === 3) throw original;
console.error(`M0 retry ${attempt}/3: ${original.message}`);
const startup = {
requested_samples: Number(process.env.M0_STARTUP_SAMPLES ?? 3),
successful_samples: 0,
max_renderer_failure_rate: Number(process.env.M0_MAX_RENDERER_FAILURE_RATE ?? 0.34),
};
assert(Number.isSafeInteger(startup.requested_samples) && startup.requested_samples > 1, "M0_STARTUP_SAMPLES must be an integer greater than one");
assert(Number.isFinite(startup.max_renderer_failure_rate) && startup.max_renderer_failure_rate >= 0 && startup.max_renderer_failure_rate <= 1, "M0_MAX_RENDERER_FAILURE_RATE must be between zero and one");
for (let sample = 1; sample <= startup.requested_samples; sample++) {
for (let attempt = 1; attempt <= 3; attempt++) {
startup.successful_samples = sample;
try {
await main(failures, startup);
break;
} catch (error) {
const original = error.cause ?? error;
failures.push({ sample, attempt, error: original.message, detached_reason: error.detachedReason ?? null });
if (!(error instanceof StartupNavigateFailure) || attempt === 3) throw original;
console.error(`M0 startup sample ${sample}/${startup.requested_samples} retry ${attempt}/3: ${original.message}`);
}
}
}
}
+68 -3
View File
@@ -2,7 +2,16 @@ import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import test from "node:test";
import { boundedBody, redactHeaders, safeUrl, shouldRetryStartup } from "../scripts/m0-spike.mjs";
import {
boundedBody,
eventCollector,
exportSession,
queryRecords,
redactHeaders,
safeUrl,
shouldRetryStartup,
summarizeProcessSamples,
} from "../scripts/m0-spike.mjs";
test("export policy redacts credentials and bounds bodies", () => {
const sensitive = randomUUID();
@@ -14,10 +23,66 @@ test("export policy redacts credentials and bounds bodies", () => {
"set-cookie": "[REDACTED]",
});
assert.equal(safeUrl("https://example.test/path?token=secret"), "https://example.test/path?<redacted>");
assert.deepEqual(boundedBody(sensitive, false, 100), { state: "fetched", storage: "omitted", reason: "default_body_policy", bytes: Buffer.byteLength(sensitive) });
assert.deepEqual(boundedBody(sensitive, false, 100), { state: "fetched", source: "network", encoding: "utf8", storage: "omitted", reason: "default_body_policy", bytes: Buffer.byteLength(sensitive) });
assert(!JSON.stringify(boundedBody(sensitive, false, 100)).includes(sensitive));
assert.deepEqual(boundedBody("hello!", false, 5), { state: "size_limit", storage: "omitted", bytes: 6 });
assert.deepEqual(boundedBody("AP8=", true, 10, "memory_cache"), { state: "fetched", source: "memory_cache", encoding: "base64", storage: "omitted", reason: "default_body_policy", bytes: 2 });
assert.deepEqual(boundedBody("hello!", false, 5), { state: "size_limit", source: "network", encoding: "utf8", storage: "omitted", reason: "size_limit", bytes: 6 });
assert(shouldRetryStartup("Render process gone."));
assert(!shouldRetryStartup("Target closed."));
assert(!shouldRetryStartup(undefined));
});
test("cache state stays unknown without a preceding request event", () => {
const collector = eventCollector("session-a", "target-a");
collector.listener("Network.responseReceived", {
requestId: "orphan-response",
response: { url: "https://example.test/favicon.ico", status: 200, protocol: "h2", mimeType: "image/x-icon", headers: {} },
});
assert.deepEqual(collector.records[0].response.cache, { state: "unknown" });
});
test("WebSocket limits aggregate drops without payloads", () => {
const collector = eventCollector("session-a", "target-a", {
maxWebSocketFrameBytes: 4,
maxWebSocketEventsPerSecond: 1,
maxWebSocketEventsPerConnection: 2,
});
const frame = (method, payloadData, timestamp, opcode = 1) => collector.listener(method, { requestId: "ws-1", timestamp, response: { opcode, payloadData } });
frame("Network.webSocketFrameSent", "okay", 1.1);
frame("Network.webSocketFrameReceived", "x", 1.2);
frame("Network.webSocketFrameReceived", "YWJjZGVm", 2.1, 2);
frame("Network.webSocketFrameSent", "ok", 2.2);
frame("Network.webSocketFrameSent", "ok", 3.1);
frame("Network.webSocketFrameSent", "ok", 4.1);
assert.equal(collector.records.filter((record) => record.kind === "websocket_frame").length, 2);
const dropped = collector.records.find((record) => record.kind === "websocket_frames_dropped");
assert.deepEqual(dropped.dropped.reasons, { rate_limit: 2, frame_size_limit: 1, event_limit: 1 });
assert.equal(dropped.dropped.count, 4);
assert(!JSON.stringify(dropped).includes("YWJjZGVm"));
});
test("query and export isolate sessions before pagination", () => {
const records = [
{ session_id: "a", target_id: "a1", value: 1 },
{ session_id: "b", target_id: "b1", value: 2 },
{ session_id: "a", target_id: "a1", value: 3 },
];
assert.deepEqual(queryRecords(records, "a", { limit: 1 }), { items: [records[0]], next_cursor: 1 });
assert.deepEqual(queryRecords(records, "a", { cursor: 1, limit: 1 }), { items: [records[2]], next_cursor: null });
assert(!exportSession(records, "a").includes('"session_id":"b"'));
});
test("resource baseline summarizes repeated samples", () => {
assert.deepEqual(summarizeProcessSamples([
{ process_count: 8, rss_kib: 700_000, cpu_percent_snapshot: 10 },
{ process_count: 9, rss_kib: 800_000, cpu_percent_snapshot: 20 },
{ process_count: 9, rss_kib: 900_000, cpu_percent_snapshot: 30 },
], 200), {
sample_count: 3,
interval_ms: 200,
process_count: { min: 8, max: 9 },
rss_kib: { min: 700_000, max: 900_000, mean: 800_000, p95: 900_000 },
cpu_percent: { min: 10, max: 30, mean: 20, p95: 30 },
});
});