Inspect incoming agents experimental · 0.x

@nessgate/inspect describes what an incoming automated caller claims and what can actually be verified. It reports evidence only — no scores, no allow/deny, no identity guarantees.

Agentsends a request
→
Your serverreceives it
→
@nessgate/inspectreads the request
→
Evidenceto your application
The hosted NessGate site cannot remotely observe agents visiting your website. @nessgate/inspect evaluates requests where it is running, so to inspect traffic on your domain, run it in your own request-handling layer — where the source IP, headers, and any signatures are available.

Run it locally

Install the package:

npm i @nessgate/inspect

A tiny server that reports evidence about each request it receives:

// inspect-server.mjs
import { createServer } from "node:http";
import { inspect } from "@nessgate/inspect";

createServer(async (req, res) => {
  const headers = Object.fromEntries(Object.entries(req.headers));
  const request = { method: req.method, url: `http://${headers.host}${req.url}`, headers };
  const evidence = await inspect(request, { sourceIp: req.socket.remoteAddress }); // the real connection peer
  res.writeHead(200, { "content-type": "application/json" });
  res.end(JSON.stringify(evidence, null, 2));
}).listen(8799);

Start it, then send it a request:

node inspect-server.mjs
curl -s localhost:8799/ -H 'user-agent: Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)'

Already using Express? Two lines — every handler then has req.nessgate:

import { nessgateInspect } from "@nessgate/inspect/middleware";
app.use(nessgateInspect());
Behind a proxy or CDN? Network verification depends on the real connection source IP. If your application sits behind a reverse proxy or CDN, pass the source IP only from a trusted platform integration (for example, the platform's documented client-IP field). Do not blindly trust client-supplied forwarding headers such as X-Forwarded-For.

A request from your own machine or a cloud function will show claimed (and directory-attributed if you send a known bot User-Agent). The stronger tiers appear only for genuine operator traffic or a signed request — that is the correct, honest result.

Evidence levels

Every fact is placed in exactly one tier and carries provenance. The examples below are real output from the package, labelled with the exact input that produced each one.

claimed  User-Agent

The caller's User-Agent, asserted in a request header. It is trivially spoofable and bound to nothing, so on its own it is not evidence of identity.

Worked example — input: a request with User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36.

{
  "kind": "user-agent",
  "tier": "claimed",
  "statement": "the caller's User-Agent is \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36\"",
  "value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
  "provenance": [
    {
      "source": "request-header",
      "header": "user-agent"
    }
  ],
  "note": "a request header asserted by the caller; trivially spoofable and bound to nothing — not evidence of identity on its own."
}

directory-attributed  public attribution

The declared User-Agent matches a published operator pattern (here, OpenAI's GPTBot). This shows the string is a recognized pattern; it does not bind this request to that operator, because the header can be spoofed.

Worked example — input: a request with User-Agent: Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot).

{
  "kind": "public-attribution",
  "tier": "directory-attributed",
  "statement": "the declared User-Agent matches a published pattern operated by OpenAI",
  "operator": "OpenAI",
  "provenance": [
    {
      "source": "request-header",
      "header": "user-agent"
    },
    {
      "source": "public-agent-directory",
      "pattern": "GPTBot",
      "documentation": "https://platform.openai.com/docs/bots"
    }
  ],
  "note": "attribution of a self-declared, spoofable string against a public directory — it does NOT bind this request to that operator. Corroborate with a verified signature or verified reverse-DNS for binding."
}

network-verified  origin infrastructure

The connection matches the operator's published network-verification method (here, Google's reverse-DNS forward-confirmation) and supports attribution to its documented infrastructure. It does not establish trust, authorization, or safety.

Worked example — input: a request whose source IP 66.249.66.1 reverse-resolves to a Google crawler host and forward-confirms, as Google's documented method requires. Methods are wired today for Google, Bing, Apple, OpenAI and Perplexity.

{
  "kind": "network-attribution",
  "tier": "network-verified",
  "statement": "the source IP 66.249.66.1 is within infrastructure Google officially attributes to its bot (method: rdns-forward-confirm)",
  "operator": "Google",
  "method": "rdns-forward-confirm",
  "provenance": [
    {
      "source": "connection",
      "sourceIp": "66.249.66.1",
      "note": "the real connection peer; forwarded headers were NOT trusted"
    },
    {
      "source": "reverse-dns-forward-confirmed",
      "hostname": "crawl-66-249-66-1.googlebot.com",
      "documentation": "https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot"
    }
  ],
  "note": "means ONLY that this request originated from Google's documented infrastructure — it does NOT mean the caller is trusted, authorized, safe, or allowed. That decision is the relying party's."
}

cryptographically-verified  Web Bot Auth signature

A valid signature establishes that the holder of the referenced key signed this request. It does not by itself establish the real-world operator, authorization, trust, or safety.

Worked example — input: a request carrying a valid RFC 9421 Web Bot Auth signature whose Ed25519 key is published at the caller's Signature-Agent directory.

{
  "kind": "web-bot-auth",
  "tier": "cryptographically-verified",
  "statement": "an RFC 9421 HTTP Message Signature validates: the holder of key \"HuaaDCJ5d1JXjy9SLWFoX76L1VWQAAwA1xaowGw8IM8\" signed this request",
  "boundComponents": [
    "@authority",
    "signature-agent;key=\"sig1\""
  ],
  "keyid": "HuaaDCJ5d1JXjy9SLWFoX76L1VWQAAwA1xaowGw8IM8",
  "algorithm": "ed25519",
  "created": 1791153422,
  "expires": 1791153722,
  "directory": "https://agent.example/.well-known/http-message-signatures-directory",
  "keyMatchedBy": "thumbprint",
  "provenance": [
    {
      "source": "request-header",
      "header": "signature-input"
    },
    {
      "source": "request-header",
      "header": "signature"
    },
    {
      "source": "key-directory",
      "url": "https://agent.example/.well-known/http-message-signatures-directory",
      "keyid": "HuaaDCJ5d1JXjy9SLWFoX76L1VWQAAwA1xaowGw8IM8",
      "matchedBy": "thumbprint"
    }
  ],
  "note": "proves ONLY that the holder of this key signed this exact request, binding the listed components. Per the Web Bot Auth specification this does NOT establish who operates the agent, its organization, or any authorization for the requested action — those remain the relying party's decision."
}

Inspect is experimental (0.x) and stores nothing: it reads a request, returns the evidence, and retains no request, credential, or key data. Source and documentation: github.com/NessGate/nessgate · @nessgate/inspect.