Skip to content
ProtoPeek

Transport boundaries

What does every protocol adapter need to preserve?

Architecture guide
  • Local-first shell
  • Protocol-native evidence
  • Release gates
Evidence path What does every protocol adapter need to preserve?
Transport boundaries evidence path Shell to Adapter to Evidence Shell Adapter Evidence
One local path. Each boundary stays visible.

ProtoPeek is a local service workbench, not a general collaboration client. Its durable product advantage is the short path from a real target to explainable request, response, route, and transport evidence.

Product contract

  • pp host:port and protopeek host:port keep their direct gRPC-compatible CLI meaning and open gRPC under Inspect through /grpc, which redirects to /protocols/grpc. With no target the browser opens Home at /. A bare host or HTTP(S) authority opens the bounded Inspect target flow with that one target prefilled and visibly probed. gRPC and HTTP are selected explicitly under Inspect at /protocols/grpc and /protocols/http; /http remains a compatibility redirect.
  • Every session runs locally, without an account, remote sync, or external database.
  • The native listener binds to loopback. A container may use -allow-non-loopback-bind only when its outer host port is published on loopback; that mode retains loopback request Host and Origin checks. -unsafe-allow-remote is a separate, unauthenticated remote mode that requires an external TLS, authentication, and rate-limit boundary.
  • The Downloader host-settings mutation is a stricter direct-loopback exception: POST /api/transfers/config accepts no forwarded-header or Host-header proxy assertion, even when -unsafe-allow-remote is enabled. The server requires CSRF, rejects standard forwarding headers, and validates the direct transport peer from a well-formed RemoteAddr before reading the bounded JSON body; it then requires a stopped engine, the cooperative engine lock, and an expected config revision. Reverse-proxy use for this endpoint is unsupported; forwarded headers never grant trust.
  • Each transport keeps its native concepts visible. The UI must not flatten gRPC trailers, Cap'n Proto capabilities, or HTTP status and headers into a misleading common response object.
  • Reflection, temporary browser-folder snapshots, host proto paths, and host protoset paths remain first-class gRPC schema paths. Their authority boundaries stay visibly separate.
  • Automatic discovery is loopback-only. Explicit private or link-local destinations require a per-scan opt-in. An explicit public IP or hostname is one user-entered target, never permission for port-range expansion. Hostnames are resolved once, all returned addresses are classified, and probes dial a validated numeric address to avoid a policy-to-dial DNS change. An explicit host without a port has only the visible 50051 plaintext and 443 verified-TLS candidates. CIDR expansion and ambient network crawling are outside this boundary.
  • Scan evidence is independently labeled as verified gRPC, safe HTTP, or open TCP. HTTP evidence uses only HEAD /, verified TLS where applicable, fixed per-protocol and whole-request deadlines, no body, no authentication, and no redirect following. Closing or cancelling the dialog cancels the request context. The handler admits two scan requests at once and rejects excess work before reading its body. One request accepts at most 20 inputs expanding to 24 candidates and has a four-second wall deadline. Each retained service name is at most 1 KiB, with 64 names and 32 KiB aggregate; reflection receive and response-header evidence are each capped at 64 KiB. Diagnostic details and the primary error are capped at 2 KiB each, while HTTP protocol, status, and Server evidence are capped at 64, 512, and 256 bytes. Truncation is explicit in the result rather than silently presented as complete evidence.
  • Next-hop evidence is one read-only kernel-selected route per resolved address from the ProtoPeek process. It sends no hop probes (hostnames may still resolve through DNS), does not trace hops, does not poll or mutate routes, and requires no elevation. Its UI permanently states that VPNs, proxies, policy routing, ECMP, and later changes can alter a real connection path.
  • Network Path is a separate active operation. Linux uses a built-in unprivileged UDP error-queue backend after explicit active-probe and public-target consent. It resolves once, pins one numeric destination, and preserves DNS, kernel route, per-TTL samples, silent hops, and multiple responders under a 96-probe and 30-second maximum. Every RTT is source-to-responder, statistics are per responder, and a destination median uses only replies from the exact pinned address—not the last responding router or per-link latency. Returned total duration is admitted only through the selected wall plus a 2-second resolver/return allowance. Saved responders are observed; silent-hop and unconfirmed-destination placeholders plus logical trace edges are inferred. Bounded safe interface zones preserve scoped IPv6 identities. Darwin and Windows report active probing as unsupported rather than installing or shelling out to another tool.
  • Local network discovery is another explicit boundary. A capability read sends no probes; the active request accepts only one authorized RFC 1918 IPv4 /24-or-smaller CIDR and one exact selected-TCP profile. Capabilities expose at most 32 deduplicated interface suggestions and omit any configured prefix not wholly contained by one RFC 1918 block. Profile applicationProbePorts are Quick 80, 443, 50051, 8080; gRPC 443, 6565, 7000, 7443, 9090, 50051; Web/API 80, 443, 3000, 4000, 5000, 8000, 8080, 8443; and Expanded 80, 443, 3000, 8000, 8080, 8443, 9090, 50051. Only those ports may receive bounded gRPC reflection and non-following HTTP HEAD /; every other selected port is TCP-connect-only, including Expanded's 22, 53, 445, 631, 1883, 3306, 3389, 5432, 6379, 9100. It previews and caps the work at 18 ports, 4,572 attempts, 32 workers, 15 seconds, and one scan. Its 64 KiB aggregate verbose-detail budget retains every open-port record. attemptsCompleted counts probe calls that returned, including cancellation returns, while probeDurationMs is full probe duration rather than network latency. Missing hosts are not called offline, and role hints never become OS, hardware, VLAN, ownership, or physical-link claims.
  • Nmap is not required to import an existing XML file. To create new XML, users obtain and run Nmap separately. ProtoPeek only imports bounded nmap -oX XML, retains no command arguments/scripts/OS/trace data, persists no inventory, and treats service names as hints until the existing bounded scanner verifies a literal-IP endpoint.
  • The running handler admits at most eight ordinary gRPC invokes across direct and workspace sessions, four HTTP relays, two native route requests, and two active path traces. Local-network discovery has a separate one-operation slot. Each class has one shared budget, not a per-browser or per-workspace pool. Method and CSRF checks happen first; saturation returns a non-cacheable 429 Too Many Requests with nosniff before request-body reads or network work. Deferred release covers success, validation/error, cancellation, and panic unwinding; deleting a workspace closes its connection so admitted workspace invokes finish and release. These admission budgets do not weaken the smaller fan-out, deadline, body, message, redirect, address, or evidence limits within an admitted request.
  • One ordinary invoke retains at most 512 response messages and 8 MiB of serialized message JSON. The callback for message 513 stops before serialization; a message that would make retained JSON exceed 8 MiB is observed but not retained. Equality at either boundary may still receive natural trailers and status. Crossing a boundary cancels the underlying RPC and returns headers, retained messages, request counts, and available timing as localLimit evidence with no fabricated server gRPC status. A missing or greater-than-60-second request deadline is replaced by a 60-second local wall; a positive deadline up to 60 seconds keeps its requested semantics. Timer/status races are attributed by callback order: an already-observed server status wins, while a locally generated deadline callback does not become server evidence.
  • New transports must not add a heavy runtime or materially grow the browser bundle without a measured reason. The first HTTP adapter uses the Go standard library.

Architecture

The desktop shell owns six-destination navigation, session/context chrome, appearance, and bounded action entry. A transport workbench owns its target selection, request/response workspace, local history, and session lifecycle. Its adapter owns discovery, schema loading, request validation, invocation, cancellation, and transport events.

pp CLI / local web server
        |
console session manager
        |
        +-- gRPC adapter       reflection | browser snapshot | host .proto | protoset
        +-- HTTP adapter       explicit HTTP(S) URL | bounded stdlib client
        +-- route evidence     kernel-selected next hop | no probe packets
        +-- Network Path       Linux native UDP error queue | consented bounded samples
        +-- local network      authorized private /24-or-smaller | selected TCP profiles
        +-- topology notebook  logical evidence | immutable snapshots | bounded local storage
        +-- Nmap import        offline XML hints -> bounded verification
        +-- Cap'n Proto adapter exploring: schema file | capability bootstrap
        |
ordered transport events -> protocol-specific inspector

Bundled Nmap execution is not planned for the core binary; public or broader range expansion and live capture remain gated. Offline Nmap XML import does not bundle or invoke Nmap. Read-only next-hop evidence remains distinct from active Network Path, and logical topology remains distinct from physical network discovery.

The shared boundary should stay deliberately small:

  1. Target: transport kind plus adapter-owned connection configuration.
  2. Operation: a selectable RPC or HTTP operation with an adapter-owned schema.
  3. Invocation: deadline, metadata, and one or more request messages.
  4. TransportEvent: ordered events such as connected, headers, request message, response message,

    trailers, status, capability resolution, or HTTP body chunk.

Adapters return their native detail alongside these events. The shell can render timing and message counts consistently, while a protocol inspector renders the actual semantics.

Delivery slices

1. Finish the gRPC local-console contract

Keep the current slice as the reference adapter: bounded loopback discovery, deterministic sessions, reflection headers, browser-folder/host-proto/protoset compatibility, request/response split view, explicit cancellation, callback-observed response timing, and visible headers, trailers, deadlines, streaming mode, and status. Headers, first message, final status, and invoke return are handler lifecycle boundaries, not packet-arrival, server-processing, or TTFB measurements; unary callbacks may cluster after transport completion.

Health is a separate, explicit gRPC diagnostic rather than a background liveness monitor. Check accepts a 0.1–30 second deadline (5 seconds by default). Watch accepts a 1–600 second duration (60 seconds by default), performs no retry or polling, and shares four stream slots across direct and workspace sessions. The Watch duration is owned by the ProtoPeek relay and is not propagated as a downstream gRPC deadline. Only that owned cancellation becomes duration-limit evidence; a server-owned DeadlineExceeded or Canceled remains an RPC error. The server accepts at most a 64 KiB JSON envelope, 1,024 UTF-8 bytes for a service name, and 64 request-body metadata entries / 32 KiB aggregate. Configured CLI metadata and explicitly preserved relay headers still follow the existing Invoke precedence. It emits at most 512 status observations; each flushed NDJSON line is at most 64 KiB and response headers plus trailers share a 32 KiB retained evidence budget. The browser validates event order and attribution, retains the latest 200 transitions with a dropped count, and treats missing terminal evidence as truncation.

Blank service means overall server health. Unknown service semantics are not normalized away: Check has no fabricated serving enum and ends NOT_FOUND; Watch reports SERVICE_UNKNOWN and stays open. UNIMPLEMENTED ends the one Watch epoch without retry. Live request metadata follows Invoke precedence and binary decoding but is never returned, persisted, or exported. Every timestamp is a ProtoPeek handler/relay observation against one selected connection, not packet arrival, server emission, dependency health, load-balancer coverage, or fleet health.

2. Keep the HTTP slice bounded

The live HTTP surface accepts method, URL, headers, body, timeout, and redirect choice. It accepts only http and https, keeps TLS verification on, leaves redirects off by default, supports cancellation, and bounds envelopes, bodies, headers, redirects, and deadlines. Its inspector owns status, HTTP protocol, response headers, text/base64 body, byte and truncation evidence, redirect hops, peer/TLS context, and phase timings.

Automatic HTTP history keeps only method, sanitized URL, status, total time, and values from a small header allowlist (Accept*, cache/conditional controls, Content-Type, range/preference, trace context, and request/correlation IDs). Every other header value is replaced with [redacted]; URL user info is removed and credential-like query names are redacted. It never retains the request body. Replaying an entry resets params, auth, body mode/body, timeout, redirect choice, validation state, and previous response before restoring those persisted fields, so a stale editor value cannot silently join the replayed request.

Copy as cURL is manual export only: no draft is copied or persisted until the user activates the action, and cURL import remains Next. Send and Copy first pass through the same method, URL, user-info, header, active-body, and timeout preparation boundary. The command then uses POSIX-safe quoting for those explicit prepared fields. Existing credential classifiers blank sensitive URL values and omit auth and credential-like headers rather than placing a [redacted] sentinel into the executable command. Redirect-enabled drafts are refused because one portable cURL command cannot reproduce the relay's ten-hop cap, method/header rewriting, cross-origin stripping, and HTTPS-downgrade refusal.

The exported command runs in the user's shell and cURL process, not through ProtoPeek's HTTP relay. Its network namespace and localhost, DNS, proxies, trust roots, implicit headers, and cURL version may differ, so export is a reviewed handoff rather than a transport replay claim. At most 64 effective headers are inspected and the UTF-8 command may be at most 512 KiB; invalid/non-HTTP(S) URLs and over-limit drafts fail before clipboard access. Request bodies are deliberate user-authored content, are copied verbatim, and must be reviewed before sharing or execution.

3. Bound workspace transfer and replay

The default workspace export is the explicit protopeek-workspace version 1 JSON format. It includes saved requests, environments, assertions, and target profiles, but excludes automatic RPC history. Metadata is sanitized again at export time. Saved request bodies are intentionally included and must be reviewed before a file is shared.

Import checks the file's reported size before reading any text and rejects files over 4 MiB. Both version 1 and the previous unversioned export shape pass through the same validator: at most 100 assertions, 100 saved requests, 50 environments, 50 history entries, 50 targets, 64 metadata entries per record, and 32 entries in each target path list. Request JSON is capped at 512 KiB; metadata values at 64 KiB; notes at 16 KiB; and target paths at 4,096 characters. Wrong container types, unsupported versions/enums, duplicate IDs, non-finite/out-of-range numbers, and oversized strings are refused without replacing the console with a boot error.

Normal workspace writes pass through the same bounded schema before persistence. Hitting a target, environment, or saved-request limit refuses the new record; it does not discard an older record. On startup, one malformed, duplicate, or over-limit stored record does not erase the entire section: valid bounded records remain usable in the live session while the exact readable source string stays untouched in its original key. A persistent recovery banner can download the raw, deliberately non-importable source or explicitly replace it with the recovered records. Imports and normal exports that touch unresolved recovery are paused. Browser read failures are called out separately because their original bytes cannot be captured.

Imported target IDs are detached from any live session and no imported target connects automatically. Host proto/protoset/import-root/CA/client-certificate/client-key values name files on the machine running ProtoPeek—not paths on the browser machine. An explicit later connection grants the ProtoPeek process local file-read authority for those paths, and the import result warns about that boundary. Browser-folder profiles are different: storage and workspace export contain only the pathless profile configuration. A browser must choose the folder again before connection; handles, bytes, root names, browser paths, and server filesystem paths never persist. A successful target-profile replacement invalidates pending connection work and returns any active profile session to the launcher before an imported profile can be used.

Browser-folder connect is one CSRF-protected multipart transaction on the existing workspace endpoint. The client mirrors limits for fast feedback, but the server independently enforces the 20 MiB envelope, 512-file, 4 MiB per-file, 16 MiB aggregate, and portable-relative-path bounds. Only the exact manifest may satisfy imports; absolute, backslash, traversal, duplicate, case-colliding, reserved, and non-.proto paths fail before compilation. Google well-known protos are the only non-manifest fallback. The server clears bounded in-memory upload buffers before the target is dialed or the session is published and writes no schema staging files. Snapshot bytes go only to the current ProtoPeek process or container and never to the gRPC target.

The remaining workspace schema sources have a separate host-process boundary. One JSON connection may name at most 128 proto entry files, 64 import roots, or 32 protosets. Every configured path is valid UTF-8 without NUL, is at most 4,096 bytes, and contributes to a 32 KiB aggregate path budget. Explicit proto entry and protoset files must be regular files; before target dial or parsing, their reported sizes are capped at 4 MiB each and 16 MiB aggregate. Import roots deliberately are not walked or pre-read: selecting one grants the ProtoPeek process authority to resolve only imports referenced by the chosen entry schemas. Those resolved imports meet the retained descriptor limits before publication even though their directory contents are not part of the explicit-file preflight.

Each workspace manager admits two concurrent non-upload JSON schema connections; the existing two browser-folder upload/parse slots remain separate. Reflection lists services once, resolves their descriptor graphs incrementally, checks cancellation between requests, and stops at the first resource limit instead of calling an unbounded all-files helper. Every source—reflection, host proto, host protoset, or browser snapshot—must fit 512 retained services, 10,000 methods, 1,024 descriptor files, 10,000 messages, 50,000 fields, 4,096 enums, 50,000 enum values, 32 levels of message nesting, 8 MiB of serialized descriptors, and a 16 MiB generated catalog before a session becomes visible. The structural walk is iterative and runs before recursive catalog summaries/proto text are materialized. Manager shutdown cancels tracked connections; request cancellation is checked before dial, between reflection fetches, after non-cancellable parser calls, and again before publication. Errors name the source, boundary, and corrective action but do not include schema contents, reflection metadata values, or credentials. Direct CLI -proto/-protoset operation remains outside this browser workspace-manager admission boundary.

New saved requests and automatic gRPC history carry target-profile ID plus target address. Replay requires the method to exist and the stored scope to match. Legacy records without scope may be applied only to a method available on the current target, then bind to that target on first replay. Persisted [redacted] metadata values restore as blank inputs with a re-entry warning. Invocation filters out blank sensitive values and the sentinel again at the send boundary; Unary Repeat applies the same filtering. Repeat JSON exports contain the method, target, run ID/start timestamp, frozen configuration, counts, per-attempt offsets/timings, classifications, and error/status text—never the request body or metadata. Review target/internal addresses and service/relay text before sharing. handlerInvokeMs includes JSON/protobuf conversion and callbacks but excludes the browser/HTTP relay; consoleRoundTripMs includes that relay and response parsing.

4. Keep passive, active, discovered, and imported evidence distinct

Use OS-native route APIs for one selected next hop and bounded streaming XML for offline Nmap evidence. Preserve source/interface/gateway and table/probed confidence, but require a fresh ProtoPeek scan before an imported hint can open a workbench. See the network evidence boundary guide for exact limits and platform behavior.

Linux active paths use only the native unprivileged UDP backend after explicit consent. Private discovery accepts only a reviewed RFC 1918 IPv4 /24-or-smaller selected-TCP plan. Saved network workspaces preserve provenance and immutable snapshots. Appending evidence preserves saved manual labels, tags, notes, pinned positions, group assignments, manual groups, services, and relationships; dirty edits are guarded until saved or deliberately discarded, and snapshot-to-current-map restore requires two actions. Stale cross-tab IndexedDB writes/deletes overwrite nothing; failed persistent deletes remain visibly retained, and restore uses a 20-record cursor bound. The interactive map is bounded at 160 nodes, 640 relationships, and 64 groups, with a complete 100-record paged inventory above any threshold. Its canvas represents logical evidence, not physical cabling or scan-derived VLAN membership. Canonical JSON is the lossless format; lossy GraphML accepts only one flat directed graph and rejects undirected/mixed, nested, hyperedge, port, duplicate, and XML-invalid-control structures. See the network workbench guide for the user workflow and exchange losses.

5. Cap'n Proto experiment (exploring)

Build one local, schema-file-driven unary/capability call path behind an explicit experimental flag. Show capability resolution and message segments in its own inspector. Measure binary and bundle cost before deciding whether it belongs in the main binary or an optional companion.

6. Cross-platform paths and wider discovery (future)

Darwin and Windows active paths require verified unprivileged native backends; no package install, shell parser, or elevation fallback substitutes for them. Discovery broader than one authorized RFC 1918 IPv4 /24, public-range expansion, IPv6 range expansion, and ambient crawling remain out of scope. Optional Nmap XML-to-topology mapping may be considered with explicit import provenance, but it must not execute Nmap.

Release gates for any adapter

  • Default gRPC startup time and bundle size do not materially regress.
  • The adapter works without a database or network service other than the target being inspected.
  • Cancellation and session teardown release connections and subprocesses.
  • Authorization, cookies, proxy authorization, binary metadata, and API-key/token-like metadata are redacted from automatic history and default exports.
  • The inspector names the real protocol concepts instead of using generic labels.