A container you run inside your own network to inventory the AI agents and MCP servers running there. It is read-only by default: verification only when you enable it, only against targets you nominated, defined in a config only you control. This page is what you are being asked to approve: what it does, what it cannot do, how to check the image is ours, and what we commit to as its supplier.
Generated from the source document, so this page and the one we work from are the same text. Contact security@agentcensus.io.
Status: Approved with the ADR-029 amendments of 2026-08-17 and 2026-08-25
(the opt-in verify mode, tier 1) · Owner: Ingmar
· Deputy: Nic · Contact: security@agentcensus.io · Review by:
2026-11-20 (first publish, 2026-08-22, + 90 days)
Written for the security team being asked to approve the connector, and kept short enough to be read by one. Every statement here is either enforced by the platform or has a named person behind it; where it is neither, it says so.
The commitments below are the ones that attach to the first signed image and to
every one after it. This file is the source of the public page at /connector,
which is generated from it at build — there is no second authored copy, so what
you read here and what you read there are the same text, and the connector's own
README.md is rendered onto the end of it.
Until the first image has been published there is nothing to pull. The release
list at /connector/releases.json is the answer to whether that has happened:
it names every version that exists, its digest, and whether it has been
withdrawn. An empty list means nothing has been published yet.
An egress-only container that runs inside your network, and is read-only by
default: verification only when you enable it, only against targets you
nominated, defined in a config only you control. It reads the hosts your own
administrator listed in its configuration file — explicit hosts, never address
ranges, and it derives no new hosts from what it finds — and on each of them
issues GET requests for a small set of agent-discovery documents: the
well-known paths for agent cards, catalogues and indexes, /openapi.json, and
/.well-known/mcp.json. It reads JSON up to 1 MiB and ignores everything else.
Once per run it makes one outbound HTTPS POST to the platform URL in its
configuration, authenticated with a credential it holds for itself alone.
Verification is off unless your configuration file has a verify block, and a
file without one runs the connector exactly as described above. Each entry in
that block names one of the hosts you already listed, one endpoint path on it,
and the protocol it speaks. For those endpoints only, the connector sends the
read-only first-tier checks, without a credential: the MCP initialize
handshake and its tools/list, resources/list and prompts/list calls, or
the A2A capabilities read. These are protocol requests rather than document
reads, and some are sent as POST, because that is how the protocol asks. The
results go to your organisation's private store, marked as observed from inside
your network, in a second outbound POST to the platform per run. They are
never written to the public census and never used in a score.
What it cannot do: it accepts no inbound connection and holds no port open; it takes no command from us, because its behaviour is fixed by the local configuration file you provide and there is no channel by which we — or anyone who compromised us — could turn verification on, add an endpoint to it, or tell it to do anything else; it never invokes a tool or submits a task, with or without verification, and being inside your network changes nothing about that; and it cannot read your data back out, because its credential authorises submission and resolves to no read scope at all.
The detailed statement is services/cmd/connector/README.md
(What it does / What it cannot do),
rendered into the same public page as this one.
Image. public.ecr.aws/q6v4u7f5/connector:<version>, on a public
registry. Anyone can pull it, whether or not they are a customer: a security
team cannot review a binary it cannot fetch. The paywall is enrollment, not the
pull (§9). The reference printed on this page is the canonical one — if a
registry alias elsewhere disagrees with it, this page is what to trust.
q6v4u7f5 is the AWS-assigned alias of the registry, and it is not a
placeholder: it is the name that resolves today, which is why it is the one
written here. A friendlier agentcensus alias has been requested from AWS and
is still under review. An alias is a name for a registry rather than a copy of
one, so when it is granted the same repository, the same tags and the same
signatures will also answer under it, and nothing published here will be
re-published or re-signed to make that true. Pin the digest if you would rather
not depend on either name.
Signature. Every published digest is signed with cosign, keyless, by the GitHub Actions workflow that published it. There is no signing key for us to lose or leak; the certificate binds the signature to that workflow in that repository, and the Rekor transparency log records that it happened. Verify with:
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity https://github.com/ingmarorg/darknetian-AD-AAS/.github/workflows/publish-connector.yml@refs/heads/main \
public.ecr.aws/q6v4u7f5/connector:<version>
The identity in that command is the publishing workflow. A signature that names any other identity is not ours.
SBOM and provenance. An SPDX SBOM and SLSA provenance are attached to the
image index as attestations, so docker buildx imagetools inspect and
cosign download attestation both find them. They are also written per
release to /connector/releases/<version>/ as sbom.spdx.json and
provenance.json, because reading an SBOM should not require OCI tooling.
Reproducibility. The binary is built CGO_ENABLED=0 -trimpath from a
tagged commit with the toolchain pinned in go.mod, with SOURCE_DATE_EPOCH
set to the commit time and both base images pinned by digest. The command to
reproduce it, and the binary's hash for each release, are published with the
release. The claim is measured before the first publish, not asserted:
confirmed. Two independent, uncached builds of the same commit — same
docker-container buildx driver build-images.yml uses, same
SOURCE_DATE_EPOCH, rewrite-timestamp=true on the image output — produced
byte-identical OCI content: matching manifest digest, config digest, and
blob set. The compiled binary alone was independently confirmed identical
too. rewrite-timestamp matters: without it, SOURCE_DATE_EPOCH normalises
the image config's timestamp but not the layers', and two otherwise-identical
builds land on different image digests despite an identical binary inside.
Source. git rev-parse connector/<version> gives the commit the digest
was built from, in the repository named in the signing certificate.
Nothing is rebuilt at publish time. The image promoted to the public registry is the digest CI built and scanned; the digest you verify is that digest.
Mail security@agentcensus.io. It is a forwarding mailbox that reaches a
person, not a queue (SECURITY.md,
ADR-016), and a report is acknowledged within
2 business days. Do not open a public issue; the repository is private and
a public report would be a disclosure.
Include what you observed, which version and digest you were running, and how to
reproduce it. What happens next: we triage, tell you what we found, and ship a
patched release under the timescale in §6. The finding is disclosed on
/connector once the fix is available, or after 90 days, whichever comes
first, and we will name you if you want to be named.
There is no bug bounty. We would rather say so than let you find out after the work.
The current minor and the previous minor are supported. A minor that is superseded stays supported for 90 days after its successor ships.
While the connector is on 0.x, a minor release may change the wire contract,
so the window is also a compatibility commitment: /api/v1/connectors/enroll,
/api/v1/connectors/reports and /api/v1/connectors/verifications keep
accepting a supported version's requests for as long as it is supported. "Supported" means that, plus security fixes.
It does not mean new features.
What happens outside the window. The connector identifies itself as
User-Agent: agentcensus-connector/<sha> — the commit SHA is the only version
the binary knows. The API resolves that SHA to its published version, records
both against the connector, and the console shows the version with "update
available". Past the window, every submission is answered with a warning for 30
days, and then refused with 426 connector_version_unsupported.
Refusal is the only lever we have, and it is worth being plain about what it does: we cannot reach into your network and change the binary, so the only thing we can stop is the data arriving. The connector keeps running; its reports stop being accepted.
There is no automatic update, and there will not be one. An updater is a command channel with a friendlier name, and the connector is scoped to have no command channel (ADR-029 §3).
How you find out a release exists: /connector and its releases.json, which
carry each version's digest and verify command; a line in the connector's own
log on start; and the console.
How you update: pull the new tag and restart the container. The enrolled credential and your configuration file carry over — you do not re-enroll, and the join token is not needed again. Compatibility across an update inside the support window is the commitment in §4.
The SBOM is watched by two things: govulncheck weekly, which is
reachability-aware, and ECR image scanning on the private build repository —
the same digest as the public copy, so a finding on one is a finding on both.
A critical or high finding that is reachable from the connector's own code paths gets a patched release within 14 days of an upstream fix being available. A finding that is not reachable is recorded on the release page and is not patched on demand; shipping a release to change a scanner's output is churn, not security. "Reachable" means govulncheck reports a call path to the vulnerable symbol, or the finding is in the base image on a path the binary uses; where the tooling is ambiguous, the owner named at the top of this page decides and the decision is recorded with the release.
A published, signed image cannot be unpublished quietly, and we will not
pretend otherwise by trying. A withdrawn version is marked as withdrawn on
/connector with the reason and the replacement to move to; its User-Agent is
refused at /api/v1/connectors/reports and /api/v1/connectors/verifications
from that day; and if the reason is a
vulnerability, it is mailed to the security@ notification list. The tag and
the signature stay where they are. The page says why nobody should run it.
internal_discovery entitlement — that entitlement is what a premium plan
buys, not the ability to pull the image.DELETE /api/v1/orgs/{slug}/discovery
— a hard delete of every private record, not a flag, and the one place this
platform deliberately breaks its append-only rule. Revoking a connector
deletes nothing, on purpose: losing a credential should not lose your
inventory. Per-connector erasure (?erase=true on a revoke, removing that
connector's records and any agent left with no other source) is decided and
specified, and lands with the primary-key change that makes a record
attributable to the connector that reported it; it is not available yet,
and this page will say so until it is. Until then the organisation-wide
erasure above is the one that exists, and it is complete.| Owner | Ingmar |
| Deputy | Nic |
| Contact | security@agentcensus.io |
| Review | this page is re-read and re-dated every 90 days, and on any change to §4 or §6 |
security@ reaches a person |
last verified 2026-09-02, by Ingmar |
The mailbox check is a test message answered by a human, done before the first image is published and at every review since. A contact address nobody has sent mail to is a promise nobody has tested.
The 2026-09-02 entry is that test, and it was the first message this address
had ever received — the mail bucket's inbound prefix had two objects before it,
both from 2026-08-09 and both to abuse@. SES accepted it with spam, virus,
SPF, DKIM and DMARC all PASS, archived it, and darknetian-mail-forwarder
forwarded it to the owner's mailbox. So the routing half of this row is measured
rather than asserted, and the log is in
/aws/lambda/darknetian-mail-forwarder. The half no log can show is the answer,
which is why the row names a person and not a pipeline.
A container you run inside your own network to find the AI agents and MCP servers running there, and report them — privately, to your organisation on agentcensus — so you have the same inventory of your internal agents that the public census gives you for the internet.
What it finds is private to your organisation. It never enters the public census, and the platform operator sees only aggregate counts, never the details, unless you approve a specific, time-boxed, revocable access request (ADR-029).
It is read-only by default: verification only when you enable it, only against targets you nominated, defined in a config only you control.
This document is written for the security team being asked to approve it. It describes exactly what the connector does, and — more importantly — what it cannot do.
GET
requests for a small set of agent-discovery documents
(/.well-known/agent.json, /.well-known/agent-card.json,
/.well-known/ai-catalog.json, /.well-known/agents-index.json,
/.well-known/mcp.json, /openapi.json). It reads JSON documents up to 1 MiB
and ignores everything else.User-Agent: agentcensus-connector/<commit>. A team
whose host was nominated can tell from their own access log exactly what read
them.POST per
run, authenticated with a per-connector credential — and a second one when
verification is on and had something to report.verify block does none of it.GET for a document. With verification on,
it also sends the read-only handshake and list calls to the endpoints you
named, and still never invokes a tool, submits a task, or sends a credential.
There is no setting that makes it do any of those. Being inside your network
changes nothing about that.302 is not a document, and following one is how
a host you nominated turns into a request somewhere you did not.| Command | What it does |
|---|---|
enroll --token <join token> --platform <https://…> |
Exchanges a single-use join token, once, for this connector's own credential; writes it to the state directory; exits. |
run [--config FILE] [--state DIR] |
The default. Reads the credential from state and the targets from config, discovers, submits. |
version |
Prints the commit the binary was built at. |
An admin in your organisation mints the join token in the console. It is
single-use and valid for 15 minutes, and it is spent the moment the connector
exchanges it. Supply it as --token, or as CONNECTOR_JOIN_TOKEN in the
environment — never in the config file, which holds nothing secret and can be
checked into your own configuration management as it is.
If run finds no stored credential and CONNECTOR_JOIN_TOKEN is set, it enrols
first. That is the Kubernetes case: a Secret mounted as an environment variable
and no init container.
You can see every enrolled connector in the console — where it enrolled from and when it last reported — and revoke any of them instantly. A revoked connector stops being able to submit on its next request. Revoking deletes nothing, on purpose: losing a credential should not lose your inventory.
A local JSON file (--config, default /etc/connector/config.json). Unknown
fields are refused, so a misconfigured connector fails to start rather than
running with a silently dropped setting. See config.example.json.
Nothing in it is secret. The join token and the credential's location both left this file deliberately: a bearer credential parked in a config file with a manual "remember to delete this" step is weaker than one that is consumed.
| Field | Meaning |
|---|---|
platform_url |
Where reports are submitted. Must be https. |
targets |
The exact https://host[:port] targets to read. Explicit hosts only — no ranges, no paths. |
well_known_paths |
Which documents to read (defaults to the agent-discovery set above). |
interval_hours |
Re-discover every N hours. 0 (default) runs once and exits. |
verify |
Optional, and absent by default. The endpoints to verify; see below. |
Discovery reads the documents an agent publishes. Verification checks that the
endpoint a document names answers the way the document says it does. It runs
only when the config has a verify block:
"verify": [
{ "target": "https://billing.corp.internal", "endpoint_path": "/mcp",
"protocol": "mcp", "tiers": ["tier1"] }
]
target must be one of targets. Verification cannot reach a host that
discovery was not already allowed to read. A target that is not in the list
stops the connector at startup.endpoint_path is a path only. The request always goes to the nominated
target's own scheme, host and port. It follows no redirect, and it will not
connect to a link-local, multicast or unspecified address, even when a name
you nominated resolves to one. That rule is what keeps a cloud metadata
address out of reach.protocol is mcp or a2a.tiers is ["tier1"], and this version knows no other tier. For MCP, the
first tier is the initialize handshake followed by tools/list,
resources/list and prompts/list. For A2A it is the capabilities read. They
are protocol requests, and some are sent as POST because that is how the
protocol asks. They are sent without a credential and invoke nothing. A config
that asks for a tier this binary does not have is refused at startup rather
than ignored.What it records for each call is the outcome, the status, timings, the certificate's expiry, the negotiated version, the names of the capabilities the endpoint listed, and whether it refused an unauthenticated caller. It records no tool output, because it calls no tool. The results go to your organisation's private store, marked as observed from inside your network. They never reach the public census or a published figure, and they are never used in a score.
Only this file turns verification on. The platform cannot turn it on, add an endpoint to it, or widen a tier, because there is no channel for it to do so.
# Once, with the token from the console.
docker run --rm \
-v agentcensus-connector:/etc/connector/state \
<image> enroll --token <join token> --platform https://agentcensus.io
# Then, on a schedule or as a long-running container.
docker run --rm \
-v /etc/connector:/etc/connector:ro \
-v agentcensus-connector:/etc/connector/state \
<image> run
Two mounts, and the split matters: the config mount is read-only and the state
directory is not. The connector writes exactly one file, its credential, into
/etc/connector/state. Mounting that path read-only makes enrollment fail — and
the connector now says so and refuses before it spends your join token, rather
than after.
It needs egress to the targets you nominated and to platform_url, and nothing
else.
The binary knows one thing about itself: the commit it was built at, which is
what version prints and what its User-Agent carries. The release it was
published as is the image tag, and the platform resolves the commit to that
version from the published release list — so the console can tell you which
release you are running and whether a newer one exists.
The support window, the vulnerability contact, how to verify the image, and what
happens to a version that falls out of the window are all in the supplier
commitments, published at /connector.