Run your security review from this page. Every control names what enforces it.
- controls enforced in code that refuses, each naming its mechanism and its test
- 34
- operational commitments — true of a deployment run as described, and missable
- 9
- returned on a wrong-tenant read, never 403, so ids cannot be probed
- 404
- things we do not have, named in the first table with what stands in their place
- 11
Reviewed re-read against the code printing opens every fold
https://entitybind.com/trust
Assurance questions go to security@entitybind.com. Data subject rights go to privacy@entitybind.com.
On this page
What we hold, and what we do not
11 rows, all of them absences, each with what stands in its place. If one of these is a hard gate in your process you cannot buy this today, and it is better to find that out on the first call than in week six of a review.
| What a review asks for | Status | Where that leaves you |
|---|---|---|
| No SOC 2, Type I or Type II. No ISO 27001. | Not held | Not started, no auditor engaged, no target date. A buyer whose procurement requires either cannot buy this today, and nothing else on this page changes that. The gap between what is published here and a SOC 2 report is not the controls — it is the independent attestation that they operated over a period. That is the thing we do not have. |
| No third-party penetration test. No bug bounty. | Not held | The threat model behind this page is internal and adversarial, and it is our own. It walks four boundaries through the six standard attack categories — spoofing, tampering, repudiation, information disclosure, denial of service and elevation of privilege, the STRIDE checklist — and gives every open risk an owner and a review date. Nobody outside this repository has been paid or invited to break the system. |
| No zero-data-retention addendum with the model provider. | Not held | Prompts are subject to the provider's standard commercial retention. Their not-used-for-training term is a contractual commitment of theirs rather than a control of ours, and nothing in the code enforces it. The code-enforced answer to the same worry is the no-egress depth cap, which sends nothing to a model provider at all. |
| No DPA or SCC package ready to sign, and no documented data protection position beyond the endpoints. | Not held | What is published is what the code does: erasure, portability, the retention reasoning, licence handling, tenant scoping and the sub-processor list. A lawful basis, a controller or processor determination, a data protection impact assessment, a record of processing activities and an international transfer mechanism are not documented. Those belong in a conversation on the first call, because none of them is written down yet. |
| No automatic retention, and no delete-my-tenant button. | Not held | All four purge windows are scheduled jobs an operator has to run, and an unscheduled deployment retains everything for ever. Erasure is per entity or per record: a buyer expecting one call that empties a tenant gets a loop rather than a button, on purpose. |
| No database-enforced isolation, and no application-side encryption at rest. | Not held | Tenant scoping is enforced in the application: one database, one schema, one runtime role, no row-level security, so a SQL-injection defect would cross every tenant at once. Encryption at rest is a property of the volume and the database underneath, not of this application, and there is no field-level encryption anywhere in it. |
| No managed multi-region offering, and no region to select. | Not held | The artifact is one container plus a database, configured entirely by environment variables, and it runs wherever it is run — including the deployment we run for a customer. Data residency is a conversation about where that deployment sits rather than a checkbox, and it does not change the sub-processor calls below, which reach US, UK, EU and global services. |
| No status page, no uptime SLA, and no performance guarantee. | Not held | The request deadline and the deep-resolution allowance are arithmetic safety ceilings that stop one request stacking timeouts into ten minutes. They are not measured latency at the 99th percentile, the p99, and they are not a service level. The backup objectives — at most 24 hours of writes lost as the recovery point objective, at most four hours to restore as the recovery time objective — are bootstrap objectives accepted by a single operator, not enterprise ones. |
| The accuracy evidence | Status |
|---|---|
| No SOC 2. It has not been started. | Not held |
| No independent external validation of the accuracy figures. Every fixture is one we built. | Not held |
| No production customer holdout. The evidence is internal. | Not held |
It is an early-stage product, sold and priced as one. Global data breadth and independent market validation are not yet at incumbent scale, and the accuracy figures come from internal fixtures rather than an external holdout.
A further 10 items are open, each published with the date or the trigger that moves it, in the open items below. Every accuracy figure, its population, its interval and what it does not show is at /accuracy.
If your procurement floor requires a certification we do not hold, we will tell you whether a compensating control gets you there — and if nothing does, you will hear that before you spend a review cycle on us.
Start freeHow you get a workspace and keys, and when the evaluation opens
Create your workspace online, verify your email and start on Free with 300 credits and no card. Paid subscriptions are available through Stripe in workspace Billing, with applicable VAT and no automatic usage or overage charges. Registry checks follow your activated plan. Assisted pilots and the planned full-pipeline evaluation are arranged separately.
Send a copy of a file and we scope a Merge Safety Sprint on it. It runs over the public API and writes nothing to your CRM, ERP, MDM or warehouse. Your workspace is a tenant of its own. Each API key you create in it is scoped to read or resolve, its value is shown once, and you can list and revoke keys yourself, so your review can probe the tenant boundary described below with its own credentials. A CSV you upload there is stored in your workspace once you start a run; it is not a no-write preview.
Give your agent room to test your companies, inspect the citations and compare the results. Deterministic matching, adjudication and registry research, with the same evidence and confidence rules as the paid product.
The full-pipeline evaluation is arranged separately and is not activated by signup. Request it for your workspace; we confirm availability, sources and the start date before activation.
How to read the tables
Every control on this page is filed as one kind or the other, and each table marks which. The difference decides how much a given control is worth to you.
enforced in code
A required argument with no default. A guard that raises before any write. A surface that refuses a credential outright. These hold whether or not anybody remembered them, and each row names the symbol, endpoint or test that makes it hold. When one fails it fails as an exception at the call site, and nobody finds out from a leak in production.
operational commitments
Scheduled purges, tested restores, a workbench kept off the internet, encryption belonging to the volume underneath. They are true and they are checkable. They are also the ones a bad week can miss, so they are counted apart and you can see how many of the 43 depend on somebody doing something.
A note on naming: settings are described by what they set, because their environment-variable names carry the engine package’s internal prefix. The site prints those names only where a process reads them verbatim, in the MCP server’s set-up. Code symbols, endpoints and test paths are quoted exactly. Those are the things you can go and check against a deployment.
A wrong-tenant read returns 404, never 403
A 403 confirms the id exists, which turns every read into a way of enumerating another customer's entities one id at a time. Entity reads, decision receipts, entity deletion, review settlement and the admin surface all answer the same way.
Tenant isolation controls
6 enforced in code 2 operational commitments every row names its mechanism and its limit
Tenant isolation controls
6 enforced in code 2 operational commitments every row names its mechanism and its limit
| Kind | Control, and its limit | Enforced by |
|---|---|---|
Every persistence method takes the tenant as a required keyword argument with no default, so a new call site that forgets whose data it is touching raises rather than quietly reading a shared tenant. There is no default because a default would have meant 'the shared tenant'. The failure mode is a TypeError at the call site rather than a leak in production. | enforced in codedomain/ports.py — required keyword argument, no default. Proven by tests/integration/test_tenancy.py | |
A read against another tenant's id returns 404, never 403, on entity reads, decision receipts, entity deletion and review settlement — and the same rule holds across the admin surface. A 403 would confirm the id exists, which turns every read into a way of enumerating another tenant's entities one id at a time. The 404 is the same answer whether the id never existed or belongs to somebody else. | enforced in codeUniform 404 on get, get_decision, delete_entity and review.resolve, and on /v1/admin/* | |
Every read restricts to the tenant inside the SQL query rather than fetching rows and discarding the wrong ones afterwards, and candidate shortlisting filters before retrieval rather than after it. | enforced in codepersistence/repositories.py; entity_blocking_keys carries a denormalised tenant column so the hot path never joins | |
The cache that recognises a repeat submission is keyed on the content fingerprint and the tenant together, as a composite primary key rather than as a filter, so byte-identical content submitted by two customers resolves to two separate entities. A filter would not have been enough. The insert itself would have collided, and one customer would have been served another's entity as a cache hit. | enforced in codeComposite primary key on resolution_fingerprints; the cross-tenant case is a test, not an assumption | |
A cross-tenant write hits a backstop that raises, mapped deliberately to HTTP 500 rather than 403. 500 is the accurate status. Reaching that line means our wiring is broken rather than that a caller did something forbidden, and a 403 would have told the caller it was their fault. | enforced in codeTenantViolationError raised in save() | |
The remote MCP transport derives the tenant per call from that call's own credential. The composition root holds no tenant field and no default, and absence fails closed: no request, no header, a malformed header, an unknown or revoked token, or any scope other than read, resolve or review all raise rather than falling back. An earlier version of the trust document said the MCP server bound one tenant at startup. That was true until 2026-08-12, and the document carries the correction beside the old sentence rather than deleting it. A single-tenant deployment setting still applies to the local stdio transport, where there is no second party to distinguish, and is ignored over HTTP. | enforced in codemcpserver/identity.BearerCaller. Proven by tests/integration/test_mcp_remote.py::TestPerCallTenancy, which puts two tenants concurrently in flight through one process | |
The internal workbench reads across every tenant by design and is carved out of the isolation guarantee rather than tucked under it. It is a separate application on its own port, it must not be internet-reachable, and it admits only a locally issued operator credential behind a single sign-on (SSO) proxy. There are no per-tenant workbench grants, and a shared proxy credential produces a shared audit identity rather than a per-person one. Mapping each operator to their own identity is infrastructure work that has not been done. | operational commitmentSeparate ASGI app; customer resolve, review and admin credentials fail closed against it | |
Isolation is enforced in the application, not in the database. One database, one schema, one runtime role, no row-level security. A SQL-injection defect or a compromised runtime role would cross every tenant at once. Parameterised queries through the ORM, and a database role that can change rows but not the schema, are defence in depth against that rather than a second enforcement point. | operational commitmentStated as a residual rather than as a control |
Your Entity Bind workspace is a tenant of its own and you create its keys yourself, so your review can probe this boundary directly rather than taking the 404 on trust.
Start freeFour windows, four commands, nothing on a timer
Nothing purges itself. Four windows exist, each is its own maintenance command, each is a deployment setting you can shorten, and every one is a scheduled job an operator has to run. An unscheduled deployment keeps everything for ever.
Retention and erasure controls
3 enforced in code 3 operational commitments every row names its mechanism and its limit
Retention and erasure controls
3 enforced in code 3 operational commitments every row names its mechanism and its limit
| Kind | Control, and its limit | Enforced by |
|---|---|---|
Erasing a company removes eight things in one transaction: its facts, the shortlisting keys that make it findable, every submitted record it holds, every open review item embedding one of those records, the replay fingerprints bound to it, every cached response quoting it, the settled review items it touches (redacted rather than deleted), and the entity row. Child rows are removed explicitly rather than by ON DELETE CASCADE, because SQLite only honours cascade behind a per-connection pragma and erasure has to be total on every supported backend. | enforced in codeSqlEntityRepository.delete_entity, and the erase-entity maintenance command | |
An engagement-scoped erasure signs its receipt twice — once with a shared secret and once with a public-key signature, Ed25519 — so your own auditor can verify it against a published public key rather than asking us. Outside development the command refuses to run at all without a signing key. The receipt names its own scope, including what it does not reach. It is evidence of a deletion event on request, and it is not evidence of an automatic expiry window, because there is not one. | enforced in codeEd25519 signature over the erasure receipt; refuses to delete before it can sign | |
There is no bulk delete and no delete-my-tenant button. Erasure is per entity or per record. A buyer expecting one call that empties a tenant gets a loop rather than a button. That is a design decision rather than a gap: one call that empties a tenant turns a single mistyped id into total loss of that tenant's data. | enforced in codeDeliberate absence. Enumerate with GET /v1/entities, /v1/entities/export and /v1/records/unbound, then delete each id | |
Four retention windows, each its own maintenance command: decisions, submitted records and the usage ledger default to 2,555 days, and open review items to 365. Every window is a deployment setting a buyer can shorten. Nothing purges itself. All four are scheduled jobs an operator has to run, and an unscheduled deployment retains everything for ever. | operational commitmentpurge-decisions, purge-records, purge-open-reviews and purge-usage in the maintenance module | |
2,555 days is seven years, chosen to clear the money laundering regulations' five-year duty to retain the evidence behind a customer due-diligence decision. Below roughly 1,825 days a know-your-business customer loses the thing they are legally required to be able to produce. Shortening the window is your choice rather than ours, and it has that consequence. | operational commitmentMLR 2017 reg. 40, reasoned in docs/TRUST.md rather than inherited from a template | |
Purging open review items logs at warning level rather than at info, because it deletes work a human being was expected to do. A silently emptying review queue is indistinguishable from one nobody is filling, and the second is a much worse problem than the first. | operational commitmentdelete_open_reviews_older_than logs WARNING |
What each right reaches, and what it does not
Six operations, each carrying both halves in the same type size. The one people miss is the third: a submission that abstained and never bound to an entity is invisible to the entity listing, and an endpoint exists whose whole job is to enumerate those so they can be erased.
Erase a company
DELETE /v1/entities/{id}, or the erase-entity maintenance command
Erase a company
DELETE /v1/entities/{id}, or the erase-entity maintenance command
- The entity's facts and the shortlisting keys that make it findable
- Every submitted record the entity holds as a member
- Every open review-queue item embedding one of those records
- Every settled review item embedding one — redacted rather than deleted
- The replay fingerprints bound to the entity
- Every cached API response quoting it
- The entity row itself
- The decision audit trail. A surviving decision row can carry cited evidence and, on an adjudicated pair, the model's rationale, and both can name the company. That is a deliberate retention rather than an oversight, it expires on the decision retention window, and nothing in the API deletes a decision on demand.
- A reviewer's free-text reason. Redaction empties the submitted record, the candidate's name and domain, the candidate snapshot, the citations and the adjudication rationale, and stamps the redaction time. The verdict, the reviewer id and their written justification stay, because together they are the evidence that a named second person approved the merge, and removing the reason while keeping the attribution leaves who approved it without on what basis. A reviewer who types a company name or an address into that box has put it somewhere erasure does not reach.
- The fact that something happened. A redacted settled review item is still a row stating that an abstention occurred, when, at which stage, and how it was settled. If your erasure duty extends to no trace that this record was ever submitted, we do not meet it.
- The usage ledger, which keeps the record id and what the call cost — never the record — and outlives the data by design, because it is the billing evidence.
Erasure removes the replay binding, so the next identical submission is a fresh, chargeable resolution and may not produce the same entity id. That is correct: the binding caches a decision rather than replacing one.
Erase a submission that never resolved
DELETE /v1/records/{record_id}, or the erase-record maintenance command
Erase a submission that never resolved
DELETE /v1/records/{record_id}, or the erase-record maintenance command
- The record row
- Open review items embedding it
- Settled review items embedding it, redacted
- Cached responses quoting it
- The submission's crosswalk rows, deleted outright rather than redacted
- The entity it contributed to. A golden record — the single merged view of the company — outlives its members: facts derived from an erased submission stay until the entity itself is erased. If the request is about the company, erase the company.
- An entity's member list, which still names a record erased on its own. The id is an opaque pointer to a row that is gone, kept because it is the crosswalk back to your rows.
Find the records erasure would otherwise miss
GET /v1/records/unbound, or the list-unbound-records maintenance command
Find the records erasure would otherwise miss
GET /v1/records/unbound, or the list-unbound-records maintenance command
- Every record no entity claims — precisely the records the entity listing cannot show you
You cannot erase what you cannot enumerate. This endpoint exists because there is no bulk delete: enumerate, then delete each id. An abstained submission that never bound to an entity is reachable here and nowhere else.
Take everything with you
GET /v1/entities/export
Take everything with you
GET /v1/entities/export
- Every golden record in your tenant, with its provenance, streamed as newline-delimited JSON — one document per line, so both ends stream it rather than buffering an unbounded array
This is the portability path, and it walks by the last key seen rather than by a row offset — a keyset walk rather than an offset walk — on purpose: rows can disappear while you export and erase in parallel, and an offset walk would shift every later row left and skip one at each deletion. The last emitted position stays a safe cursor even after that row has been erased.
Join our evidence back to your own rows
GET /v1/records/by-ref/{client_ref}
Join our evidence back to your own rows
GET /v1/records/by-ref/{client_ref}
- The record id we minted, the decision id receipt, the entity it landed on, and the review id if it abstained
Your own reference is stored beside the resolution and is still excluded from the submission fingerprint, so two submissions of identical content under different references are still the same content and the second is still a free replay.
Reverse a merge
POST /v1/entities/{id}/detach
Reverse a merge
POST /v1/entities/{id}/detach
- One attributed member and every fact carrying its origin record id, moved to a fresh entity, with both blocking indexes, the replay binding, the crosswalk and the correction receipt updated in one transaction
- Anything wider. This is not a general unmerge: legacy customer facts with no attribution, missing stored members, a sole-member entity, identical-content members and arbitrary cluster splits all fail closed rather than guessing. Registry and research facts stay on the source rather than being assigned to one side.
If your erasure duty reaches further than these do, the reply is where to say so, before you integrate. We will tell you whether shortening the decision window closes the gap.
Start freeSeparate scopes, and what an agent cannot reach
Both credential families verify through one authenticator. Revoke a credential and it is dead on the HTTP surface and the MCP surface in the same instant. On an agent-facing product the narrowest control matters most: the MCP surface refuses an admin credential outright, and admin is not an issuable OAuth scope at all. An agent that can be talked into anything still cannot be talked into minting itself a credential.
Credential, scope and OAuth controls
8 enforced in code 1 operational commitment every row names its mechanism and its limit
Credential, scope and OAuth controls
8 enforced in code 1 operational commitment every row names its mechanism and its limit
| Kind | Control, and its limit | Enforced by |
|---|---|---|
API keys and OAuth access tokens are both stored only as SHA-256 digests — a one-way hash that cannot be reversed to the credential it came from — and the raw value is returned exactly once, at issue. Install tickets, authorization codes and refresh tokens go through the same hashing function, so no table holds a usable secret. | enforced in codesha256_hex across the account store and the OAuth store | |
Both credential families authenticate through one authenticator, so a revoked credential is revoked on the HTTP surface and the MCP surface at the same instant rather than eventually. | enforced in codeApiKeyAuthenticator, shared by both transports | |
Key scopes are separate rather than layered: read, resolve, review, admin, metrics and operator. A read credential reads what one tenant already holds and cannot resolve; a resolve credential reaches one tenant's data plane and cannot provision anything; a review credential can settle that tenant's review queue but cannot run the resolver; an admin credential reaches the provisioning subtree and no customer data at all. A leaked admin credential can mint a credential that could exfiltrate — an act recorded in the log and visible in list-keys — but cannot itself read a record. That is a smaller blast radius than a single all-powerful key, not a zero one. | enforced in codeADR-0016; routes/admin.py refuses a resolve credential and the data plane refuses an admin one | |
The MCP surface refuses an admin credential outright, so the credential with the widest provisioning blast radius is unreachable by a model. On an agent-facing product this is the control that matters most. An agent that has been talked into something it should not do still cannot mint itself a credential, because the surface it speaks through will not carry one. | enforced in codekey_scope_forbidden on the MCP transport; admin is not an issuable OAuth scope and has no entry in the scope mapping | |
The client has to prove at the token exchange that it started the authorisation, by producing the secret whose SHA-256 hash it published at the request — PKCE with S256 — and the weaker plain method is refused at the request. Redirect URIs match by exact string equality with no wildcards, prefixes or normalisation, and an unknown client or unregistered URI is refused without redirecting, so the endpoint cannot be used to bounce a visitor to somebody else's address. Every client is public and holds no secret, so that proof is the only binding between the request and the exchange. It is load-bearing here rather than defence in depth, which is why plain is refused rather than discouraged. | enforced in codevalidate_code_challenge; registration_error_for, following RFC 6749 §4.1.2.1 | |
Authorization codes and refresh tokens are single-use: they work once, and the check is a conditional update that is atomic on both supported databases. A second use — a replay — revokes the entire grant, every access and refresh token descended from that authorization. | enforced in codeConditional UPDATE; RFC 6819 §5.2.1.1 | |
Access tokens are opaque — they carry no signed claims of their own — and are checked against the database on every call, so revoking a grant takes effect on the next call with no window between revocation and expiry. Tokens expire in one hour by default and are bound to one grant. The revocation endpoint returns 200 whatever happened, following RFC 7009 §2.2, so it cannot be used to confirm whether a string is a live token. The real outcome is in the logs. | enforced in codeADR-0019; DELETE /v1/admin/oauth/grants/{id} for the customer, revoke-oauth-client for the operator | |
A human verdict is attributed three ways, none of them supplied by the caller: the write requires a review-scoped credential, the reviewer identity is the immutable name assigned when that credential was minted and a supplied name is refused before any state change, and every verdict carries a server-derived acting key id and acting surface. | enforced in codeGET /v1/decisions?acting_key_id=… lists every verdict a given credential approved | |
Key rotation runs a 24-hour grace window by default, during which the old key still works, and a zero-second grace kills a leaked key immediately. | operational commitmentRotation grace configured per rotation, not globally |
Company-first by design, with two named exceptions
Research findings are checked against an attribute allow-list at the ingestion boundary, so anything outside it is dropped at the door and never stored. Company-first is not the same claim as no personal data, and this page does not make the second one.
Entity Bind resolves companies and organisations only. It does not resolve people, vessels, bank-account ownership, sanctions exposure, emissions or geolocation, and its results may not be used for eligibility decisions about individuals, including employment, credit and tenancy.
Both people-data exceptions are registry data published under an open licence: officer and PSC names, roles and dates from the UK register under the Open Government Licence, and a director’s or statutory auditor’s name and role from the French register under Licence Ouverte 2.0. Everything else about a person is discarded at ingestion rather than filtered on the way out.
Scope and people-data controls
3 enforced in code 1 operational commitment every row names its mechanism and its limit
Scope and people-data controls
3 enforced in code 1 operational commitment every row names its mechanism and its limit
| Kind | Control, and its limit | Enforced by |
|---|---|---|
Research findings are checked against an attribute allow-list at the boundary, so anything outside it is dropped rather than persisted, and the research agent's own instructions put private individuals out of scope explicitly. An instruction to the agent is a request. The allow-list is what actually happens to a finding, which is why both exist and why the allow-list is the one worth checking. | enforced in codeADR-0005; ALLOWED_ATTRIBUTES validated at the ingestion boundary | |
Personal identifiers are discarded at ingestion, not filtered on the way out. Dates of birth, birth years and nationalities never become a stored fact, only four corporate fields are read out of the identification block of a person with significant control — a PSC, in the register's own term — and the API type for an individual owner carries a name, the natures of control and a notification date, with no field that could carry a date of birth. | enforced in code_DISCARDED_OFFICER_FIELDS, _PSC_IDENTIFICATION_FIELDS, and the shape of IndividualOwnerView in api/schemas.py | |
A French sole trader is handled one step earlier still: the adapter stores nothing at all and returns only the citation, because for that legal form the person is the company. | enforced in codesources/sirene.py | |
Two deliberate people-data exceptions exist, both registry data under an open licence: UK officer and person-with-significant-control (PSC) names, roles, appointment and notification dates under the Open Government Licence, and French officer names and roles under Licence Ouverte 2.0, including a corporate officer's own registration number. Company-first is not the same claim as no personal data, and only the first is being made. What is true is that there is no purchased people corpus, no personal email address, no personal phone number and no date of birth anywhere in the model. | operational commitmentsources/companies_house.py and sources/sirene.py, listed rather than discovered |
Sanctions, adverse media and risk scoring are out of scope, and there is no risk field anywhere in the schema. A screening result is an assertion about a matcher rather than a fact about a company, and this product does not store one.
Every host this deployment can reach
The set is closed. Register base URLs are compiled-in constants rather than caller input, there is exactly one model-provider SDK in the dependency manifest, and a unit test reads the request URL off every wired adapter and fails the build if a host is missing from this table.
| Sub-processor | What it receives | Why | Status |
|---|---|---|---|
| Anthropicapi.anthropic.com | The candidate pair's populated fields, including the submitted address, the free-text context and the entries of the extra map, plus the cited evidence. On a research call, the search queries and fetched URLs as well.Reached only by a record that escalates past probabilistic scoring. A call capped at the scoring stage reaches no model provider at all. | LLM adjudication of the borderline band, and agentic research on the ambiguous tail. | Reached today |
| Companies Houseapi.company-information.service.gov.uk · document-api.company-information.service.gov.uk · s3.eu-west-2.amazonaws.com | The company number, or the name plus jurisdiction, being looked up.The S3 host is the presigned URL the document service redirects to; it is listed because the adapter follows the redirect. | UK register facts, officers and person-with-significant-control (PSC) data, registry-backed candidate generation, and financials read from filed accounts. | Reached today |
| GLEIFapi.gleif.org | The LEI, or the name being searched. | LEI records and Level-2 parent relationships. | Reached today |
| SEC EDGARdata.sec.gov · www.sec.gov | The CIK, ticker or name being searched. | US filer identity and tickers. | Reached today |
| INSEE SIRENErecherche-entreprises.api.gouv.fr | The SIREN or SIRET being looked up, or the name being searched.Reached through the French state's keyless search API, operated by DINUM. A reviewer mapping a country column needs the whole of this: the register needs no credential, so it is either on or off and never half-configured, and jurisdiction routing skips a national register only when a record states a different country. A record that states no country has made no claim to contradict, so its name goes to a French state service unless the source is disabled. | French register facts, and registry-backed candidate generation. | Reached today |
| filings.xbrl.org (ESEF)filings.xbrl.org | The LEI of the company being enriched. | Listed-company financials from the annual financial report an issuer filed under DTR 4.1.14. | Reached today |
| Brønnøysund Register Centredata.brreg.no | The organisation number being looked up, or the name being searched. | Norwegian register facts and candidate generation. | Reached today |
| PRH / YTJavoindata.prh.fi | The Business ID being looked up, or the name being searched. | Finnish register facts. | Reached today |
| ARESares.gov.cz | The IČO being looked up.Facts are pass-through by default — shown but never warehoused — because the REST endpoint publishes no licence. The persistence boundary therefore refuses to store anything it returns unless the licence tier is deliberately raised. | Czech register facts. | Reached today |
| KRSapi-krs.ms.gov.pl | The KRS number being looked up.Also pass-through by default: an open-data label on the national portal is contradicted by the ministry's own re-use conditions, and where two sources disagree the guard takes the stricter one. | Polish register facts. | Reached today |
| EUIPOapi.euipo.europa.eu · euipo.europa.eu | The company name being searched, and an applicant identifier.Access is free but not self-service, so without a credential the adapter opens no socket. Facts are pass-through: EUIPO's copyright grant expressly excludes database rights. | EU trade-mark portfolio and holder-of-record assertions. | Off — opens no socket |
| USPTOtmsearch.uspto.gov | The company name being searched.Off by default: the only free route searchable by owner name is undocumented and unversioned. Facts are pass-through, because the public-domain statement is scoped to within the United States. | US trade-mark portfolio and holder-of-record assertions. | Off — opens no socket |
| MoltSetsapi.moltsets.com | The record's registrable domain, or its name.Wired and silent: the adapter no-ops without making a request when no credential is set, and this deployment sets none. It is listed because configuring a credential makes it a sub-processor that day. It is also a people database that answers company questions, so an operator who sets its credential has added one — only the company-shaped keys are ever read out of its responses, and its facts default to pass-through, but for that deployment 'we use no contact database' stops being true. | Commercial enrichment extras, ranked last. | Only with a credential |
| QuickEnrichapp.quickenrich.io | The record's registrable domain.Wired and silent on the same terms: no credential, no request. | Commercial enrichment extras, ranked last. | Only with a credential |
| PostgreSQLyour deployment | Everything stored. | The system of record. | Inside the deployment |
| Redisyour deployment | Tenant ids, caller IP addresses and token counts. Idle keys expire after 120 seconds by default. | Shared rate-limit buckets across replicas. Optional at one replica. | Inside the deployment |
Not on the list, No reseller. No analytics tag and no session-recording tag anywhere in the product. And, on a deployment holding no vendor credential, no data broker and no contact database either: enrichment then comes from public registers and from cited web research rather than from a purchased corpus.
Egress, fetching and network controls
7 enforced in code 0 operational commitments every row names its mechanism and its limit
Egress, fetching and network controls
7 enforced in code 0 operational commitments every row names its mechanism and its limit
| Kind | Control, and its limit | Enforced by |
|---|---|---|
Capping a call at the scoring stage keeps it inside the deployment — a no-egress mode, enforced in code rather than configured: a capped record reaches no model provider and no registry, because one flag gates both the registry candidate pass and the enrichment gather. What the cap costs in automation is unmeasured. The continuous-integration figure of a fully abstaining run is the behaviour of a deliberately weak offline stand-in with no network, not evidence that the cap is free. | enforced in codeResolutionDepth.allows_egress is false for probabilistic alone. An orthogonal enrich flag turns registries off at any depth, on the request, on each batch item and on the MCP tool | |
The research agent uses the model provider's server-side web tools, so the search and the page fetch execute inside their network. We never open a connection to a researched third-party website, and there is no general-purpose outbound fetcher in the codebase to compromise or repoint at an address of an attacker's choosing. There is no server-side request forgery pivot here. The consequence in the other direction is that the provider sees the search queries and the fetched URLs as well as the prompt. If which companies a customer is looking at is itself sensitive, that signal reaches them. | enforced in coderesearch/agent.py, using the provider's own web_search and web_fetch tools | |
Fetched page content is treated as untrusted input: it is wrapped as data inside a marker the page cannot close for itself, findings are constrained to an allow-list from which registration identifiers are deliberately absent, and web observations rank below customer facts in the source precedence order. A poisoned page cannot issue instructions or force a merge. It can still supply plausible false content that nudges a genuinely borderline verdict, and the bound on that is abstention: below the confidence floor nothing is bound at all, so a nudged borderline case lands in human review rather than in an automatic merge. | enforced in code_fenced in research/agent.py; SOURCE_PRECEDENCE | |
The set of hosts this application can reach is closed. Every register base URL is a constant in the source rather than caller input, and there is exactly one model-provider SDK in the dependency manifest — so no request body, no field mapping and no context string can make the deployment call an address that is not in the table below. Server-side request forgery, SSRF, has nowhere to land here: there is no general-purpose fetcher to repoint, precisely because the open web is fetched by the model provider rather than by us. Configuring a credential for a wired-but-silent vendor adds a host to that closed set, which is why the table below marks those rows conditional rather than absent. | enforced in codeBase URLs as module constants; one provider SDK in pyproject.toml | |
Outbound calls to public registers run under a per-host budget with per-host overrides and a concurrency cap, both binding while the upstream is perfectly healthy. Over budget, the call is shed — dropped rather than queued — and spend is counted per host. | enforced in codePer-host request budget; shed as SourceUnavailableError and counted in source_budget_total | |
Outbound calls verify TLS certificates and verification is never disabled. Registry credentials travel in headers rather than in URLs, and are never logged. Inbound TLS is assumed rather than provided: it is terminated at the ingress proxy in front of the application, and TLS to the database is a deployment setting rather than something this code requires. | enforced in codehttpx defaults, never overridden | |
The sub-processor list below is kept honest by a build-failing test that reads the request URL off every wired adapter and fails continuous integration if a host is missing from the published table. It was written after the table went stale once: a French register shipped on by default and undisclosed, which put a wrong annex in front of a buyer. The test dates from that incident rather than from anticipating one. | enforced in codetests/unit/test_subprocessor_disclosure.py, added 2026-08-24 |
Most of the hosts on this table can be switched off without a deploy, so a policy that forbids one is a configuration question rather than a dead end — and we will go through your list row by row.
Start free4 boundaries, each with its strongest control and its residue
A reviewer who wants the document asks for it and gets it. What is on the page is the shape of the exercise: which boundaries were modelled, the strongest control on each, and what is left over.
01The caller reaching the API
The caller reaching the API
Tenant scoping required at every persistence call, a uniform 404 on a wrong-tenant read, rate limits applied post-auth in per-tenant buckets so rotating credentials cannot multiply one customer's allowance, and a looser per-peer valve before credential lookup so an unauthenticated flood costs no database round trip.
Isolation is application-enforced. The internal workbench sits outside the guarantee by design and must not be internet-reachable.
02The agent or connector reaching the MCP surface
The agent or connector reaching the MCP surface
The tenant is derived per call from that call's own credential and absence fails closed; the surface refuses an admin credential outright; the client must prove it started the authorisation by producing the secret whose hash it published at the request, PKCE with S256, and the weaker plain method is refused; redirect URIs match by exact string equality; authorization codes and refresh tokens work once and a second use revokes the whole grant.
Consent authorises a tenant, not a person. The initialize and tools/list calls are unauthenticated, so the tool catalogue is readable by anyone who can reach the listener — no customer data is.
03The deployment reaching a public register or the model provider
The deployment reaching a public register or the model provider
A closed set of hosts that are constants in the source rather than caller input, a per-host egress budget binding while the upstream is healthy, verified outbound TLS that is never disabled, and no general-purpose outbound fetcher in the codebase at all — the open web is fetched by the model provider, not by us.
The provider sees the search queries and fetched URLs as well as the prompt, and the adjudication payload is derived from the record's populated fields rather than from a fixed whitelist, so a newly added field will reach them too.
04The application reaching its own store
The application reaching its own store
Parameterised queries only, through the ORM, with no string-built SQL anywhere; a runtime role that can change rows in application tables but cannot alter the schema, DML only and no DDL; migrations under a separate role; an append-only decisions table; a non-root container; and a licence guard that raises before any write rather than after one.
One database, one schema, no row-level security. Encryption at rest belongs to the volume underneath.
Integrity, logging and change controls
7 enforced in code 2 operational commitments every row names its mechanism and its limit
Integrity, logging and change controls
7 enforced in code 2 operational commitments every row names its mechanism and its limit
| Kind | Control, and its limit | Enforced by |
|---|---|---|
The adjudicator's output is constrained to one verdict schema and the decision policy abstains below a confidence floor. The model can recommend and can never commit. | enforced in codeAdjudicationVerdict schema; DecisionPolicy.decide_verdict | |
Every fact carries a licence tier, and the persistence boundary refuses to store a pass-through one — a fact that may be shown but not warehoused — raising before any write so a violation cannot partially persist. Pass-through evidence is stripped from the serialised decision while the in-memory object keeps it, and the step that picks one winning value per field, survivorship, drops those facts before a golden record is assembled. The guard is real, tested and unexercised by traffic. Every wired keyless source declares itself open, and the two metered vendors make no request at all without a credential. It is a boundary built before the vendor rather than a filter currently catching violations. | enforced in codeLicenceViolationError raised in SqlEntityRepository.save(); ADR-0004 | |
The decisions table is append-only in application code; the runtime database role can insert, update and delete rows in application tables but cannot alter the schema — DML only, no DDL; migrations run under a separate, more privileged role; and the container runs as a non-root user. | enforced in codeSQLAlchemy 2.0 ORM with parameterised queries only and no string-built SQL anywhere; schema changes only through migrations | |
A decision receipt returns the features, the thresholds, the evidence, the model id and the prompt version, plus a re-run of the score from the stored features, so the number can be checked rather than believed. A decision that no scorer decided — an identifier rule, a replay, a mint with no candidate — returns an empty reproduction rather than a fabricated one. The empty field is itself a statement about how that decision was reached. | enforced in codeGET /v1/decisions/{id} | |
A settled human verdict writes the record's content fingerprint stamped as a human-review decision, so a merge a person approved stays attributed to that person however many times the record comes back, and can never migrate into the automated population the precision figures are measured over. The binding is keyed by exact content, not by company. One corrected postcode or a trailing space is a different fingerprint and can queue for review again. A settled verdict stops the same bytes re-queueing; nothing in the system stops the same company re-queueing. | enforced in coderesolution_fingerprints stamped DecisionStage.HUMAN_REVIEW, migration 0007 | |
Logs are structured JSON carrying correlation ids and record, entity and decision identifiers, and never names, emails, phone numbers or addresses. Error responses are generic envelopes keyed by stable machine codes. | enforced in codeEH-OBS-02 and EH-SEC-09 | |
Every change runs lint, the full test suite, a dependency audit, static analysis, secret scanning and an accuracy gate before it can merge. No secret has a default in configuration: secrets come from the environment or a secret store only. | enforced in code.github/workflows/ci.yml — pip-audit, bandit, gitleaks, and the accuracy gate | |
Backups are a nightly database dump with restores tested quarterly: at most 24 hours of writes lost, which is the recovery point objective, and at most four hours to restore, which is the recovery time objective. These are bootstrap objectives accepted deliberately by a single operator, not enterprise ones, and they are not a service level agreement. If your continuity standard sets tighter numbers, say so on the first call rather than after signature. | operational commitmentFormally accepted by the founder as EH-GOV-02 | |
Encryption at rest is a property of the volume and the database underneath the application. Nothing here encrypts application-side and there is no field-level encryption. The control belongs to whoever runs the volume and the database, so the claim never appears here without that qualifier. | operational commitmentDeployment-level, and named as such rather than claimed |
10 things that are not done
The threat model's own live risks, plus the assurance work a regulated buyer will look for and not find. Some carry a date. The rest carry a trigger, and the trigger is what is published.
A DPA and SCC package ready to sign
Before a regulated EU buyer can complete a review
Not drafted
A DPA and SCC package ready to sign
Before a regulated EU buyer can complete a review
A documented data processing agreement, standard contractual clauses and a zero-data-retention addendum with the model provider are all prerequisites for a regulated EU buyer, and none of the three exists yet. Ask for one today and there is nothing to send.
A documented data protection position
Before a regulated buyer's data protection review
Not written
A documented data protection position
Before a regulated buyer's data protection review
The published position covers what the code does: the erasure and portability endpoints, the retention reasoning, licence handling, tenant scoping and the sub-processor list. It stops there. A lawful basis, a controller or processor determination, a data protection impact assessment, a record of processing activities and an international transfer mechanism are not documented anywhere.
Third-party penetration test and a bug bounty
No date set
Neither exists
Third-party penetration test and a bug bounty
No date set
The security review behind this page is our own: internal, adversarial, and written by the people who built the system. Nobody outside this repository has been paid or invited to break it, and an internal threat model is not a substitute for that.
Residual prompt-injection risk on adjudication and research
Review date 26 October 2026
Open, bounded
Residual prompt-injection risk on adjudication and research
Review date 26 October 2026
Mitigated in depth — schema-constrained verdicts, an enrichment allow-list, abstention below a confidence floor, a policy biased to under-merge, content fenced as data, and web facts ranked below customer facts. The residue is that a poisoned page can supply plausible false content that nudges a genuinely borderline verdict. It is bounded by abstention: below the confidence floor nothing is bound at all, so a nudged borderline case lands in human review, not in an automatic merge.
Rate limits shared across replicas
Before the first multi-instance deployment
Mitigation available
Rate limits shared across replicas
Before the first multi-instance deployment
The counters sit behind a store interface and can be shared across replicas through Redis, refilled atomically. Left unset, they degrade to per-instance counters and say so at startup. Because the limits are published in the documentation and in response headers, running multiple instances without the shared store breaks a published contract rather than approximating it.
Per-user identity on an OAuth install
Before OAuth is offered for per-user installs
Open
Per-user identity on an OAuth install
Before OAuth is offered for per-user installs
Consent authorises a tenant, not a person. The consent step carries no end-user identity — no user, password, session or upstream identity provider takes part in it — so the resource owner proves themselves with a single-use, minutes-long install ticket a tenant administrator minted. Anyone who obtains one authorises as that tenant, and the audit trail records which admin credential minted the ticket rather than who used it. Workable for one engineer installing a connector for their own organisation; not acceptable for a multi-user consumer install.
Expired OAuth rows are never reaped
Before the first production OAuth install
Open, bounded
Expired OAuth rows are never reaped
Before the first production OAuth install
Authorization requests, install tickets and tokens all filter expiry in SQL, so nothing expired ever authenticates. This is unbounded table growth rather than an authorisation gap, and there is no purge command for it yet.
Usage metering is best-effort
Before billing enforcement is switched on
Accepted while billing is shadowed
Usage metering is best-effort
Before billing enforcement is switched on
The usage ledger swallows storage errors and a period read fails open to zero, so a ledger outage under-counts rather than failing a customer's request. Both paths log at error level so the gap shows up in reconciliation. Acceptable while usage metering runs in shadow mode — the ledger records what a call would cost and the ledger does not create automatic charges — and it has to be revisited before a row is chargeable.
Per-tenant workbench grants and identity mapping
No date set
Infrastructure work
Per-tenant workbench grants and identity mapping
No date set
The internal workbench takes a locally issued operator credential behind a single sign-on (SSO) proxy. There are no per-tenant grants, and a shared proxy credential produces a shared audit identity rather than a per-person one.
OAuth discovery metadata on the MCP surface
Before broad connector distribution
Not published
OAuth discovery metadata on the MCP surface
Before broad connector distribution
The MCP surface publishes no metadata document telling a connector where to authenticate — the RFC 9728 one — and issues no 401 challenge, so a connector that discovers its authorization server from a challenge header cannot install against it. One that falls back to origin-level discovery can, provided the MCP and HTTP surfaces share a hostname. Separately, the initialize and tools/list calls are unauthenticated: anyone who can reach the listener can read the tool catalogue, though no customer data is reachable that way.
Responsible disclosure. Found a defect in the API, the MCP surface or this site? Email security@entitybind.com and we will acknowledge within two working days. We will not pursue legal action for good-faith research. There is no bug bounty, and we will thank you in public if you would like us to.
If your review needs something this page does not carry
Ask, and you will get one of two answers: what it is, or that it does not exist. The assurance address is answered by the person who wrote the threat model. A question about what we hold on a specific record goes to the data protection address and is answered inside a month, usually inside a week. If a certification is a hard gate, the answer is in the status register at the top of this page and a call will not change it.
Security and assurance: The assurance pack, the sub-processor list, and a straight answer on the paperwork that does not exist yet — same week, usually same day.
Or skip the questionnaire: send a file, and the receipts it comes back with are the evidence this page describes.
Talk to usA question this page did not answer
Prefer to put it to a person? Talk to us instead →
Answers here are generated from the pages on this site. They carry no date and they are not a contractual statement of our security posture. The mechanisms named above are.
Start with your own company records.
Print this page for the vendor file, then create your Free workspace to inspect a company CSV of your own. Every decision carries a receipt that reopens at a URL and reproduces its own score from the stored features — the evidence this page describes, checked against your own records. Contact us for an assisted review. We write nothing back to your systems.
300 credits. No card.
For a guided review of your file, explore the Merge Safety Sprint →
Questions a security review asks first
Do you have SOC 2?
No. Not SOC 2 Type I, not Type II, not ISO 27001. It has not been started, no auditor is engaged and there is no target date. There is also no third-party penetration test and no bug bounty; the security review is internal and adversarial, but it is our own. If your procurement floor requires any of those, you cannot buy this today, and it is worth establishing that on the first call. What does exist is a trust document naming the file, test or architectural decision that enforces every claim it makes, so each control can be checked rather than taken on trust. The gap between that and SOC 2 is the independent attestation that the controls operated over a period.
How is our data isolated from another customer's?
By application-enforced tenant scoping, and the exact shape of that matters. Every table carries a tenant id; every persistence method requires it as a keyword argument with no default, so a forgotten argument raises rather than silently reading a shared tenant; reads scope in SQL before retrieval rather than filtering after it; and the replay cache is keyed on a composite primary key, so byte-identical content from two customers resolves to two separate entities. A wrong-tenant read is a 404, never a 403. What it is not is database-enforced: one database, one schema, one runtime role, no row-level security, so a SQL-injection defect would cross every tenant at once. The internal workbench sits outside that guarantee.
What happens when we ask you to delete something?
Erasure runs per entity or per record, in one transaction across eight targets: the entity's facts, the keys that index it for matching, every submitted record it holds, open review items embedding one of those records, settled review items redacted rather than deleted, the replay fingerprints, cached responses quoting it, and the entity row. There is deliberately no bulk delete and no "delete my tenant" button, because a single call that empties a tenant turns one mistyped id into an unrecoverable loss. One limit to read before you rely on it: the decision audit trail survives, and a surviving decision row can carry cited evidence and an adjudication rationale naming the company. It expires on the retention window rather than on request.
How long do you keep data?
Four separate retention windows, each its own command — decisions, records and usage default to 2,555 days, and open review items to 365. Seven years is chosen to clear the money laundering regulations' five-year duty to retain the evidence behind a customer due-diligence decision; below about five years, a know-your-business customer loses what they are legally required to produce. The windows are environment variables you can change: set record retention to 90 days and you have 90-day retention. One caveat outranks the rest, and our own trust document states it first: nothing purges itself. Every window is a scheduled job an operator has to run, and an unscheduled deployment retains everything for ever.
Does our data go to an LLM provider?
Yes, on the records that reach adjudication or research, and it is worth being exact about what travels. The adjudication prompt renders the candidate record's populated fields — including the submitted address — plus the free-text context and the entries of the `extra` map, so a CRM column mapped into `extra` reaches the model verbatim. The research agent uses the provider's own server-side web tools, so search queries and fetched URLs reach them too; if "which companies is this customer looking at" is sensitive, that signal reaches them. Two things do not exist and should not be assumed: a zero-data-retention addendum, and any code-level enforcement of the provider's not-used-for-training term, which is contractual. Setting `depth=probabilistic` sends nothing at all.
Who are your sub-processors?
One model provider and a set of public registers, published as a table and kept honest by a build-failing test that reads the request URL off every wired adapter and fails continuous integration if a host is missing from the list. The registers are Companies House and its document service, GLEIF, SEC EDGAR, INSEE SIRENE via the French state search service, Brønnøysund, plus wired adapters for Finland, Czechia, Poland, EUIPO and USPTO. There is no reseller, and no analytics or session-recording tag anywhere in the product. One disclosure a reviewer needs before mapping a country column: the French service is on by default and needs no credential, so a record stating no country will have its name sent there unless you disable it.
Where does it run, and can we run it ourselves?
The artefact is one container plus a database, configured entirely by environment variables, and it runs wherever it is run — including a deployment we run for a customer. There is no managed multi-region offering and no region to pick on a form, so data residency here is a conversation about where that deployment sits rather than a checkbox somebody ticks. One thing not to infer from that: the sub-processor calls do not move with it. A deployment sitting in the EU still reaches a US-hosted model provider on the records that escalate, US federal hosts for SEC filings, UK and global hosts for Companies House and GLEIF, and a French state service for SIRENE. The only setting that sends nothing anywhere is the probabilistic depth cap.
Can you sign a DPA?
Not one that is already drafted and waiting, so there is no request-our-DPA button to press. A documented data processing agreement, standard contractual clauses and a zero-data-retention addendum with the model provider are all prerequisites for a regulated EU buyer, and all three are outstanding. The published position covers what the code does — the erasure and portability endpoints, the retention reasoning, the licence handling, tenant scoping and the sub-processor list — and stops there rather than asserting a lawful basis, a controller or processor determination, or a transfer mechanism it has not documented. Send the questions to our security address and you will get a straight answer, usually the same week.
Can you prove you deleted something?
Yes, for an engagement-scoped erasure. The maintenance command signs its receipt with a public-key signature, Ed25519, as well as an HMAC, so your own auditor can verify it against a published public key rather than take our word for it, and outside development the command refuses to run without a signing key. What is not on offer is automatic expiry: nothing deletes itself on a timer, so the claim is "we erase on request and hand you proof anyone can check" rather than "your data expires automatically". One limit: a redacted settled review item is still a row recording that an abstention happened and how it was settled. If your erasure duty extends to no trace at all, we do not meet it.