Trust

The posture in one page. Security questionnaire has the long answers; this is what to hand somebody who has ten minutes.

What it is

One process, on your hardware, that clones your repositories and answers questions about them — for humans in a browser and for agents over MCP and a REST API.

No SaaS component. No vendor account. No model, no GPU, no inference cost. No database to provision, no cache, no message queue.

What it stores

Everything lives under one directory, instance.dataDir:

Clones of your repositories Rebuildable — git clone again
The analysis store Derived from the clones
subjects.db Users, credentials, seats, sessions. The only durable state that cannot be reconstructed.

That last one is the whole backup story. See Backup and restore.

What it sends

Nothing to us. The product never phones home — no telemetry, no licence check-in, no analytics, no update ping, on any tier, for any reason.

Egress, precisely

The claim people expect is "no egress", and the honest version is more useful than the slogan, because most of what looks like egress is not made by this process at all.

Connection Made by When
git clone, git fetch the git binary, in a subprocess Whenever a repository is ingested or refreshed
Container image pull your container runtime At deploy
SSO to your identity provider your auth proxy Every browser sign-in, in auth.mode: proxy
Code-host enumeration searchcode discover, a CLI you run Only when you run it
Code-host enumeration searchcode Only if you declare a connections.sources entry, which names exactly one host
OTLP trace spans searchcode Only if you set observability.tracing.enabled: true
Audit records to a remote syslog searchcode Only if you set audit.output: syslog with a remote address

The last three rows are the only outbound connections the searchcode process itself ever makes, and each one exists because you configured it. A default install configures none of them: there is no connections: block, tracing is off, and audit.output defaults to stdout. The honest sentence is therefore "no outbound connection unless you configure one, exactly three things can configure one, and never the vendor" — not "no egress", which was true before connections existed and is not the same claim.

observability.tracing.enabled, .endpoint and connections.egress are restart-only, deliberately: opening one of the process's outbound connections is not a change a file edit should make a second later without the deployment noticing.

A connection to a code host inside your own network is not internet egress. GitHub Enterprise Server, self-managed GitLab and Bitbucket Data Center live behind the same firewall as this product, so the socket goes to ghe.internal.example.com:443 and never leaves — and connections.egress: internal-only refuses a connection naming a public code host at config load, so it is a switch you can point at rather than a policy somebody has to review. The generated NetworkPolicy is derived from the hosts you declared, so what the process may reach is readable off the manifest.

A connection is bounded in three ways that are tests rather than promises: it names one host and the client is pinned to it, so a cross-host redirect is refused and the credential never travels to a host you did not declare; its token is metadata-only and nothing in this product reads, writes or mirrors your code host's access-control lists; and no domain we own is reachable from the process at all, in any mode, with no setting that permits it.

If you would rather the server never called a code host, do not declare a connection: searchcode discover is a CLI you run, whose output is a conf.d/ fragment you review and commit, and it is unchanged.

What it runs as

Who can see what

One function answers "which repositories may this principal see", and every surface derives from it — search, files, blame, history, the API, MCP, analytics, aggregates. Filtering happens at query time, so a repository you cannot see is never read.

Access is granted by declarative bindings in configuration: identity groups on one side, repository groups on the other. Allow-only, order-independent, validated in CI. authz.defaultAccess defaults to deny.

/api/v1/explain-access answers "why can Bob see this?" with the binding that granted it.

What is recorded

A separate audit stream that records denials as well as grants, unconditional on every tier — no licence check, no off switch. Application logs are a different stream: pure JSON on stdout, one object per line, with a unique code per emission site. See Log codes.

What the licence does

It sets two numbers: repository count and user count. No feature is licence-gated on any tier, there is no capability array in the payload, and verification is offline against a compiled-in public key.

An absent, tampered, expired or unknown licence is community mode — logged loudly, reported on /api/v1/status, never a crash and never a lockout. Past grace pauses indexing and nothing else.

Untrusted input

Every repository in the estate is untrusted input by definition — it is whatever your developers wrote, plus whatever their dependencies vendored.

Raw file bytes are served as the declared type with nosniff, Content-Disposition: attachment and a default-src 'none'; sandbox CSP, with no exception list. Any HTML or SVG among your code would otherwise be stored cross-site scripting against every user of the instance. api.rawContentOrigin optionally moves raw content to a second hostname; the headers are the defence and the origin is an addition to it.

Syntax highlighting uses a text/scanner tokenizer rather than a regex lexer, because a backtracking lexer on a minified bundle or a 40,000-character generated line is a denial of service against the file view.

Where to go next

Generated from the documentation that ships with searchcode enterprise. Questions: ben@boyter.org