Scope
What the agent may touch: identifiers, subdomains, and the in-scope and out-of-scope lists.
An engagement carries three lists of identifiers: its targets, its in-scope list and its out-of-scope list.
A target is a host, address range or application named in the scope of one run. An asset is something your organization owns and has proven it owns, registered once and reused across runs. Targets and in-scope entries have to be covered by an asset; out-of-scope entries do not, because excluding something is not a claim to own it.
Identifier kinds¶
Four kinds exist. The same four are used for assets and for scope entries.
| Kind | Written as | Stored as |
|---|---|---|
domain |
Example.COM. |
example.com |
ip |
10.0.0.5 |
the canonical form of the address, v4 or v6 |
cidr |
10.0.0.5/24 |
10.0.0.0/24 |
url |
https://app.example.com:443/login/?next=%2Fa#top |
https://app.example.com/login |
Domains are lowercased, stripped of a trailing dot and IDNA-encoded. A domain
must contain a dot, and its last label must not be all digits — 1.2.3.4 is an
address, not a name. A CIDR keeps its prefix length and drops its host bits, so
10.0.0.5/24 and 10.0.0.0/24 are the same asset. A URL must be http or
https, and only its scheme, host, non-default port and path survive: the
default port for the scheme, a trailing slash, the query string and the fragment
are all dropped. Two URLs differing only in their query are the same asset.
When a bare string arrives in a target or scope list, its kind is inferred:
anything containing :// is a URL, anything else containing / is a CIDR,
anything that parses as an address is an IP, and everything else is treated as a
domain. Registering an asset is different — there you name the kind, and the API
rejects any kind outside those four.
Subdomains¶
Two separate mechanisms answer to the word "subdomain".
Ownership coverage is the include_subdomains flag on a domain asset. With
it set, the asset covers any name beneath it: the launch check walks a target's
ancestors and accepts a domain asset that matches one of them and carries the
flag. Without it, the asset covers its exact value and nothing else. The flag is
forced off for ip, cidr and url assets.
Run scope is the in-scope list, matched inside the container by shell-style
glob. example.com matches example.com alone; *.example.com matches
sub.example.com and not the bare domain. To reach both, list both. The
matching rules are on
Rules of engagement.
A domain asset with include_subdomains authorizes you to name a subdomain. It
does not put that subdomain in scope.
Address ranges¶
A cidr asset covers an ip target that falls inside it, and a cidr target
that is a subnet of it. Both v4 and v6 ranges are accepted and stored.
Two limits apply past that point. No automated verifier applies to a cidr
asset, so the only way to prove one is an owner attestation — see
Ownership verification. And the scope
guard inside the container reads in-scope entries as IPv4 networks only: an IPv6
range on the in-scope list is not recognised as a range, falls through to the
hostname list, and matches no address the agent might target.
How the two lists combine¶
Out-of-scope is checked before in-scope, for addresses and for names alike, so an excluded target is refused even where an in-scope entry would otherwise cover it.
The lists are not matched the same way:
- In-scope names are matched by glob, so
*.example.comworks. - Out-of-scope names are matched by exact string equality. A wildcard on the out-of-scope list excludes nothing. Write each excluded host out in full.
- In-scope addresses are read as networks. Out-of-scope addresses may be a
single address or a network — and a single out-of-scope address inside an
in-scope range is read as its own
/32, a subnet of that range, so it is treated as an overlap and aborts the run rather than narrowing the range. Only names narrow an in-scope entry, and only when written out in full.
Overlap between the lists¶
Overlap is detected once, in the container, when the rules-of-engagement document is rendered in the first phase. Two shapes are detected: an out-of-scope network that is a subnet of an in-scope network or the reverse, and a domain string that appears verbatim on both lists.
Nothing checks for it at submit time. The engagement is queued, waits its turn,
and gets a container; the render then raises every time the first phase is
attempted, and the run aborts after five consecutive failures without ever
leaving pre-engagement. That costs a queue slot and a container. The run still
ends completed and is sealed, over an empty chain with no findings and no
artifacts — see Sealing. Fix the lists and create a
new engagement; a completed engagement cannot be submitted again.
Because the domain check is an exact-string comparison, *.example.com in
scope and secret.example.com out of scope is not an overlap, and the run
starts.
A wildcard in scope does not stop at the host you excluded
In-scope names are matched with fnmatch, whose * crosses dots.
Out-of-scope names are matched by exact string. With *.example.com in
scope and secret.example.com out of scope, secret.example.com itself is
refused — and a.secret.example.com matches the wildcard, does not equal
the exclusion, and is allowed.
An exclusion removes the exact names you write and nothing beneath them. To keep a subtree out, either list every host in it on the out-of-scope list or narrow the in-scope entries so the wildcard never reaches them.
Targets¶
A target is submitted as a host plus a type. The type is discarded: the
engagement record stores hosts only, and the type the agent is eventually told
comes from the kind of the asset that covered that target at launch. A target
spelled Example.COM is normalised before it is matched against your inventory,
and the raw spelling is what reaches the agent.
parent_asset_id on an asset records a relationship between two assets you
registered. It is validated to name an asset in your own inventory and returned
in the API response; it plays no part in coverage.
Authenticated testing¶
Pentr has no way to pass credentials to the agent. The engagement create schema
accepts a credentials list and the agent's config model has a field for it, but
the server never reads the one or fills the other: the engagement record drops
the list, the job payload leaves the credential slot empty, and the environment
variable the container would read is never set. Section 6 of every
rules-of-engagement document reads "No credentials provided — black-box testing
only".
Runs are unauthenticated. Anything behind a login is out of the agent's reach unless it can get in by attacking the login itself.
Last updated 2026-09-24