vendorprint builds an evidence-backed vendor fingerprint from public DNS. It is a Node.js CLI that checks for:
- the inbound email provider or security gateway;
- recursive SPF, DMARC, and conventional plus evidence-guided DKIM selectors;
- self-identifying TXT ownership proofs for SaaS products;
- authorized third-party email senders and DMARC monitoring vendors;
- short CNAME chains and delegated sending subdomains attributable to GTM vendors;
- evidence-ranked profiles across sales engagement, CRM, marketing automation, customer data, analytics, support, education, events, payments, and delivery.
The product site includes an interactive sample replay and complete CLI documentation. The replay does not make browser-side DNS or website requests.
The useful sales-engagement clue is a custom tracking subdomain whose CNAME target identifies a vendor. SPF and DKIM generally describe the connected mailbox provider, not the sequencing product. A positive CNAME is strong setup evidence, but it can be stale. A negative result is inconclusive because DNS cannot list arbitrary subdomains, the customer chooses the label, a proxy can hide the CNAME, or branded tracking can be disabled.
Node.js 20 or newer is the only requirement. Run a scan without installing a global command:
npx vendorprint@latest example.comTo keep the vendorprint command available globally:
npm install --global vendorprint
vendorprint --helpTo use the library API from a Node.js project:
npm install vendorprintnpm test
vendorprint example.com another.example
vendorprint example.com --full --pretty
vendorprint --input accounts.txt --output findings.json
vendorprint --input accounts.txt --format ndjson \
--output findings.ndjson
vendorprint signatures --pretty
vendorprint signatures audit findings.ndjson --pretty
vendorprint diff prior.json current.json --prettyThe default output is a result-first JSON report: observed email and technology
providers, confidence, relationship level, evidence, and caveats. It omits the
candidate labels searched, raw DNS answers, query budgets, and empty technology
categories. Use --full when you need that forensic scan detail.
This output choice is separate from scan depth. --mode full is still the
accuracy-first scan default; balanced and fast remain explicit opt-ins for
cases where reduced coverage is acceptable.
Input files may be comma-, space-, or newline-separated. To use a commented file:
rg -v '^\s*(#|$)' examples/accounts.txt |
vendorprint --stdin > findings.jsonIf you know a company's naming convention, add candidate labels:
vendorprint sample.example \
--labels news,updates,teamname,brandnameThe output is formatted JSON on stdout unless --output is supplied. Diagnostics
and CLI errors go to stderr, so redirecting stdout is safe. --pretty remains
accepted for backwards compatibility and for the offline utility commands.
For large jobs, use NDJSON. Each completed domain is appended immediately, which keeps memory bounded and creates a durable checkpoint:
vendorprint --input accounts.txt --format ndjson --output findings.ndjson
# If the process is interrupted, rerun with the same original input:
vendorprint --input accounts.txt --resume findings.ndjsonResume reads both full and compact result events, skips normalized domains already present, tolerates a final partially written line, and appends the remaining results. New output files are created without overwriting an existing file.
The default discovery view returns only non-empty arrays under
technologyProfile, including
aiWorkspaces, crm, salesEngagement, marketingAutomation,
advertisingAndAbm, customerData, productAnalytics, customerSupport,
customerSuccessAndEducation, eventsAndWebinars, meetingIntelligence,
commerceAndPayments, emailDelivery, and businessSoftware. The profile also
separates websiteAndContent, brandAndCreative, and identityAndAccess.
--findings-only remains as a backwards-compatible alias for this default.
--full additionally includes the inference graph, deterministic technology
fingerprint, raw DNS records, and scan diagnostics.
Each technology result retains the evidence, strongest confidence, product scope,
relationship level, and a caveat. The relationship levels are:
domain_relationship for ownership TXT records, sending_authorization for SPF,
and platform_configuration for attributable CNAMEs. For example, an OpenAI
ownership TXT record is useful evidence of an OpenAI domain relationship, but it
does not by itself prove ChatGPT Enterprise. Likewise, a Salesforce-hosted custom
domain proves Salesforce platform use without proving that Sales Cloud seats are
active. Salesforce Marketing Cloud Engagement configuration also produces a
medium-confidence Salesforce CRM inference, explicitly labeled inferred, because
it proves a Salesforce customer relationship without directly proving Sales Cloud.
The inference graph never silently converts compatibility into an observed product.
For example, Marketo produces a CRM candidate set containing Salesforce, Microsoft
Dynamics 365, Veeva, and other/no native CRM. Independent CRM DNS evidence marks a
candidate as corroborated and raises the candidate-set confidence, but the other
possibilities remain because co-presence does not prove an enabled integration.
The scanner can be imported without invoking the CLI:
import { scanDomains } from "vendorprint";
const report = await scanDomains(["example.com"], {
concurrency: 4,
maxInflightDns: 32,
qps: 100,
});scanDomainEvents() exposes the same scanner as an async iterator for
completion-order streaming:
import { scanDomainEvents } from "vendorprint";
for await (const event of scanDomainEvents(domains)) {
// meta, then one result per completed domain, then summary
process.stdout.write(`${JSON.stringify(event)}\n`);
}Signatures live in a versioned registry, and each scan identifies the catalog version it used:
vendorprint signatures --prettyAudit an existing full JSON or NDJSON scan without making network requests. The audit emits aggregate unmatched TXT prefixes and CNAME, SPF, DKIM, and delegated-NS targets. It never emits account domains or verification tokens:
vendorprint signatures audit findings.ndjson --prettyCompare two JSON reports to identify category/provider additions and removals:
vendorprint diff before.json after.json --pretty--ct asks a third-party public certificate-transparency index for direct
subdomains, then validates the resulting labels with DNS. It is disabled by
default, limited to 100 domains per run, waits at least 500ms between index
requests, and never contacts company web endpoints.
vendorprint --input accounts.txt --ct \
--ct-cache .vendorprint-ct-cache.json \
--output findings.jsonUse a cache and small batches. Certificate data can be stale, and a certificate name alone never becomes a technology finding without live DNS corroboration.
- Full mode tries common tracking, help, community, education, and hosted-content labels, plus adaptive SPF, CNAME, DKIM, and delegated-NS checks. It is the default.
--mode fastuses the 10 highest-yield labels and omits DKIM: about 14 queries per domain.--mode balanceduses 23 labels and omits DKIM: about 27 queries per domain.- URLs and hostnames are normalized to their registrable company domain using the
Public Suffix List. Use
--preserve-hostnameonly when a specific delegated hostname should be scanned. - TXT verification records are often self-identifying (
openai-domain-verification,slack-domain-verification, and similar). Their tokens are intentionally redacted. A live record proves that the domain was configured with that service, not that seats are paid, assigned, or recently active. - Generic verification-looking TXT records are excluded from named vendor evidence.
--include-unclassifiedexposes them in a separate diagnostic array. --concurrency 4controls how many account domains run at once.--timeout 1800controls the resolver attempt timeout.- The default domain concurrency is four and the CLI rejects values above 12.
- Each domain has a hard budget of 80 DNS attempts, no more than four logical queries in flight, and at least 25ms between per-domain query starts. A global default ceiling of 32 in-flight attempts and 100 starts per second protects the configured recursive resolver across concurrent domains.
- Only MX, apex TXT, DMARC, and nameserver lookups receive one retry after a
non-absence resolver failure. The retry reserve is included in the 80-attempt
budget. Extra candidate labels are safely skipped when the budget is exhausted
and reported in
querySafety. - Recursive SPF follows
includeandredirectterms with a maximum of ten recursive lookups. - Positive CNAME answers can be followed for up to three hops. Delegated sending subdomains and evidence-guided DKIM selectors are checked only in full mode and remain inside the same hard budget.
- A deterministic nonexistent-label preflight identifies wildcard DNS. Wildcard records can create irrelevant answers, but a vendor is emitted only when the CNAME target matches a supported vendor domain.
- Wildcard-like CNAME answers are collapsed to one observation and reported in
cnameDiagnostics, preventing dozens of guessed labels from inflating evidence. - Cloudflare-proxied records often expose only A/AAAA records and therefore hide the upstream CNAME.
- vendorprint performs DNS lookups only; it does not request company websites, tracking endpoints, login pages, or application APIs.
- See Responsible use before scanning at scale.
- Outreach documents branded tracking URLs as CNAMEs and says Outreach itself does not require vendor-specific SPF/DKIM configuration.
- Apollo documents custom tracking CNAMEs
and explicitly says its messages are sent through the connected email provider.
The observed
aplolinks.comfingerprint is deliberately medium-confidence because Apollo's public setup documentation does not name that generated target. - Gong documents Engage branded URL CNAMEs whose unbranded target is under
email-composer-webhooks.gong.io. - Salesloft supports custom tracking domains; its attributable target used by the
detector is
custom-tracking.salesloft.com. - HubSpot documents reverse-proxy targets under
hscoscdn.net, Salesforce documents Marketing Cloud targets undersfmc-content.com, and SparkPost documents branded sending-domain CNAME configuration. These signals are classified by their narrowest defensible product scope. - Adobe documents native Marketo connectors for Salesforce, Microsoft Dynamics, and Veeva, while its developer documentation also describes operation without a native CRM.
- Microsoft documents Dataverse as a required Power Pages component. Power Pages therefore infers Dataverse directly while keeping Dynamics 365 as an unconfirmed candidate.