| English | 中文版 |
RFC Number: NPS-RFC-0003
Title: Three-tier Agent identity assurance levels for anti-scraping / trust gating
Status: Accepted (Phase 1 — spec + .NET reference types landed)
Author(s): Ori Lynn iamzerolin@gmail.com (LabAcacia)
Shepherd: Ori Lynn (pre-1.0 fast-track per spec/cr/README.md)
Created: 2026-04-21
Last-Updated: 2026-04-25
Accepted: 2026-04-25 (pre-1.0 fast-track; see spec/cr/README.md)
Activated: (set when first reference SDK ships, target v1.0-alpha.3)
Supersedes: none
Superseded-By: none
Affected Specs: NPS-3 NIP, NPS-2 NWP, spec/services/NPS-AaaS-Profile.md, spec/error-codes.md, spec/status-codes.md
Affected SDKs: .NET, Python, TypeScript, Java, Rust, Go
—
Introduce three assurance levels for Agent identities — anonymous
(L0), attested (L1), verified (L2) — and let Nodes declare a
min_assurance_level in their NWM. The assurance level travels in
IdentFrame and is carried as a critical X.509 extension (see
NPS-RFC-0002 §4.1). This is NPS’s answer to “how do I know a request
is from an Agent, not a scraper?” — the protocol doesn’t block
scrapers, but it gives Nodes a primitive to demand verifiable Agent
provenance and to charge / rate-limit / refuse accordingly.
This RFC responds to a 2026-04-20 review comment:
最核心的,如果我要接这套协议,最关心的反而是怎么做反爬?我怎么 知道来个请求不是爬虫而是 Agent 访问?
(Most critically — if I adopt this protocol, my biggest concern is anti-scraping. How do I know a request is an Agent and not a scraper?)
The commenter is right that NPS today answers “who is this Agent?” (NIP) but not “how much do I trust this Agent’s identity?” An NID is just a public key — anyone, including a scraper farm, can mint one and sign requests with it. Without a way to rank identities by trustworthiness, Nodes either accept anonymous traffic (scraper-friendly) or hand-maintain allowlists (operational burden).
The X.509 PKI world solved the same class of problem with EV / OV / DV certificates and the CA/B Forum baseline. The identity-proofing world solved it with NIST SP 800-63 Identity Assurance Levels (IAL1/2/3). We adopt the tier shape, not the human-identity specifics.
The concrete business model this unlocks: an AaaS operator can price
L2-verified Agent traffic higher than L0, auditors can require L2
for regulated integrations, and victims of scraper abuse can set
min_assurance_level: attested on hot endpoints without breaking
legitimate Agents that upgrade their NIDs.
L0 traffic — the
level is a signal, not an enforcement decision by itself.| Level | Enum | Minimum criteria | Typical use |
|---|---|---|---|
anonymous |
0 | Self-signed NID; no CA involvement; or CA-signed without identity binding | Hobbyist Agents, dev/test, “show me a free quote” read-only endpoints |
attested |
1 | NID signed by an RFC-0002-compliant CA; CA attests possession of the NID private key (ACME agent-01); contact email / domain verified by CA |
Most production Agents; rate-limit tier default |
verified |
2 | L1 criteria plus CA attests the operator’s legal identity: corporate registration for org-NIDs, or signed attestation from a known AaaS operator for hosted Agents | Regulated integrations (finance, healthcare), paid premium tiers, high-trust orchestration |
What “attested” really buys a Node: the CA has verified that someone controls the NID private key and an out-of-band channel (ACME account email, domain, etc.), so if the Agent misbehaves the CA can at minimum email a human and at most revoke the cert. L0 offers neither.
What “verified” adds on top: the CA binds the NID to a legal entity. A Node can then issue an invoice, subpoena the operator, or make contract-grade claims about who it is interacting with.
IdentFrame gains one field:
| Field | Type | Required | Description |
|---|---|---|---|
assurance_level |
enum { anonymous, attested, verified } |
yes | When the NID’s cert carries the id-nid-assurance-level extension (NPS-RFC-0002 §4.1), this field MUST carry the same value. Phase gate: Phase 1–2 (current) — verifiers SHOULD check and log mismatches, but enforcement is opt-in. Enforcement becomes MUST and triggers NIP-ASSURANCE-MISMATCH starting Phase 3 (flag day — see §8.1). |
The cert is the source of truth; assurance_level in IdentFrame is
redundant convenience for servers that don’t want to parse the cert
just to route requests.
Phase 1–2 (current): Verifiers SHOULD check the cert extension and
log any mismatch, but MAY skip enforcement. Implementations that do
enforce MUST close the connection with NIP-ASSURANCE-MISMATCH.
Phase 3 (flag day, see §8.1): Verifiers MUST check the cert
extension and MUST close the connection with NIP-ASSURANCE-MISMATCH
if the two disagree. This is the hard deadline for completing the
upgrade.
X.509 extension promotion (Phase 3 — not yet active): NPS-RFC-0002
defines id-nid-assurance-level (1.3.6.1.4.1.<PEN>.2.1) as
non-critical. Starting Phase 3 flag day, this RFC promotes it to
critical — old verifiers MUST reject certs that carry it as critical
until they upgrade. That’s intentional: a Node that enforces
min_assurance_level MUST NOT silently accept a verifier that can’t
parse the extension. The promotion is NOT yet active in Phase 1–2.
NWM gains one optional top-level field:
# /.nwm excerpt
min_assurance_level: attested # default: anonymous
auth:
required_scopes: [...]
min_assurance_level: verified # per-action override (optional)
min_assurance_level applies to all read paths (/.schema,
/.actions, anchor fetches, synchronous /invoke).min_assurance_level under auth: overrides it.anonymous — this RFC does not change the default trust
posture; Nodes opt in.NWP-AUTH-ASSURANCE-TOO-LOW and status NPS-AUTH-FORBIDDEN,
and SHOULD include a hint pointing to an ACME enrollment URL.New entries in spec/error-codes.md:
| Error Code | NPS Status | Description |
|---|---|---|
NIP-ASSURANCE-MISMATCH |
NPS-CLIENT-BAD-FRAME |
IdentFrame.assurance_level disagrees with the cert extension |
NWP-AUTH-ASSURANCE-TOO-LOW |
NPS-AUTH-FORBIDDEN |
Request’s assurance level < Node’s min_assurance_level |
NIP-ASSURANCE-UNKNOWN |
NPS-CLIENT-BAD-FRAME |
Cert extension carries a value outside the defined enum |
Rejection path (Node enforces min_assurance_level: attested):
Agent (L0) Node
│ │
│── HelloFrame ─────────────────→ │
│── IdentFrame (assurance=0) ───→ │
│ │ check: 0 < 1 (attested)
│ ←── ErrorFrame ──────────────── │ code: NWP-AUTH-ASSURANCE-TOO-LOW
│ hint: "https://ca.../acme" │ status: NPS-AUTH-FORBIDDEN
│ ←── close │
Upgrade path is out-of-band: the Agent re-enrolls through an
RFC-0002-compliant CA, receives a new cert with id-nid-assurance-level
set to attested, and retries the connection.
min_assurance_level: anonymous keep working.min_assurance_level in NWM)? They accept everything
(default L0). New Agents presenting L2 certs still work.min_agent_version
bump and the 21-day window.Drop attested; only unverified and verified.
unverified since legal-entity binding is heavy for hobby /
dev use. Nodes lose the useful knob.Skip assurance levels; let Nodes rely on NPS-RFC-0004 reputation log instead.
Let CAs define their own policy OIDs à la X.509 certificatePolicies; Nodes enumerate accepted OIDs in NWM.
certificatePolicies remains usable for
finer-grained CA-specific policy on top of our three tiers, but
the three tiers are the common vocabulary.verified level concentrates power
in CAs that can attest legal identity. Mitigated by: (a) multiple
independent CAs supported (no single root); (b) per-Node NWM can
specify trusted_issuers list (RFC-0002 §3 non-goal confirms this
stays possible).trusted_issuers.IdentFrame.assurance_level=2
while the cert extension says 0. Mitigated: NIP-ASSURANCE-MISMATCH
closes the connection; cert is source of truth.trusted_issuers (per-Node)
and by RFC-0004 reputation tracking per-issuer.| Phase | Scope | Exit criterion |
|---|---|---|
| 1 | .NET NIP parses cert extension; IdentFrame carries field; NWM parses min_assurance_level; enforcement optional |
Unit tests green; the default behavior is unchanged |
| 2 | All 6 SDKs + 6 CA servers; CA servers issue L1 certs via ACME; L2 issuance flow documented per CA | Cross-SDK interop: L0/L1/L2 matrix green |
| 3 | min_assurance_level enforcement on by default when set in NWM; extension id-nid-assurance-level promoted to critical. Flag day: announced via NPS-Dev GitHub Discussions with ≥ 21 calendar days notice before the activation date; after activation, NIP-ASSURANCE-MISMATCH enforcement is MUST and non-compliant implementations MUST NOT claim any NPS conformance level |
No regressions; all 6 SDKs pass mismatch-enforcement tests |
| 4 | Remove L0-default fast path from Nodes that opted in to stricter defaults | N/A — operators decide |
| SDK | Owner | Status | Notes |
|---|---|---|---|
| .NET | Ori Lynn | pending | Reference impl |
| Python | TBD | pending | — |
| TypeScript | TBD | pending | — |
| Java | TBD | pending | — |
| Rust | TBD | pending | — |
| Go | TBD | pending | — |
min_assurance_level: attested Node → rejected
with NWP-AUTH-ASSURANCE-TOO-LOW.min_assurance_level: anonymous Node → accepted.NIP-ASSURANCE-MISMATCH.orders.create → L1 Agent
gets /invoke:orders.create rejected, other actions pass.NIP-ASSURANCE-UNKNOWN.None yet. Before Accepted, commit:
min_assurance_level: attested Node, confirming the L0
loop gets 403 early and never touches business logic.pebble-based test harness) issuing L1
certs via ACME agent-01.| Metric | Baseline | Proposed | Delta | Method |
|---|---|---|---|---|
| NWM size | no field | +~40 B | +~40 B | JSON byte count |
| Per-request overhead | not enforced | ~1 µs | +1 µs | BenchmarkDotNet |
tools/nip-ca-server/docs/policy.md. Owner: Ori Lynn.O, jurisdictionOfIncorporation)? Default position:
yes; deferred to CA policy document.X-NPS-Authed-Nid
header. Pending AaaS working-group sign-off.trusted_issuers well-known list for AaaS
profile.| Date | Author | Change |
|---|---|---|
| 2026-04-21 | Ori Lynn | Initial draft |
| 2026-04-25 | Ori Lynn | Accepted via pre-1.0 fast-track. Spec changes landed: NPS-3 §5.1.1 Assurance Levels + IdentFrame assurance_level field, NPS-2 NWM min_assurance_level field, error codes NIP-ASSURANCE-MISMATCH / NIP-ASSURANCE-UNKNOWN / NWP-AUTH-ASSURANCE-TOO-LOW. Phase 1 .NET reference types (NPS.NIP.AssuranceLevel enum, IdentFrame.AssuranceLevel, NipVerifyContext.MinAssuranceLevel, NeuralWebManifest.MinAssuranceLevel, related error/status code constants) landed alongside; active enforcement in the verifier remains opt-in per RFC §8.1 (Phase 1 = parse only, default unchanged). Phase 2 (other 5 SDKs + 6 CA Servers issuing L1 certs via ACME — depends on RFC-0002) deferred to v1.0-alpha.4. The X.509 critical-extension flip (§4.2) coordinates with RFC-0002 and is NOT yet active. AaaS-Profile §10 OQ-3 (Gateway enforcement vs backing-Node enforcement) deferred to CR-0001 follow-up. |