- Common Lisp 72.9%
- Scheme 25.7%
- Shell 0.6%
- C 0.5%
- Makefile 0.3%
| .builds | ||
| benchmarks | ||
| bin | ||
| data | ||
| native | ||
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| ARCHITECTURE.md | ||
| BENCHMARKS.md | ||
| jpkg.sexp | ||
| Makefile | ||
| README.md | ||
jerboa-search
jerboa-search is a Jerboa-native crawler and full-text search engine. It is
an equivalent product to the useful single-node core of YaCy, not a protocol or
data-format port.
It composes existing local projects:
jsqlitestores authoritative document metadata, crawl state, links, content references, and the index generation.jerboa-websearchprovides SSRF-safe HTTPS fetching and sandboxed HTML5 parsing.jerboa-sinatraserves the search UI and JSON crawl/search APIs.- Jerboa supplies native threads, HTTP, JSON, WASM isolation, and build tooling.
The new search-specific pieces are a BM25 inverted index and a content-addressed body store. The body store deduplicates immutable page text outside SQLite. Generation-bound, checksummed immutable index segments make checkpoints proportional to changed documents and make ordinary restarts fast. A missing, stale, discontinuous, or corrupt segment chain is discarded and rebuilt from authoritative metadata and content. There is no YaCy/Solr compatibility layer.
The mutable core maintains document count through its document table and total document length as an incremental invariant. BM25 average length and hybrid overlay statistics are therefore constant-time metadata reads rather than an unconditional document-table traversal on every query. Single-term core queries stream final BM25 scores directly into the bounded deterministic top-k heap; only multi-term queries need a score accumulator.
The qix version-4 sidecar stores posting document references as validated ordinals into its fixed-width mapped document table. IDs and lengths are read directly from that table, so the immutable base needs no corpus-sized Scheme metadata hash; hot WAND cursors cache only their current posting. Its length-bound hierarchical SHA-256 checksum validates fixed 1 MiB chunks, so opening a large mapping never copies the complete payload into the Scheme heap. The build produces a small dependency-free native QIX shim that validates fixed-width records, posting invariants, and block summaries in one bounded pass. Jerboa's existing native digest hashes each chunk, while Jerboa retains mapping ownership, layout checks, error handling, and the final length-prefixed manifest hash. The checksum and QIX v4 bytes are unchanged and regression-checked against the pure Jerboa hash. Publication uses the same bounded chunks as its sequential output buffer and seeks back only to install the final checksum before atomic rename; it does not materialize a second sidecar-sized bytevector. A mutex-scoped sorted core view streams documents and one term's postings at a time. Dense postings reuse the already sorted document table; sparse postings sort only their own IDs and use forward galloping lookup. Fixed-width posting fields are stored directly into the checksum chunk with Jerboa's endian-aware bytevector operations, avoiding millions of tiny intermediate copies without changing QIX v4 bytes.
All query engines use a bounded deterministic top-k heap rather than sorting
every match. Ranking selection scales with log(limit) and retains only the
requested candidates. Single-term core, mapped, and hybrid queries stream scores
directly into that heap. Mapped and hybrid traversal additionally uses validated
128-posting block maxima to skip blocks whose BM25 upper bound cannot enter
top-k. Multi-term mapped queries use
exact weak-AND (WAND) cursors with binary advance-to seeks and shared hybrid
statistics, so they score only pivot candidates whose safe term-bound sum can
beat top-k. Participating cursors then sum validated block-local bounds before
exact BM25 scoring; when that sum cannot win, binary seeks advance every cursor
past the complete half-open range covered by those bounds. Block-max-aware
pivot scheduling across virtual block boundaries remains a possible research
refinement; it requires separate evidence because substituting a current-block
bound directly into ordinary WAND pivot selection is not safe.
Immutable term-wide maxima are reconstructed once from block summaries and
retained in a mutex-protected 4,096-slot direct-mapped cache. Cache entries hold
only raw QIX statistics, so hybrid document counts and frequencies remain live;
collisions merely trigger another safe scan and memory stays corpus-independent.
Mapped queries share a per-index Jerboa read lock and do not hold the SQLite store mutex while scoring postings. Writers and atomic index publication use the exclusive side, while result metadata hydration happens after the read lease is released. Retained result rows are fetched with prepared primary-key point lookups and replayed in rank order.
Closing a search store automatically unloads its managed index and mmap.
Explicit unload-search-index! remains useful for restart/fallback tests but is
not required for ordinary ownership cleanup.
Crawler ingestion commits each page, generation, outgoing links, and newly
discovered frontier URLs atomically before updating the disposable text index.
Controlled bulk import paths can use index-documents! to commit a document
batch with one generation advance, while ordinary crawler writes retain
per-page durability.
Run
Building requires a C compiler for the hardened QIX validation shim.
No external crypto library is required.
Every Scheme test converts a false run-tests! result into process status 1,
so assertion failures stop the aggregate target instead of merely printing a
failure summary. Migration fixtures also remove a same-name interrupted-run
database before setup and pass cleanup procedures—not invoked return values—to
with-resource.
make test
make integration
N=1000 make bench
N=100000 ABSENT_ROUNDS=1000 COMMON_ROUNDS=10 make core-query-bench
ROUNDS=100000 REPEATED=8 make query-terms-bench
LINKS=600 ROUNDS=100 make link-replacement-bench
URLS=200 ROUNDS=20 make frontier-enqueue-bench
N=1000 make restart-bench
N=1000 make segment-bench
N=1000 make mmap-bench
make mmap-capacity-bench CAPACITY_N=1000000 CAPACITY_ROUNDS=1000
N=100000 ROUNDS=10 LIMIT=20 make ranking-bench
N=1000 WORKERS=4 QUERIES=500 make concurrency-bench
N=1000 LIMIT=20 ROUNDS=1000 make hydration-bench
N=1000 LIMIT=20 OFFSET=100 ROUNDS=1000 make search-page-bench
N=1000 LIMIT=20 OFFSET=100 BODY_PREFIX=10000 ROUNDS=1000 make search-page-bench
N=10000 OVERRIDES=100 ROUNDS=1000 make document-frequency-bench
N=1000 make ingest-transaction-bench
MODE=file-store-batch N=2000 make memory-scaling-bench
make run
Open http://127.0.0.1:8890. The first crawl must use a public HTTPS URL. Crawler fetches reject loopback, private networks, IP literals, unsafe ports, and unsafe redirects.
SIGINT and SIGTERM request an ordered shutdown. The listener stops accepting
connections, active HTTP requests drain, crawler workers join, the current
index delta checkpoints, background compaction quiesces, and mapped-index plus
SQLite resources close. Repeated shutdown calls are harmless. A drain or
compaction failure is reported only after every remaining cleanup stage has
been attempted, causing a non-zero service exit instead of claiming a clean
shutdown.
Environment overrides:
JERBOA_RUNTIME_HOME— one coherent Jerboa runtime/library home (defaults tojerbuild --jerboa-home; do not mix it with a source checkout containing separately compiled.sofiles)JERBOA_SEARCH_DB— database path (defaultdata/search.db)JERBOA_SEARCH_PORT— listen port (default8890)JERBOA_SEARCH_BIND— listen address (default127.0.0.1)JERBOA_SEARCH_ADMIN_TOKEN— optional bearer token protecting every state-changing HTTP route; it must contain 32–1024 characters. A non-loopback bind is refused unless this is configured.JERBOA_SEARCH_WORKERS— crawler workers (default4)JERBOA_SEARCH_MAX_DEPTH— maximum discovered-link depth (default3, capped at32;0fetches only the seed)JERBOA_SEARCH_MAX_PAGES— hard page-attempt budget per crawl (default1000, capped at10000000;0disables fetching)JERBOA_SEARCH_DELAY_MS— minimum delay between requests to the same host (default500, capped at ten minutes;0is allowed). Crawler-local waits are interruptible in 25 ms slices, so stop does not wait out the interval.JERBOA_SEARCH_SAME_ORIGIN_ONLY— whether discovered links must share the seed's scheme/host/port (defaulttrue; acceptstrue/false,yes/no,on/off, or1/0)JERBOA_SEARCH_CHECKPOINT_EVERY— processed pages between incremental index checkpoints (default1000, capped at1000000)JERBOA_SEARCH_MAX_OVERLAY_DOCUMENTS— changed-document threshold for scheduling background segment compaction (default1000, capped at1000000)JERBOA_SEARCH_PAGE_UPSERT_BATCH_SIZE— maximum rows per bulk page-upsert statement for controlled import/reindex paths (default500, capped at500; ordinary crawler writes remain per page)JERBOA_SEARCH_MAX_ATTEMPTS— total attempts allowed per URL, including the initial request (default3, capped at16)JERBOA_SEARCH_RETRY_BASE_MS— initial delay before retrying a transient fetch failure (default1000, capped at ten minutes;0is allowed)JERBOA_SEARCH_RETRY_MAX_MS— exponential-backoff ceiling (default60000, capped at one hour and never lower than the configured base delay)JERBOA_SEARCH_MAX_CONNECTIONS— global concurrent HTTP connections (default128, capped at4096)JERBOA_SEARCH_MAX_CONNECTIONS_PER_IP— concurrent connections per client IP (default16, capped by the global limit)JERBOA_SEARCH_ACCEPTS_PER_SECOND_PER_IP— accepted connections per second per client IP (default32, capped at10000)JERBOA_SEARCH_IDLE_TIMEOUT_MS— idle connection timeout (default5000)JERBOA_SEARCH_HEADER_TIMEOUT_MS— request-header deadline (default15000)JERBOA_SEARCH_BODY_TIMEOUT_MS— request-body deadline (default30000); all three timeout settings are capped at five minutesJERBSEARCH_HTML_PARSER_WASM— shared sandbox parser artifact
Scope
The service implements document crawling, durable frontier recovery and retry deadlines, link graph capture, document replacement, BM25 search, HTML results, JSON APIs, crawl controls, and health/status endpoints. It deliberately excludes YaCy peer protocols, DHT/RWI exchange, Solr compatibility, legacy templates, and YaCy data migration.
For a database at data/search.db, durable page bodies live below
data/search.db.content/ and the derived index cache is
data/search.db.jerboa-index (manifest) plus
data/search.db.jerboa-index.segments/ (immutable segments). A validated
data/search.db.jerboa-index.qix binary sidecar serves mmap-hybrid queries.
Back up the
database and content directory together while the service is stopped. The
manifest, segments, and binary sidecar are optional derived data: deleting them
only makes the next startup rebuild the index.
Operational endpoints are:
GET /api/search?q=...&limit=20&offset=0— deterministic BM25 results.limitis capped at100andoffsetat1000. Responses include the effectivelimit,offset, returnedcount,has_more, and bounded previous/next offsets. The HTML/searchroute accepts the same parameters and renders encoded Previous/Next navigation. Ranked metadata hydration uses prepared primary-key point lookups, so every supported offset avoids compound-select and bind-variable ceilings.GET /api/status— crawler, generation, checkpoint generation, segment count, changed-document overlay size/threshold, query engine, full-core residency, compaction activity/failure, source, and dirty status. Documents are exact;terms_exactdistinguishes an exact term count from the safe upper bound used while base documents are replaced or tombstoned. Itshttpobject reports authentication state, effective connection/rate/timeout settings, and application input ceilings without exposing the bearer token, seed URL, local filesystem paths, or internal exception text. The crawl object includes the durablecrawl-id, its lifecycle state, and frontier counts andmax-frontierceiling scoped to that run.not-modified-this-runcounts accepted conditional304responses without exposing page validators, whilesitemaps-this-runcounts successfully processed sitemap documents.GET /healthz— process liveness only; it does not touch SQLite or the index.GET /readyz— readiness for search traffic. It opens/validates the current index view and returns503when the store is unavailable or background compaction has failed. Successful responses include generation and query engine; failures do not expose exception text or filesystem paths.GET /metrics— Prometheus text exposition with fixed, label-free gauges for readiness, index documents/terms/generation/segments/overlay/dirty/compaction, and crawler running/workers/retry-policy/frontier/conditional-fetch, robots-cache occupancy, and host-deadline cleanup state. It returns503withjerboa_search_up 0when metric refresh cannot read the store/index.
Cross-origin discovery does not relax fetch security. When
JERBOA_SEARCH_SAME_ORIGIN_ONLY=false, every discovered URL still passes the
public-HTTPS, DNS/IP, safe-port, redirect, robots, and per-host politeness
checks. The page budget remains global across all discovered origins.
With same-origin policy enabled, the final URL after redirects must also remain
on the seed origin; a cross-origin final response is rejected before storage.
Robots handling follows RFC 9309 group selection and longest-match behavior.
Exact, case-insensitive product-token groups are merged; wildcard rules are
used only when no exact group exists, and blank lines do not accidentally end a
group. A missing robots.txt reported by a permanent 4xx permits crawling.
Server/network failures, throttling responses, and unresolved redirects do not
fall through to allow-all: the page remains unfetched and enters the bounded
durable retry policy. Robots wildcard matching is linear in the rule and URL
length, avoiding recursive backtracking on untrusted policy text. Matching
normalizes percent-encoded ASCII unreserved bytes, uppercases retained reserved
encodings, and percent-encodes raw UTF-8. Encoded literal * and $ bytes
remain data and cannot become rule metacharacters.
Global Sitemap: directives in a successful robots.txt are discovered once
per origin and entered as durable frontier work. Oversized Sitemap: directive
lines share the same 8,192-character robots line ceiling and are ignored. Both
URL-set and sitemap-index documents are supported. Fetches are capped at 4 MiB,
each document yields at most 50,000 unique locations, indexes nest at most
three levels, and redirects and emitted locations must remain on the sitemap's
origin. DTDs and nested markup inside ordinary <loc> text are rejected; the
narrow parser resolves only the five predefined XML entities and valid numeric
character references.
Sitemap attempts share the crawl's hard request-attempt budget, durable retry
policy, politeness schedule, and run isolation. /metrics publishes
jerboa_search_crawl_sitemaps.
Robots responses are read under a 524,288-byte transport ceiling. Their
per-crawl in-memory cache uses a fixed 128-entry FIFO ring, bounding worst-case
retained policy state even when cross-origin crawling encounters an unbounded
stream of hosts. Policies are parsed and normalized once per origin and cached
as compiled matchers instead of reparsing raw text for every page. Parsing
retains at most 10,000 rules, 256 agents in one group, and 8,192 characters per
directive line. Cache occupancy, capacity, response bytes, and parser ceilings
are reported as robots-cache-entries, robots-cache-capacity,
robots-body-bytes, robots-rule-limit, robots-agent-group-limit, and
robots-line-chars in crawl status. Page and sitemap fetch ceilings are
likewise reported as page-body-bytes, sitemap-body-bytes,
sitemap-location-limit, and sitemap-depth-limit.
Discovery growth is independently bounded. Each crawl run persists a frontier
ceiling equal to 10× max-pages, with a 1,000-row floor for nonzero crawls and
a 1,000,000-row hard cap. Every transactional HTML-link, conditional-replay,
and sitemap enqueue shares the remaining capacity; duplicate URLs consume no
additional slot. This prevents a small request budget from generating an
unbounded SQLite queue. The effective ceiling is exposed as max-frontier and
jerboa_search_crawl_max_frontier.
Discovery attempts rejected after that ceiling is full are persisted per crawl
run, exposed as discovery-dropped in status, and published as
jerboa_search_crawl_discovery_dropped. The counter measures attempts rather
than unique URLs; rediscoveries already assigned to the current run are free.
Workers acquire a non-blocking per-host lease before processing a claimed row.
If another worker owns that host, the row is durably deferred without consuming
a fetch attempt and the worker can claim a ready URL from another origin. This
prevents a same-host queue prefix from occupying every worker while preserving
the configured request delay and one-at-a-time robots/page processing per host.
Per-origin delay deadlines are indexed by a min-heap and removed from the host
map as soon as they expire. Stale heap entries from a superseded deadline are
discarded without deleting its newer value, so cross-origin crawls retain only
the active politeness window instead of every host ever observed.
host-deadlines and host-expiry-events expose both live structures in crawl
status; jerboa_search_crawl_host_deadlines and
jerboa_search_crawl_host_expiry_events publish them as fixed-name gauges.
jerboa_search_crawl_robots_cache_entries and
jerboa_search_crawl_robots_cache_capacity likewise expose bounded compiled
robots-policy cache occupancy and its ceiling.
Successful page fetches have a second, document-level admission policy.
Case-insensitive HTML robots and JerboaSearch meta directives are combined
with generic and JerboaSearch-scoped X-Robots-Tag response directives; the
most restrictive noindex/nofollow result wins. none means both. A
noindex page retains its real HTTP status, metadata, body, and outgoing graph
in authoritative storage, but is removed from the live BM25 view and excluded
from every rebuild. nofollow retains outgoing links in the graph but adds none
of them to the crawl frontier. An individual rel=nofollow link is likewise
kept in the graph but excluded from discovery. This separates durable fetch
history and graph analysis from search-result and crawl admission.
Relative HTML links use the first <base href> in tree order, as defined by
the HTML document-base algorithm. An invalid or non-web first base falls back
to the response URL and later base elements remain ignored. URL
canonicalization removes dot segments without collapsing repeated or trailing
slashes, query-only references retain the current document path, and any
explicit non-HTTP(S) URI scheme is rejected before frontier admission.
Canonical identity also decodes percent-encoded ASCII unreserved bytes and
uppercases retained percent triplets. Encoded reserved delimiters stay encoded,
malformed triplets are rejected, and dot segments are removed after safe
unreserved decoding. Raw whitespace, including leading or trailing whitespace,
control characters, and backslashes are rejected before any trimming or
resolution. Canonical URL identity is capped at 4,096 characters at the shared
URL-module boundary, including relative-resolution output. The same
ceiling therefore applies to seeds, redirects, HTML links, canonical hints,
robots sitemap directives, and sitemap locations before hashing or durable
storage. Callers should provide percent-encoded URL data.
The first link[rel~=canonical][href] is resolved against the effective HTML
base. A valid non-self target is accepted only on the response origin, retained
in the outgoing graph, and admitted through the ordinary bounded frontier.
When follow policy and depth allow that admission, the variant page remains in
durable fetch history but is excluded from live and rebuilt search results.
Malformed, cross-origin, self, nofollow, and depth-boundary hints do not
suppress the fetched page.
Transport conditions and HTTP 408, 425, 429, and 5xx responses are
retried with bounded exponential backoff. Retry deadlines and attempt counts are
stored in SQLite, so a restart neither forgets nor resets them. Other HTTP
responses and failures in parsing, persistence, or indexing are terminal. Every
request attempt, including one that is rescheduled, consumes the crawl's hard
page-attempt budget. /api/status exposes the effective retry policy and the
number of queued URLs currently waiting for a deadline; /metrics publishes
the same values as fixed, label-free gauges.
Persisted retry/failure diagnostics are truncated at 4,096 characters at the
store boundary, preventing an unusually verbose network or parser condition
from amplifying durable frontier storage. Status exposes
frontier_error_chars.
Valid Retry-After delta-seconds and each of the three RFC 9110 HTTP-date wire
formats are honored for page and sitemap responses. The server value can extend
but never shorten exponential backoff, and the configured retry ceiling still
caps it. Malformed or over-128-character values are ignored.
Only exact text/html, application/xhtml+xml, and text/plain success
responses enter the text index; content-type parameters are allowed, but media
type substrings are not guessed. Unsupported successful media is completed as
skipped work rather than falsely incrementing the indexed count. Robots
exclusions use the same skipped counter; jerboa_search_crawl_skipped exposes
it to Prometheus.
Index amplification is separately bounded: each document contributes at most
50,000 tokens, consuming title tokens before body tokens, and alphanumeric runs
longer than 128 characters are ignored. The tokenizer scans string indices
without first allocating a character list. Live ingestion, persisted
token_count, rebuilds, segments, and mmap sidecars all use this same bounded
logical document. /api/status exposes document_tokens and term_chars.
Queries likewise use at most the first 32 distinct terms found within a bounded
256-token prefix. Core, mmap, hybrid overlay ranking, result hydration, and
snippet selection share this parser, preventing query text from multiplying
term-stat lookups or WAND cursors without bound. Status exposes query_terms
and query_token_scan. Direct paged search windows are bounded to the first
100,000 ranked candidates, while the HTTP API clamps offsets lower for
interactive use; status exposes search_page_window.
Preview localization scans at most the first 65,536 body characters and first
eight query terms. It reuses the parsed query term list and avoids lowercasing
the body window when that window is already lowercase, while retaining
case-insensitive matching for mixed-case content. Ranking still uses the full
bounded token view; when a match lies outside the preview window, the snippet
safely falls back to the document start. Status exposes snippet_scan_chars
and snippet_terms.
HTML extraction has independent post-sandbox ceilings. A representation may
contain at most 100,000 parsed nodes with nesting no deeper than 256, retain at
most 2,097,152 readable characters, and contribute at most 10,000 unique links
including an accepted canonical target. The canonical slot is reserved before
ordinary-link traversal, and traversal stops as soon as its graph budget is
full. Whitespace normalization scans by string index instead of materializing a
second character list. /api/status exposes document_nodes,
document_depth, document_text_chars, and document_links.
Titles are collected directly under a 1,024-character ceiling rather than
materializing an unbounded title subtree. HTML/header robots values and link
rel values use an index-based 8,192-character directive scanner; oversized
directives fail closed as noindex,nofollow and cannot produce a canonical
hint. Status exposes document_title_chars and directive_chars.
Each accepted seed belongs to a durable numeric crawl run. Starting the same canonical seed resumes its latest paused run, retaining queued URLs, attempts, and retry deadlines. Starting a different seed creates an isolated run, so it cannot claim frontier rows left by another seed. An explicit stop, process restart, or exhausted page-attempt budget leaves unfinished work paused; a fully drained frontier becomes completed, and starting that seed again creates a new run. Newly rediscovered URLs may move from an older run into the current run, but duplicate links within one run remain no-ops. Stopping interrupts local politeness and frontier-poll waits within one 25 ms slice. A row interrupted before its request is durably requeued and its claim attempt is reversed. Synchronous network activity is allowed to finish under its existing 10/20-second request deadline.
Successful responses persist bounded ETag and Last-Modified validators.
Later crawl runs send them as If-None-Match and If-Modified-Since; validator
values containing HTTP control characters or more than 1,024 characters are
never reused as request headers. An exact-URL 304 Not Modified refreshes
fetch time and any returned validators without replacing content, advancing the
search generation, or perturbing live rankings. The same transaction replays
only the page's
previously approved crawl edges into the new run, so unchanged seeds do not
truncate traversal and nofollow links remain graph-only. Legacy link rows
default to non-crawlable until a fresh representation classifies them.
An observed 404 Not Found or 410 Gone retires the final URL immediately.
The authoritative row keeps its prior title, body reference, and link history,
but records the new status, clears cache validators, becomes non-indexable, and
advances the search generation. Live postings are tombstoned and rebuilds also
exclude the row, so stale content cannot reappear after restart or compaction.
A later successful response replaces the preserved state and restores the URL
normally. Retryable responses such as 408, 429, and 5xx never retire the
last good representation. /api/status exposes retired-this-run, and
/metrics publishes jerboa_search_crawl_retired.
POST /api/index/checkpoint— append changes as an immutable segment and atomically publish its manifest; reaching eight segments or 1,000 changed document IDs since the mapped base schedules background compaction without delaying the checkpoint response. SetJERBOA_SEARCH_MAX_OVERLAY_DOCUMENTSto tune the latter threshold. Responses includeok,generation, andcheckpoint_generation, not local manifest paths.POST /api/index/compact— replace the current chain with one full immutable segment and delete only obsolete files named by the old validated manifest. Responses includeok,generation, andcheckpoint_generation, not local manifest paths.POST /api/content/gc— remove unreferenced managed content blobs and abandoned managed blob temporaries; the crawler must be stopped so newly fetched content cannot race collection. The stop endpoint joins all fetch workers before returning, so a successful stop is a quiescent GC boundary.
All state-changing routes are administrative: POST /crawl,
POST /api/crawl, POST /api/crawl/stop, POST /api/index/checkpoint,
POST /api/index/compact, and POST /api/content/gc. When
JERBOA_SEARCH_ADMIN_TOKEN is set, clients must send
Authorization: Bearer <token> on each of them. Missing or incorrect
credentials receive 401, a WWW-Authenticate challenge, and a JSON error;
the HTML home page hides its crawl form because browser forms cannot attach the
header. Search, status, and health endpoints remain public. Token comparison
uses Jerboa's timing-safe crypto primitive, and the token is retained only in
the application closure—not exposed in settings or responses.
The application rejects bodies above 65,536 bytes with 413 before form
decoding, search queries above 1,024 characters with 414, and crawl seeds
above 4,096 characters with 400. Discovered URLs enforce that same ceiling
inside canonicalization and are simply ineligible for admission. The
underlying Jerboa transport independently bounds request headers at 16 KiB and
bodies at 4 MiB, so malformed traffic is bounded before application policy
runs.
Without a token, the administrative routes remain available for the default
local-only workflow, but the server accepts only 127.0.0.1, ::1, or
localhost as its bind address. Set the token even on a loopback bind when a
reverse proxy makes the service reachable by other machines. Supply the token
through the environment or a service manager's secret facility; do not place it
in URLs, request bodies, or command-line arguments. Bearer credentials are
replayable, so any non-local client connection must use TLS. The current
launcher serves plain HTTP and is intended to sit behind a TLS-terminating
reverse proxy on a private or loopback connection.
See ARCHITECTURE.md for scaling boundaries and package extraction points.