llmfp attributes the infrastructure that executes a web fetch for an LLM API.
It creates a one-time oast.pro URL, asks the API to open it with a hosted web
tool, observes DNS/HTTP callbacks, and compares the HTTP source IP against
provider-published CIDR registries.
It deliberately reports model identity as unknown. A genuine provider web tool does not prove that the same provider generated the final model response.
This is an independent research tool. It is not affiliated with, endorsed by, or sponsored by OpenAI, Anthropic, or ProjectDiscovery.
| Signal | Current conclusion | Not established |
|---|---|---|
| Exact callback from a provider-published CIDR | Web-tool infrastructure attribution | Generation-model identity |
| User-Agent match only | Weak, forgeable supporting evidence | Provider attribution |
| API-reported model name | Unverified endpoint claim | Model authenticity |
| No callback | Inconclusive result | That a relay is fake |
Read the evidence model before using results in an audit or public claim. Planned behavioral fingerprints and contribution opportunities are tracked in TODO.md.
The normal evidence path is shown in blue, grey, and green. The dashed red path is an important adversarial alternative: because the API endpoint sees the unique probe URL, a relay can fetch it directly or invoke a genuine hosted tool independently of the model used to generate its answer.
Download the editable Draw.io source, the editable high-resolution PNG, or the SVG export.
Download the archive for your operating system and CPU from the
releases page. Prebuilt releases do
not require Go. Extract llmfp (llmfp.exe on Windows), place it in a directory
listed in PATH, and verify it:
llmfp versionGo 1.25.12 or newer is required:
go install github.com/toads/llmfp/cmd/llmfp@latest
llmfp versiongo install installs the executable but does not modify your shell's
PATH. If the installation succeeds but llmfp says command not found, find
the actual install directory:
GO_BIN="$(go env GOBIN)"
[ -n "$GO_BIN" ] || GO_BIN="$(go env GOPATH)/bin"
echo "llmfp was installed in: $GO_BIN"
"$GO_BIN/llmfp" versionIf the absolute-path command works, add that directory to PATH. For macOS
with zsh:
printf '\nexport PATH="%s:$PATH"\n' "$GO_BIN" >> "$HOME/.zshrc"
source "$HOME/.zshrc"
hash -r
llmfp versionLinux bash users can use the same commands with $HOME/.bashrc. Windows and
additional troubleshooting steps are covered in the
installation guide. Do not use sudo go install: it can
install the executable under root's Go directory instead of your own.
export RELAY_API_KEY='replace-me'
llmfp test --base-url https://relay.example --model claimed-model-namellmfp automatically checks LLMFP_API_KEY and then RELAY_API_KEY. It uses
OPENAI_API_KEY automatically only when the target host is api.openai.com,
and ANTHROPIC_API_KEY only when the target is api.anthropic.com, so an
official key is not accidentally sent to a relay.
Go 1.25.12 or newer is required. This minimum includes the standard-library security fixes used by the release CI.
go build -o llmfp ./cmd/llmfpPass either the API origin, the base URL ending in /v1, or the complete
adapter endpoint. llmfp normalizes all three forms.
For OpenAI Responses:
https://relay.example -> https://relay.example/v1/responses
https://relay.example/v1 -> https://relay.example/v1/responses
https://relay.example/v1/responses -> unchanged
For Anthropic Messages:
https://api.anthropic.com -> https://api.anthropic.com/v1/messages
https://relay.example/v1 -> https://relay.example/v1/messages
https://relay.example/v1/messages -> unchanged
export RELAY_API_KEY='replace-me'
./llmfp test \
--base-url https://relay.example/v1 \
--model claimed-model-name \
--api-key-env RELAY_API_KEYProgress is written to stderr. Use --quiet to suppress it, or --json for a
versioned machine-readable report on stdout.
Frequently reused values can be set without a config file:
export LLMFP_BASE_URL=https://relay.example/v1
export LLMFP_MODEL=claimed-model-name
export LLMFP_API_KEY=replace-me
./llmfp testFor machine-readable output:
./llmfp test \
--base-url https://relay.example/v1 \
--model claimed-model-name \
--api-key-env RELAY_API_KEY \
--jsonIf neither --api-key-env nor --api-key-stdin is supplied, an interactive
terminal prompts for the key without echoing it. Do not put API keys directly
on the command line, where shell history and process listings may expose them.
--adapter auto is the default. It selects anthropic-messages for
api.anthropic.com or a claude-* model name; otherwise it selects
openai-responses. Override it when a relay uses aliases that hide the upstream
protocol:
./llmfp test \
--adapter anthropic-messages \
--base-url https://relay.example/v1 \
--model relay-claude-aliasThe OpenAI adapter sends:
{
"model": "claimed-model-name",
"tools": [{"type": "web_search"}],
"input": "Open the unique probe URL and return its body"
}The Anthropic adapter calls /v1/messages with x-api-key,
anthropic-version: 2023-06-01, and the broadly compatible basic server tool:
{
"model": "claude-model-name",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Fetch the unique probe URL"}],
"tools": [{
"type": "web_fetch_20250910",
"name": "web_fetch",
"max_uses": 1
}]
}APIs that only emulate /v1/chat/completions or do not expose hosted web tools
cannot be classified by this adapter. A missing callback is inconclusive; it
does not by itself prove that a relay or model is fake.
At test time, llmfp downloads and hashes these official registries:
https://openai.com/chatgpt-user.jsonhttps://openai.com/searchbot.jsonhttps://openai.com/gptbot.jsonhttps://platform.claude.com/docs/en/api/ip-addresses.md
Anthropic publishes 160.79.104.0/21 for outbound tool calls including web
search and web fetch. The reported surface is therefore outbound-tools; the
CIDR alone does not distinguish one Anthropic server tool from another.
An official CIDR match is primary evidence. A matching User-Agent without a CIDR match is only weak evidence because headers are forgeable.
Check public probe registration and all official registry downloads without an API key:
./llmfp doctor
./llmfp doctor --jsonattributed: HTTP source IP matched a downloaded provider registry.likely: only forgeable supporting evidence, such as User-Agent, matched.dns_only: name resolution occurred but no HTTP fetch was observed.unattributed: HTTP fetch occurred but no official CIDR matched.no_callback: no callback was observed before the timeout.
no_callback is inconclusive. The API may ignore tools, lack hosted web search,
or block public probe domains.
JSON reports contain "schema_version": "1". Fields in schema version 1 will
not be removed or change meaning in a patch release. New optional fields may be
added. Consumers should preserve unknown enum values instead of treating them
as errors.
Exit codes are intentionally about whether the test ran, not whether a provider was attributed:
0: the command completed and produced a report, including inconclusive results such asno_callbackorunattributed.1: invalid arguments, registration/API/runtime failure before a report, failed diagnostics, or output failure.
Use tool_attribution.status for the result; do not infer it from the process
exit code.
The API key stays in the local CLI, but it is sent to the host selected by
--base-url. The public oast.pro service can observe callback metadata. Never
put secrets in probe URLs or prompts; use a self-hosted Interactsh server for
sensitive testing. See SECURITY.md.
The API endpoint sees the unique probe URL and can fetch it directly. Therefore an exact path proves correlation with the test, not that the claimed model chose or executed the tool. Full interpretation and adversary assumptions are in docs/EVIDENCE.md.
make check
make integration # explicitly uses public network servicesRelease archives for Linux, macOS, and Windows are configured through GoReleaser. CI intentionally keeps public-network integration tests out of the default test suite.
Project source code and documentation are licensed under Apache-2.0. You may use, modify, and redistribute them subject to that license, including its notice and patent terms. Contributions are accepted under the same license. See LICENSE, NOTICE, and THIRD_PARTY_NOTICES.md.
External papers and datasets keep their own licenses. In particular, the planned Zenodo behavioral-fingerprint dataset is CC-BY-4.0 and is not currently bundled or relicensed as Apache-2.0.
