Technograph is a conservative, HTTP-only technographic detection CLI and local MCP server. It reads company domains, observes their public homepage and apex DNS records, and matches those signals against Wappalyzer-style fingerprints.
The original technical-home-assignment interface is preserved unchanged.
Later releases add versioned machine output, human and spreadsheet views,
typed partial-failure states, snapshot comparison, SSRF protection for
autonomous callers, and a separate stdio MCP binary. The immutable assignment
submission is tagged v0.1.0.
It does not execute JavaScript, use a headless browser, call a paid API, or try to bypass bot protection.
brew install b1rd33/tap/technograph
technograph --versionUpgrade later releases with brew upgrade technograph.
The formula installs both technograph and technograph-mcp. Homebrew handles
the Go build dependency automatically; Go is not needed when using the
installed binaries afterward.
- Go 1.25 or newer
- Network access to public HTTP(S) endpoints and the system DNS resolver
git clone https://github.com/b1rd33/technograph.git
cd technograph
make buildThe binaries are written to bin/technograph and bin/technograph-mcp.
Dependencies are pinned in go.mod and go.sum.
Prebuilt archives and checksums for macOS and Linux on ARM64 and AMD64 are also available from the GitHub releases.
Put one bare domain on each line of a text file, then run:
./bin/technograph -output output.json domains.txtThe committed assignment scan can be reproduced with:
./bin/technograph \
-output output.json \
-report report.json \
-concurrency 10 \
-timeout 8s \
-dns-timeout 3s \
domains.txtAvailable options:
| Flag | Default | Purpose |
|---|---|---|
-output |
output.json |
Required technology-only JSON artifact |
-report |
empty | Optional diagnostics and evidence JSON |
-concurrency |
10 |
Maximum domains scanned simultaneously |
-timeout |
8s |
HTTP request deadline per domain |
-dns-timeout |
3s |
Deadline per DNS record query |
-dns-server |
system | Custom resolver IP and optional port; repeatable |
-fingerprints |
embedded | External normalized fingerprint file |
-insecure |
false | Explicitly disable TLS verification |
-verbose |
false | Show DNS and soft-block diagnostics |
Diagnostics go to stderr. A failed or blocked individual domain does not abort the batch; configuration, input, fingerprint, and output errors return a nonzero exit status.
The structured interface accepts direct domains, stdin, or a file. Flags may appear before or after positional domains:
technograph scan stripe.com shopify.com --format json
technograph explain stripe.com shopify.com
technograph explore stripe.com shopify.com
technograph explain stripe.com --pages 3
technograph scan stripe.com shopify.com --format table
technograph scan --input domains.txt --format csv --output results.csv
printf 'stripe.com\nshopify.com\n' | technograph scan --format jsonl
technograph scan --input domains.txt --output snapshot.json
technograph validate example.com https://invalid.example
technograph fingerprints
technograph fingerprints import --input webappanalyzer/src/technologies --output generated.json --report compatibility.json
technograph compare before.json after.json --output diff.json
technograph watch stripe.com --store .technograph-history
technograph history stripe.com --store .technograph-historyJSON output uses schema version 1.0 and gives every input an ok, partial,
blocked, failed, or invalid status plus typed warnings/errors. JSONL emits
domain results in actual completion order. Logs stay on stderr, so stdout is
safe to pipe into agents and automation. table is a compact terminal summary;
csv emits one spreadsheet-safe row per domain. Both preserve input order.
Detections include optional technology_categories metadata such as Payments,
Analytics, CRM, or CDN. Categories describe the technology; they never create
a detection and therefore do not change the conservative matching behavior.
technograph explain runs the same evidence-based scan but formats the result
for people: detections are grouped with their matching channel and source,
followed by HTTP/DNS coverage and any partial or blocked conditions. It does
not introduce a second detection path or an AI-generated confidence score.
technograph explore scans once and opens a lightweight interactive terminal
view over that same report. filter payments, show 1, show stripe.com,
list, and clear make live reviews easier without introducing a web
dashboard, a local server, or another dependency. Use positional domains or
--input domains.txt; standard input is reserved for explorer commands.
technograph watch is also one-shot: it compares a fresh scan with compatible
immutable local history, stores the observation, and exits. Confirmed changes
are structured separately from uncertain removals. --fail-on-change returns
status 3 after writing the report, which lets an external scheduler send a
notification without giving Technograph notification credentials. history
reads those private local entries without network access.
Homepage-only scanning remains the default. Local CLI users can opt into a
small deterministic crawl with --pages 2 through --pages 5 on scan,
explain, or watch. Secondary requests stay on the exact final homepage
host, use only public HTTP(S) ports, skip static assets, share the original
per-domain HTTP deadline, and never execute JavaScript. Evidence from an
opt-in scan includes the page URL. A secondary failure makes the structured
result partial but preserves valid homepage and DNS observations.
The separate technograph-mcp binary always serves five local stdio tools:
scan_domain, scan_domains, explain_domain, validate_domain, and
list_fingerprints. explain_domain groups every technology only with its
actual matching evidence; it never asks a language model to invent confidence.
{
"mcpServers": {
"technograph": { "command": "technograph-mcp" }
}
}Starting the server with a human-chosen fixed history directory adds
watch_domain and domain_history:
{
"mcpServers": {
"technograph": {
"command": "technograph-mcp",
"args": ["--history", "/absolute/path/to/history"]
}
}
}The agent cannot provide or change that filesystem path. watch_domain is the
only state-changing MCP tool and stores the same private immutable observation
used by the CLI; all other tools are read-only.
See docs/agent-interface.md for the complete contract, docs/stability.md for the 1.x compatibility policy, docs/fingerprint-import.md for the native Wappalyzer adapter and compatibility reports, docs/automation.md for scheduling patterns, docs/benchmarks.md for reproducible performance checks, and schemas/scan-report.schema.json for the machine-readable schema. No Codex/ChatGPT skill is created or required.
The required file contains every valid input domain, including domains for which no technology was detected:
{
"example.com": ["Cloudflare", "Google Workspace"],
"unreachable.example": []
}Technology names are sorted. Domain order follows the deduplicated input. Files are written to a temporary sibling and atomically renamed, so an interrupted run does not leave a partial JSON file.
-report adds final HTTP status, redirect and block information, exact DNS
records, elapsed time, and evidence for every match. The report is deliberately
separate so the required submission shape stays exact.
The implementation is divided into small packages:
internal/clivalidates bare domains, normalizes IDNs, derives registrable apexes with the Public Suffix List, and preserves the original command.internal/appconstructs the reusable scanner used by every interface.internal/agentapidefines the versioned status, report, streaming, and diff contracts;internal/agentcliexposes them as structured subcommands.internal/probefetches homepages, optionally follows a bounded deterministic set of same-host pages, and performs exact MX, TXT, and apex CNAME queries. HTTPS is preferred; HTTP fallback occurs only for transport/TLS failures. DNS uses the system resolver configuration, nameserver failover, UDP first, and TCP retry for truncated replies. Local CLI users may opt into literal-IP resolvers; MCP always uses the host configuration.internal/extractproduces structured signals from final response headers, cookie names/values, raw HTML, script URLs and bounded inline source, meta fields, and staticwindow.*syntax. Relative script URLs are resolved against the final response URL.internal/fingerprintcompiles patterns once, indexes them by channel, and returns sorted technologies plus auditable evidence. There is no technology-specific matching code.internal/scanneruses a bounded domain worker pool. HTTP and DNS run in parallel inside each domain job, each with a hard parent deadline. Results retain input order and partial successes.internal/outputcreates deterministic required JSON and detailed reports.internal/historystores private immutable per-domain observations and rejects corrupt, oversized, or incompatible history rather than guessing.internal/policyresolves and pins public addresses for autonomous HTTP requests while rejecting private, loopback, link-local, metadata, multicast, documentation, and other special-use networks.internal/mcpserverexposes a small explicitly scoped tool surface using the official Go MCP SDK. MCP dependencies and protocol output are isolated in the separatetechnograph-mcpexecutable. Optional history is fixed at process startup and cannot be redirected by a tool call.
The supplied fingerprint file uses a normalized representation because the
assignment's header and cookie expressions match names, while native
Wappalyzer normally uses exact names as selectors and applies regexes to their
values. Every rule therefore has an explicit channel, regex, and optional
target (name or value). A technology is detected when any of its patterns
matches a signal in the same channel. Evidence and technology names are
deduplicated. Optional category metadata is loaded generically from the same
fingerprint definition.
Accuracy is intentionally favored over recall:
- only the final response's headers and body are inspected; cookies accepted during redirects may contribute when they apply to the final URL;
- TLS verification is enabled unless the user explicitly opts out;
- response bodies are capped after decompression;
- confirmed challenge pages are recorded but their HTML/script/meta signals are suppressed;
- final headers, cookies, and DNS remain usable when HTML is blocked;
- HTTPS does not fall back to HTTP for ordinary 4xx/5xx responses;
- DNS record types fail independently.
A Cloudflare-protected site is therefore not a fatal condition. cf-ray or
cf-cache-status can still detect Cloudflare itself, DNS still runs, and the
scanner safely avoids claiming technologies from a Cloudflare challenge body.
This may produce false negatives for technologies visible only after browser
execution or only in protected HTML, which is the correct trade-off for this
assignment.
Structured CLI scans enable the autonomous network policy by default. It
revalidates every connection and redirect, pins the validated address, allows
only ports 80/443, and ignores proxy environment variables. A clearly named
--allow-private-network escape hatch exists for trusted local CLI testing;
it is deliberately unavailable through MCP. MCP also caps each request and
global active work at 20 domains and accepts only embedded fingerprints.
Local CLI users may opt into a resolver with repeatable --dns-server IP[:port] flags (or -dns-server in legacy mode). Literal IPv4/IPv6 addresses
are required. Custom resolvers should be treated as parties that can observe
queried domains.
Technograph implements its own matcher; Wappalyzer is not a runtime dependency.
Patterns are case-insensitive and support Wappalyzer's escaped
\\;confidence:N and \\;version:... metadata (version templates are retained
for future version reporting). Go's RE2 engine supports all 24 supplied
patterns and 13,710 of 13,724 current upstream patterns measured across the
relevant pattern-bearing fields (99.90%). The 14 unsupported JavaScript
lookaround expressions are reported when loading an external file rather than
silently rewritten. Full details and methodology are in
docs/wappalyzer-compatibility.md.
technograph fingerprints import provides an offline, deterministic adapter
for native upstream technology files. It preserves exact header, cookie, and
meta selectors, resolves category IDs when a mapping is supplied, removes
duplicates, and emits a machine-readable reason for every skipped unsupported
channel or regex. Generated databases remain opt-in; the small reviewed
assignment set stays embedded by default.
The scanner implements static HTTP/DNS channels only. Runtime DOM inspection, executed JavaScript globals, XHR observation, TLS certificates, robots files, and unbounded crawling are outside scope. Public pages and DNS records also change over time, so real results are observations, not permanent facts.
make fmt
make vet
make test
make race
make benchmark
make buildThe suite covers domain validation and fuzzing, all supplied fingerprints, wrong-channel negatives, structured extraction, redirects, TLS/HTTP fallback, compression, body limits, challenge suppression, DNS normalization and TCP retry, bounded and completion-order concurrency, deterministic legacy JSON, typed agent output, conservative diffs, SSRF address policy, atomic output, and an in-memory MCP client/server integration test. Opt-in page coverage tests also enforce deterministic ordering, deduplication, static-file exclusion, default compatibility, and cross-host redirect rejection.
output.json is the raw, unedited result of scanning the exact 20 domains in
domains.txt on 2026-08-27 (Europe/Sofia) using Go 1.27.0 on Darwin/arm64,
concurrency 10, an 8-second HTTP timeout, and a 3-second per-query DNS timeout.
The final measured wall time was 4.064 seconds. All 20 domains were emitted; there
were zero HTTP transport failures and one detected challenge response.