Kuebiko is a passive, extensible network capture tool for browsers that expose the Chrome DevTools Protocol (CDP). It can launch a dedicated browser profile or attach to an existing local CDP endpoint, then saves request/response bodies plus metadata while you browse manually. Trusted local plugins can react to completed captures without changing browser traffic.
This tool is intentionally narrow. It does not use mitmproxy, SSLKEYLOGFILE,
packet capture, request interception, browser automation, login automation,
analytics, parsers, dashboards, or HAR viewers.
Use this when you want raw local capture files from normal manual browsing in a throwaway browser profile:
- response bodies from CDP
Network.getResponseBody - request payloads that the browser exposes through CDP
- request/response metadata in append-only NDJSON
- Chromium NetLog in the same run directory when using browser launch mode
The logger is written in TypeScript for Bun and runs on Windows, Linux, and
macOS. Run the logger on the same OS as the browser whenever possible so it can
connect to http://127.0.0.1:9222 without cross-VM networking assumptions.
The logger observes the browser locally. The website does not receive a header, cookie, JavaScript variable, or protocol message saying that CDP logging is enabled.
Launch mode and the logger are deliberately passive:
- CDP is bound to
127.0.0.1. - The logger uses the CDP
Networkdomain to observe completed browser network activity and fetch stored bodies. - NetLog writes a local debugging file from the Chromium network stack.
- There is no
Fetch.enable, request pausing, request rewriting, or response rewriting. - There is no
Runtime.evaluate, script injection, extension injection, or Debugger-domain attachment. - Launch mode does not use
--headless,--enable-automation, or--remote-debugging-port=0. - Launch mode does not use
--disable-quic; browser network behavior is kept close to normal. - After enabling Network on an attached popup, iframe, or worker target, the
logger sends
Runtime.runIfWaitingForDebuggerfor that target session; it does not otherwise use the Runtime domain.
That means a destination site should see ordinary browser requests from the dedicated profile, not an explicit "logger enabled" signal.
This is not a stealth or evasion guarantee. A site may still notice ordinary environment differences, such as a fresh profile, missing old cookies, different cache state, different permissions, no usual extensions, or local timing changes from heavy logging. Some sites also use broad anti-debugging or automation heuristics. This project avoids the obvious automation and interception signals; it does not promise undetectability.
Install the latest release with mise:
mise use -g github:risu729/kuebikoLaunch mode is the recommended path. It starts a browser with the required CDP and NetLog flags, uses a dedicated profile, and keeps capture files in one run directory.
Run the logger and let it launch your browser:
kuebiko \
--launch-browser \
--browser-command google-chromeUse another browser command or executable path when needed:
kuebiko.exe `
--launch-browser `
--browser-command chrome.exekuebiko --launch-browser \
--browser-path "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"The browser opens with a dedicated profile, CDP bound to 127.0.0.1:9222, and
NetLog writing to the same capture directory. Log in manually inside that
profile and browse normally.
Launch mode uses a dedicated profile under the platform default base directory:
- Windows:
%LOCALAPPDATA%\Kuebiko\browser-profile - macOS:
~/Library/Application Support/Kuebiko/browser-profile - Linux:
${XDG_STATE_HOME:-~/.local/state}/Kuebiko/browser-profile
The tool does not attach to your default browser profile and does not depend on it. Treat this profile as a separate browser identity. If a website needs login, log in manually inside this browser window.
Each run creates a timestamped directory under the platform capture root:
Kuebiko/captures/2026-07-06T12-34-56
The run directory contains:
run.json
metadata.ndjson
errors.ndjson
websocket.ndjson
bodies/
requests/
plugins/
netlog.json
bodies/ contains saved response bodies. requests/ contains request payloads
that the browser exposes through CDP. Filenames are generated from timestamp,
SHA-256, counter, and MIME-derived extension; URLs are not placed into
filenames.
plugins/ is created when configured plugins write per-plugin output. The core
logger still only writes raw capture files; plugins are trusted local extension
code you opt into with --config.
metadata.ndjson contains one JSON object per completed response that passed
the filters. When available, the same metadata line links to both a saved
request payload and a saved response body.
errors.ndjson contains per-request capture failures. Individual CDP failures
do not stop the logger.
netlog.json is Chromium NetLog for network-stack debugging.
For completed responses, metadata includes request and response fields such as:
- URL, method, request ID, loader ID, target/session identifiers
- request headers and response headers
- status, status text, MIME type, protocol, remote IP/port
- cache/service-worker/prefetch flags where the browser provides them
- encoded data length
- response body path, byte length, SHA-256, and CDP
base64Encoded - request body path, byte length, SHA-256, and source when available
- any capture error for body retrieval
Response bodies are saved exactly from CDP's body result:
base64Encoded: trueis decoded and written as bytes.base64Encoded: falseis written as UTF-8 bytes.
Request payloads are written as UTF-8 bytes from CDP strings. The logger first
uses inline request.postData from Network.requestWillBeSent when present. If
the browser only reports hasPostData, the logger tries
Network.getRequestPostData. This is suitable for JSON, forms, GraphQL, and
other text request bodies. It is not raw upload byte capture, and arbitrary
non-UTF-8 uploads may not round-trip exactly.
CDP bodies are not raw wire bytes. For exact network-stack debugging, use the
companion netlog.json.
The plugin system is a core part of Kuebiko. It lets trusted local TypeScript or JavaScript modules react to completed captures in real time without duplicating the CDP logger. The logger saves request/response files and metadata first. Plugins then receive small immutable events containing metadata and relative file paths.
Plugins run in the logger process. They are not sandboxed third-party code. A
bad plugin cannot mutate requests through the logger API, but it can still use
normal local runtime APIs, CPU, and memory. Plugin failures and queue overflows
are written to errors.ndjson; capture continues.
Create a config file:
import { defineConfig } from "kuebiko";
export default defineConfig({
plugins: [
{
module: "./plugins/json-api-mirror.ts",
enabled: true,
timeoutMs: 5000,
queueSize: 1000,
},
],
});defineConfig provides TypeScript context and validates plugin config entries
when the config module is evaluated.
Plugin module paths are resolved relative to the config file.
Example plugin:
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import type { LoggerPlugin } from "kuebiko";
export default {
id: "json-api-mirror",
name: "JSON API Mirror",
version: "0.1.0",
events: ["response.completed"],
async setup(ctx) {
await mkdir(ctx.pluginDirectory, { recursive: true });
},
async onEvent(event, ctx) {
if (event.event !== "response.completed") return;
if (!event.response.bodyFile) return;
if (!event.response.mimeType?.includes("json")) return;
const source = ctx.resolveRunPath(event.response.bodyFile);
const requestIdPattern = /[^A-Za-z0-9._-]/gu;
const safeRequestId = event.request.requestId.replace(requestIdPattern, "_");
const output = ctx.resolvePluginPath(`${safeRequestId}.json`);
await mkdir(dirname(output), { recursive: true });
await Bun.write(output, Bun.file(source));
},
} satisfies LoggerPlugin;Run with plugins:
kuebiko --config C:\path\logger.config.ts --out <capture-dir>Disable configured plugins for a run:
kuebiko --config C:\path\logger.config.ts `
--no-plugins --out <capture-dir>Supported plugin events are:
run.startedrun.stoppingrun.stoppedresponse.completedwebsocket.frame.receivedcapture.error
Hook events do not contain inline request or response bodies. Read saved files
with ctx.resolveRunPath(event.response.bodyFile) or the request-body path when
present.
After browsing, check the latest run directory:
state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
base="${KUEBIKO_BASE_DIR:-$state_home/Kuebiko}"
capture="$(find "$base/captures" -mindepth 1 -maxdepth 1 -type d |
sort |
tail -1)"
printf '%s\n' "$capture"
find "$capture/bodies" -type f | wc -l
find "$capture/requests" -type f | wc -l
wc -c "$capture/metadata.ndjson" "$capture/netlog.json"On Windows PowerShell:
$capture = Get-ChildItem "$env:LOCALAPPDATA\Kuebiko\captures" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
$capture.FullName
(Get-ChildItem "$($capture.FullName)\bodies" -File -Recurse).Count
(Get-ChildItem "$($capture.FullName)\requests" -File -Recurse).Count
(Get-Item "$($capture.FullName)\metadata.ndjson").Length
(Get-Item "$($capture.FullName)\netlog.json").LengthYou should see metadata.ndjson grow while the logger is running. Normal CDP
misses are recorded in errors.ndjson.
Use launch mode first. It is easier to get right because the logger owns the browser process and supplies the CDP, dedicated-profile, and NetLog flags.
Use attach mode only when you specifically need to connect to a browser you
started yourself, such as an existing profile. That is harder in practice: the
browser must already have been started with --remote-debugging-port, and
Chromium-family browsers often reuse an existing profile process instead of
applying new flags. You may need to fully close that profile first or use a
separate profile directory.
Launch mode starts the browser, owns the dedicated profile for that run, enables CDP, writes NetLog by default, starts capture, and closes the browser when the logger exits.
Use a browser command from PATH:
kuebiko --launch-browser --browser-command google-chromeOr use an explicit browser executable:
kuebiko --launch-browser \
--browser-path "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"kuebiko --launch-browser `
--browser-path "C:\Program Files\Google\Chrome\Application\chrome.exe"Use an explicit capture directory:
kuebiko --launch-browser --browser-command google-chrome \
--out "$HOME/captures/manual-run"Disable NetLog for a run:
kuebiko --launch-browser --browser-command google-chrome --no-netlogThe logger intentionally does not auto-discover browsers. Pass either
--browser-command or --browser-path. Launch mode uses these browser flags by
default:
--user-data-dir=<profile-dir>
--remote-debugging-address=127.0.0.1
--remote-debugging-port=<port>
--log-net-log=<capture-dir>\netlog.json
--net-log-capture-mode=Everything
The NetLog flags are omitted when --no-netlog is set.
Pass repeated --browser-arg=<arg> values only when your local browser
environment requires them. For example, CI Chrome sometimes requires
--browser-arg=--no-sandbox. These extra args are explicit and are not added by
default.
Attach mode is for externally launched browsers. Start the browser yourself with CDP enabled, then run:
kuebiko --cdp http://127.0.0.1:9222 --out <capture-dir>Attach mode does not launch a browser or write NetLog by itself. It only
connects to the CDP endpoint you provide. If you also need NetLog in attach
mode, the browser must have been started with --log-net-log=<path> before the
logger connects.
Chrome and other Chromium-family browsers may show this banner after startup:
You are using an unsupported command-line flag: --log-net-log=<path>. Stability
and security will suffer.
This is expected when NetLog is enabled from the command line. --log-net-log
is the Chromium-documented startup flag for writing a NetLog file, but browser
security warning UI can still flag diagnostic command-line switches as
potentially dangerous.
The warning does not mean that NetLog failed or that the browser ignored the
flag. Verify capture by checking that netlog.json exists and grows in the run
folder. The warning is meaningful: NetLog captures sensitive network metadata,
and --net-log-capture-mode=Everything can include more private debugging
detail than the default browser behavior.
Chromium-family browsers may otherwise show this banner when a page opens a popup or another window while CDP is attached:
Debugger paused in another tab, click to switch to that tab.
The logger does not enable the CDP Debugger or Fetch domains and does not
intentionally pause scripts. The browser can still create an auto-attached
popup, iframe, or worker target in a debugger-waiting state. For each attached
inspectable target, the logger enables Network first, then calls
Runtime.runIfWaitingForDebugger for that target session.
This Runtime call only tells a target to continue if the browser has it waiting for a debugger. It is not request interception, script injection, browser automation, or general Runtime evaluation.
kuebiko [options]
Options:
--cdp <url> CDP endpoint (default: http://127.0.0.1:9222)
--out <capture-dir> Capture directory
--verbose Print verbose status logs
--include <regex> Only persist matching response URLs
--exclude <regex> Do not persist matching response URLs
--max-body-bytes <num> Skip body retrieval above encoded byte length
--config <path> TS/JS logger config with plugin modules
--no-plugins Disable plugin loading from --config
--launch-browser Launch and own a local CDP browser process
--browser-command <cmd> Browser command for --launch-browser
--browser-path <path> Browser executable path for --launch-browser
--browser-profile <dir> Browser profile directory for --launch-browser
--browser-arg <arg> Extra browser arg for --launch-browser
--cdp-port <port> Local CDP port for --launch-browser
--no-netlog Disable netlog.json in --launch-browser mode
--help, -h Show help
--version, -v Show version
Successful pushes to main are released automatically from conventional commit
messages after CI passes. GitHub Releases contain archives for Windows x64,
Linux x64, and macOS arm64.
If --out is omitted, the logger creates a new timestamped capture folder under
the platform default capture root:
- Windows:
%LOCALAPPDATA%\Kuebiko\captures - macOS:
~/Library/Application Support/Kuebiko/captures - Linux:
${XDG_STATE_HOME:-~/.local/state}/Kuebiko/captures
Set KUEBIKO_BASE_DIR to override the base directory on any platform.
- Windows:
%LOCALAPPDATA%\Kuebiko - macOS:
~/Library/Application Support/Kuebiko - Linux:
${XDG_STATE_HOME:-~/.local/state}/Kuebiko
Each base directory contains browser-profile, captures, and plugin output
created by configured plugins. Nothing is intentionally written under %TEMP%,
/tmp, or WSL /tmp during normal capture.
mise install
mise run test
E2E_BROWSER_PATH=/path/to/chrome-or-chromium mise run test-e2e
mise run check --lint
mise run compilemise run compile builds the configured Bun executables into dist/. Use
mise run compile --target <target> to build only one target.
- CDP may fail to return bodies for downloads, streaming responses, very large responses, redirects, cached responses, service-worker cases, or after navigation races.
- CDP may not expose every request payload.
Network.getRequestPostDatacan fail after navigation races and does not include uploaded files for multipart form data. --max-body-bytescompares against CDPencodedDataLength; it is a skip guard, not a perfect final decoded-size predictor.- WebSocket messages are not normal HTTP response bodies. This tool writes
server-to-browser WebSocket frames to
websocket.ndjson; it does not write client-to-server frames. - This tool does not parse, analyze, classify, or display responses.
- Plugins are trusted local code running in the logger process. They are useful for local real-time consumers, but they are not sandboxed.
- Logs can contain sensitive data, including private API requests, private API responses, submitted form content, and cookies-adjacent content. Treat every capture directory as secret.
- Store capture directories somewhere private, avoid syncing them to cloud drives by default, delete runs you no longer need, and share only minimized redacted samples.