Repository navigation
feat(dns): add dns command for Active DNS lookups - #90
Open
Blevene (Blevene) wants to merge 25 commits into
Open
Blevene (Blevene) wants to merge 25 commits into
Blevene (Blevene) wants to merge 25 commits into
Conversation
Collaborator
Author
|
@greptile review |
|
Collaborator
Author
|
@greptile review |
Collaborator
Author
|
@greptile review |
1 similar comment
Collaborator
Author
|
@greptile review |
Collaborator
Author
|
@greptile review |
Collaborator
Author
|
@greptile review |
Collaborator
Author
|
@greptile review |
Blevene (Blevene)
force-pushed
the
feat/fqdn-dns
branch
from
October 7, 2026 15:26
d081959 to
382360d
Compare
Deviation: this moves resolveTimeWindow and its error out of the history command into internal/pkg/flags. It is the first function in that package that is not a flag type or a flag error. Reason: the new dns command takes the same --start/--end/--duration flags, and one shared implementation avoids a copy; internal/pkg/datetime holds only parsing and time zones and has no error types. The logic and the error message are unchanged; the history tests protect the move, and the resolver now has its own unit tests. The tags app package keeps its own NewInvalidTimeWindowError. It checks filter pairs (created-before/created-after), lives in the app layer, and has a different message, so it stays separate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deviation: this adds a new identifier type. Reason: cencli has no FQDN type, and reusing NewWebPropertyID(...).Hostname would accept "example.com:8080" and drop the port without an error. DomainName rejects a port and bracketed IPv6, and accepts defanged names and pasted URLs. Its errors use the same "invalid <type>: %q" prefix as the other id constructors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the four Active DNS operations (name and IP, bounds and ranges) with positional arguments, retry, and response metadata, as the other GlobalDataClient methods do. Deviation: the methods take record types as []string, not SDK enums, and a small generic helper (dnsRecordTypes) converts them. Reason: the SDK generates four different enum types for the same values, one for each operation; plain strings keep those types inside the client, and the helper avoids four copies of the conversion loop. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds lookups for the records of a name and for the names that resolved to an IP, each as aggregated rows or as observed time ranges. The service validates record types for each direction (sets sourced from the SDK enums), pages with the shared paginator, and replaces a 403 with a message that names the Search and Core plan requirement. The four lookups share one private paging helper. Deviation: validatePaginationParams, NewInvalidPaginationParamsError, and optionalInt64 are copied from tags; search and tags each own their copy. Reason: the repo keeps these per package, and sharing them would add a refactor of search and tags that this feature does not need. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds `censys dns <name|ip>`. An IP looks up the domain names that resolved to it; a name looks up its A, AAAA, MX, NS, SOA, and TXT records. --timeline shows each observed time range. The command takes the same --start/--end/--duration flags as history (default 7d), plus --record-type and --page-size/--max-pages. The table always shows the time window, and a note says when --max-pages cut the results short. Deviation: the default output is the short table, not JSON as in history. Reason: the closest precedents (censeye, tags, aggregate) show a table first; JSON, YAML, tree, and --streaming stay available. Deviation: input detection does not use assets.AssetClassifier. Reason: the classifier has no domain-name type and reads a bare name as a web property on port 443; adding a domain type would change what view and history do with that input. The command still splits the input like history and gives the same Too Many Assets error for a list. Deviation: an invalid domain name keeps the constructor's reason in the Invalid Asset ID error (for example "remove the port"), where other callers use a fixed reason. Reason: the reason tells the user how to fix the input. Deviation: parsePaginationFlags and the fetch-all-pages warning are copied from tags and search; search and tags each own their copy. Reason: the repo keeps these per command, and sharing them would add a refactor of search and tags that this feature does not need. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The invalid-port fixture compares stderr with the help golden after the command's description, which is 13 lines for dns (view's is 2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gif lines are omitted from README.md and DNS.md because vhs was not available to record them; the tapes are registered in cmd/examples, so `go run ./cmd/examples dns` can record them later. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… output A pasted URL whose host is an IP, such as https://8.8.8.8/, was silently routed to the domain-name lookup instead of the IP lookup: NewDomainName strips the scheme and path and returns a bare IP-shaped string, which looked like a valid name. parseInput now retries NewHostID on the parsed name and switches to the IP direction when that succeeds, so both plain and defanged IP-in-URL inputs (https://8.8.8.8/, hxxp://1.2.3[.]4/x) now resolve as IPs. A bare CIDR range such as 8.8.8.8/32 or 10.0.0.0/24 has no single IP to look up, so parseInput now rejects it before any API call with a message telling the user to give one IP address. A URL with a numeric path (e.g. https://8.8.8.8/32) is not mistaken for a CIDR, since it carries a scheme. The short-table truncation note ("Showing N of M records. Use --max-pages -1 to fetch all.") used to appear any time the API's total exceeded what was fetched, even when that advice didn't apply: with --max-pages -1 the user is already fetching everything, and when a later page failed the partial-error message printed after the table already explains the gap. The note now only prints when a finite --max-pages was set and no partial error occurred. The docs note was updated to match. The ip-short e2e fixture now passes -p 1 so it makes one page's worth of calls and finishes reliably within its timeout. A TXT record's value could contain a newline, carriage return, or tab, which would break the DNS table's one-row-per-record layout. The TXT column now replaces those characters with a space before truncating, and truncates by rune rather than by byte so a multi-byte character is never split. NewDomainName now rejects a name containing '@' and a name with an empty label (an inner "..", or a second trailing dot such as "censys.com.."), both with a clear reason in the error message. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Test tables now use one field per line, matching the other table
tests in the repo.
DNS.md now says the truncation note is suppressed by --quiet and
that a CIDR range is rejected.
Deviation: when --max-pages stops the fetch before all records
arrive, dns prints a note to stderr ("Showing N of M records. Use
--max-pages -1 to fetch all.") in addition to the "(n of M)" count
in the title. Reason: tags shows truncation only in the title; a DNS
lookup of a busy IP can have tens of thousands of records, so the
note names the flag that fetches the rest. The note is suppressed by
--quiet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A non-positive --duration (e.g. -1h or 0s) could produce a start time after the end time, or a zero-length window, whenever only one of --start/--end (or neither) was given, leaving the API to reject the request with a 400. ResolveTimeWindow now rejects a --duration <= 0 in the branches that use it, before any request is made. This also applies to history, which shares the same resolver, so it now rejects a non-positive duration up front instead of letting the API return a 400. The short table only stripped newline, carriage return, and tab from TXT values, so any other control character (an ESC byte, a BEL, or a C1 control) in a TXT value, domain name, mail server, name server, or SOA name from the API could reach the terminal unescaped. Every string cell the short table prints now goes through one sanitizer that removes control characters, applied once at the column level rather than per record type. A pasted URL with a comma in its path or query (e.g. https://censys.com/a,b) was split on the comma before parsing and rejected as too many assets. The raw argument is now refanged and checked for a scheme first; a URL is kept as a single input, and a plain comma-separated list still splits and is rejected as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…truncating Detect a pasted URL by checking the raw argument for a literal "//" instead of refanging it first: refang.RefangURL prepends "http://" to some bare inputs, so a comma-separated list like "8.8.8.8,example.com" or "www.censys.com,api.censys.com" was misread as one pasted URL and reached the API as a single name instead of being rejected as too many assets. NewDomainName also now rejects any comma left in a name after its scheme and path are stripped, as a backstop, while still accepting a comma that only appears in a URL's path. Sanitize a TXT record's value before truncating it for the table instead of after: control characters used to count against the truncation width, so a value padded with enough of them could be cut down to nothing visible before sanitizeCell ever ran. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add e2e fixtures for the `dns` command's input-rejection paths that fail before any API call is made: a bracketed IPv6 address, a CIDR range, four comma-separated asset lists, an email-shaped input, an empty DNS label, and a negative duration. Each asserts the error title line and a Contains check on the reason, following the existing invalid-port fixture's style. The invalid --record-type case (`-r MX`) is included too, but needs NeedsAuth: true: PreRun builds the DNS client before Run validates --record-type, so with no client configured the command fails earlier with "Censys Client Not Configured" instead of the record-type rejection. Configuring auth lets the client build so the record-type check, which still runs before any list request, is what actually gets exercised. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RefangURL did not recognize the bracketed-scheme defang form ([://]), as in hxxps[://]censys[.]com. Neither the "looks like a URL" guard nor the replacement logic handled it, so the input passed through unmodified and downstream parsing treated the brackets and slashes as part of the hostname. Add [://] to the URL-likeness detection alongside the other defang markers, and replace it with :// in the same place and style as the existing [:] handling. Deviation: this changes the shared refang package, which the dns feature otherwise does not touch. Reason: the bracketed-scheme defang form ([://]) was not recognized by any command; dns showed it worst, sending a malformed name to the API. Fixing it in RefangURL fixes every caller (dns, view, history, tags, and censeye), and the refang tests cover the new form. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… CIDRs Move --record-type validation in the dns command's PreRun ahead of the DNS service lookup, by exporting internal/app/dns.ValidateRecordTypes (reusing the existing normalizeRecordTypes and name/IP type sets). Previously the service resolved the API client first, so an unsupported record type on an unauthenticated run failed with "Censys Client Not Configured" instead of the actual record-type error; the service keeps its own validation as defense in depth for direct callers. Refang the IP part of a candidate CIDR range (as NewHostID already does) before checking it with net.ParseIP, so a defanged CIDR such as "8[.]8[.]8[.]8/32" or "10.0.0[.]0/24" is rejected with the same "a CIDR range is not supported" error instead of silently dropping the prefix and looking up the wrong IP. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a --domain flag that narrows an IP timeline lookup ("when did this
domain point at this IP") to one domain name. The Active DNS API only
supports this filter on the IP-ranges operation, so the client's
ListDNSIPResolutionRanges gained a domain mo.Option[string] parameter
(between recordTypes and pageSize) that maps to the SDK request's Domain
field; the other three DNS lookups are unaffected and mocks were
regenerated for the new client method signature.
The service's Params struct carries the filter as
mo.Option[assets.DomainName], matching how a domain name is already typed
elsewhere in this package, and only IPResolutionRanges forwards it to the
client.
The command validates --domain before resolving a service: it only
applies to an IP input with --timeline, since that is the only lookup the
API supports it on, and any other combination is a usage error before any
API call. The value is parsed with assets.NewDomainName, so defanged
input normalizes and an invalid name gets the usual Invalid Asset ID
error. Added a documented example and a help-text test, and covered the
flag with e2e fixtures for the two new usage errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dns now takes several inputs: positional arguments, comma lists (an argument that contains "//" stays one URL input), and -i/--input-file (a path, or - for STDIN), which overrides the positional arguments as in view. Every input is parsed before any API call, so one invalid input fails the command with that input's error. Inputs are de-duplicated by their normalized form, and one command accepts at most 100. Lookups run one after another, each with its own direction (name or IP), --timeline, and pagination limits. A failed input does not stop the others: its error is printed to stderr, prefixed with the input, after the output, and the command exits 0 when at least one input succeeded. A 403 (plan restriction) stops at once, because every other input would fail the same way. Data output is one flat list of all records in input order, and every JSON record now has an "input" field with the name or IP it answers; --streaming emits one NDJSON line per record with the same field. Short output for one input is unchanged; several inputs print the time window once, then one section per input. Deviation: dns now accepts several inputs, where history takes one. Reason: DNS lookups are often run over a list of indicators; view already accepts several inputs and --input-file, and dns follows that. The API has no bulk operation, so each input is its own lookup, capped at 100 inputs per command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a "dns" template entity for the dns command's -O template output, alongside the existing host/certificate/webproperty/searchresult entities. The default template, internal/config/templates/dns.hbs, renders each record in the flat array the command's data output already prints (one input, so a lookup over several names or IPs still distinguishes their records): the input, the record type, the type-specific value (A/AAAA ip; MX priority and mail server; NS name server; SOA mname and rname; TXT value; IP records: domain), and the first/last seen time, using first_observed/last_observed for a --timeline lookup and first_seen/last_seen otherwise. The dns command now advertises OutputTypeTemplate and renders through config.TemplateEntityDNS via the same record slice its JSON/YAML output uses, so the template sees exactly the API's field names. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…d tags A plan-restricted (403) lookup in the middle of a bulk run now prints the records already collected before returning the error, instead of discarding them; a 403 on the first input still fails immediately, since there is nothing to keep. When every input fails, stderr now lists the failures in input order (all but the last are printed inline, and the last is returned, since a returned error is what the caller prints right after this command's own stderr output). A context cancellation mid-loop now prints how many inputs were never looked up, instead of stopping silently. Several names or IPs can be given as positional arguments, comma-separated lists, or via --input-file, and the help text and the --input-file description now say so. This corrects an earlier credit: several positional arguments follow the pattern in tags assign/unassign, and --input-file and comma lists in positional arguments follow view; a file's lines are read as-is and are never comma-split, so a line containing a comma is one (invalid, multi-name) input. A --record-type rejected only because an IP is among the inputs (e.g. MX with a mixed name/IP list) now says so, instead of reading as if the type were unsupported outright. Several inputs now print one combined response-metadata block after all lookups (the last response's method/URL/status, and the latency and page count summed across every lookup) instead of one block per input. Added tests pinning the exact one-input short-output layout (with and without records) and confirming an MX record's priority of 0 still renders in the handlebars template. Deviation: the four DNS record wrappers define MarshalJSON. Reason: the SDK's DNS record types define their own MarshalJSON, so a wrapper that embeds one would inherit it and drop the "input" field; the wrapper writes "input" and then the SDK's own encoding. All output formats (json, yaml, tree, NDJSON) go through it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
After `view` renders short output for exactly one host, print a one-line hint to stderr pointing at the new `censys dns <ip>` command, using the IP as view normalized it. The hint is suppressed by --quiet, and it never appears for json/yaml/tree/template output, streaming mode, certificates, web properties, or a view of more than one host. Deviation: this changes the view command, which the dns feature otherwise does not touch. Reason: a hint on the command users already run for a host makes the new dns command discoverable. The hint goes to stderr only, only for short output of a single host, and --quiet suppresses it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bring DNS.md up to date with everything the command now does: several names or IPs in one call (arguments, comma lists, --input-file, the 100-input cap, de-duplication, and that every input is validated before any API call), the defanged and pasted-URL forms that are accepted or rejected (including a defanged CIDR range, a port, brackets, "@", and an empty label), --domain, template output, and how several inputs behave in the short table, JSON/YAML/NDJSON, and on a partial or plan-restricted failure. Add the same --domain example and a short-output tip note to VIEW.md, add "dns" to GLOBAL_CONFIGURATION.md's template-entity table and list, and describe the command's bulk/format support and gif in README.md. Record examples/dns/dns.gif and examples/dns/dns-timeline.gif with vhs and link them from DNS.md and README.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e output The template engine looks up a field that a record lacks in the parent context, the list of records, so the default dns template printed a missing field collected from every other record (for example "Value: [10] [aspmx.l.google.com]" on an A record). RenderTemplate now passes each record as a map that holds every JSON field of the four record types plus "input", nil when the record lacks it, so a lookup stops at the record. This also protects user-supplied templates. Every string in those maps is stripped of control characters, as in the short table, and the default template prints API strings unescaped. Each map also has a "timeline" flag, so the template labels IP timeline records (which carry only first_seen/last_seen) "First Observed" and "Last Observed", as the short output does. A failed input followed by a plan error (403), with no input succeeded, now prints the earlier failures in input order before the 403 is returned. A CIDR range with a defanged slash (8.8.8.8[/]32) is rejected like any other CIDR range instead of being looked up as its IP. The interrupt line says "1 input" in the singular. The view hint to run censys dns now needs exactly one host input and one host returned, and names the returned host's IP. The DNS docs state the exit rule and the error order exactly, and describe input-file trimming, the defanged-slash CIDR form, and how template values and the default template file are handled. Deviation: the default dns template prints API strings with triple braces (no HTML escaping), unlike the other shipped templates. Reason: TXT, SPF, and DKIM values contain quotes and ampersands that HTML escaping would mangle on a terminal; every string is stripped of control characters before rendering, so nothing unsafe reaches the terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a bulk lookup's context is cancelled after an input's first page already came back, that input's fetch can return successfully with partial records and a page-level interruption error, rather than a fetch error. The loop previously only checked for cancellation after a fetch failed outright, so in this case it went on to start the next input's lookup, which failed before any request went out and only then reported the interruption. The loop now checks the context at the top of every iteration, before starting an input, so a cancelled bulk lookup stops immediately and reports the correct remaining count without an extra, doomed request. An explicitly empty --domain (for example --domain "") was read the same as an absent flag, silently widening an IP timeline to every domain instead of one. --domain is only ever meant to narrow a lookup, so a blank value now fails with a usage error instead of being accepted. Deviation: an explicitly empty --domain is a usage error, where tags treats a blank filter value as absent. Reason: --domain only narrows an IP timeline, so treating an empty value (for example an unset shell variable) as absent would silently widen the lookup to every domain. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s looked up The no-results path assumed at least one failure had been recorded, but the loop can stop before any input's lookup starts (a cancelled context caught by the loop-top guard on the first iteration) or right after the only attempted input fails and cancels the context. In both cases the failures slice can be empty, and indexing failures[len(failures)-1] panicked. Track whether the loop stopped because the context was cancelled. When it was, print every failure collected so far in input order and return the interruption itself, so the exit code reflects the interruption (130) rather than an unrelated failure or a panic. When there is no interruption, keep the existing behavior of printing every failure but the last and returning the last. The remaining case (no failures and no interruption) can't happen given the loop's structure, but returns a generic error instead of indexing an empty slice, just in case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR #89 moved the tags paginator to internal/app/pagination with the type names Page and Result. This branch had made the same move with the names PageData and Paginated. The dns service now uses the #89 names, and this branch no longer carries its own copy of the move. The two paginator tests that only this branch had (empty first page and streaming mode) move into the shared test file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR #89 moved the pagination param check, its error, OptionalInt64, and --page-size/--max-pages parsing into internal/app/pagination and internal/command. dns had its own identical copies of all four; it now uses the shared ones. The fetch-all-pages warning stays local: with several inputs it says the fetch repeats for each one, which the shared warning cannot say. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blevene (Blevene)
force-pushed
the
feat/fqdn-dns
branch
from
October 7, 2026 20:16
382360d to
8829bfb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
censys dns <name|ip>..., a command for Censys Active DNS lookups over a time window.--timelineshows each observed time range instead of one row per record.--domainnarrows an IP timeline to one domain name ("when did this domain point at this IP").--input-file. Each is looked up independently — one failing input does not stop the rest.https://censys.com/path,censys[.]com,hxxp://8.8.8[.]8/x,hxxps[://]censys[.]com. A port (censys.com:443), bracketed IPv6, a CIDR range (including defanged, such as8[.]8[.]8[.]8/32and8.8.8.8[/]32), an@, an empty label, or a non-positive--durationfails with a clear fix before any API call.json,yaml,tree,template, and--streaming(NDJSON) are all supported.It is a new top-level command rather than a mode of
vieworhistory:viewis a point-in-time snapshot, and every DNS endpoint is a time window, so one meaning per command.After a short-format
viewof a single host, a stderr tip now points atcensys dns <ip>(suppressed by--quiet), so the new command is discoverable from the one people already run.Flags
-s/--start,-e/--end,-d/--duration-d 7dhistory)-t/--timeline--domain--timelineonly-r/--record-type-i/--input-file-); overrides positional arguments-n/--page-size,-p/--max-pages-p -1fetches all pages--org-idRequires the Censys Search or Core plan; a 403 names the plan requirement, and (with several inputs) keeps and prints whatever was already fetched before reporting it.
Structure
Same layers as the other commands:
internal/command/dns→internal/app/dns→ four newGlobalDataClientmethods → censys-sdk-go.Two refactors come first so the new code reuses existing logic instead of copying it:
internal/app/tags/paginate.gomoves tointernal/app/pagination(exported; tags only changes imports).history's time-window resolver moves tointernal/pkg/flags.ResolveTimeWindow.Deviations from existing patterns
Each deviation and its reason is also in the body of the commit that contains it.
assets.NewDomainNameidentifier: no FQDN type exists, and the web-property parser acceptshost:portwithout an error.[]string: the SDK generates four enum types for the same values.resolveTimeWindowmoves fromhistorytointernal/pkg/flags: one implementation for both commands. It now also rejects a non-positive--durationup front for both commands, instead of letting the API return a 400.AssetClassifier: it has no domain type and reads a bare name as a web property on 443.parsePaginationFlags,validatePaginationParams,optionalInt64, and the fetch-all warning are per-package copies: search and tags each own theirs.--quiet.dnsaccepts several inputs, wherehistorytakes one: DNS lookups are often run over a list of indicators. Several positional arguments followtags assign/unassign;--input-fileand comma lists in arguments followview(file lines are not split). The API has no bulk operation, so each input is its own lookup, capped at 100 per command, run one after another; a failed input does not stop the rest, and the command exits 0 if at least one input succeeded, except after a plan error (403), which prints what was already fetched and then exits non-zero.NameRecord,NameRangeRecord,IPRecord,IPRangeRecord) define their ownMarshalJSON: the SDK's record types already defineMarshalJSON, so an embedding wrapper would inherit it and drop the addedinputfield. Every output format (json, yaml, tree, NDJSON) goes through it.viewgained a stderr hint pointing atcensys dns <ip>, which the dns feature otherwise does not touch: it makes the new command discoverable from the command people already run for a host. Short output of exactly one host only; suppressed by--quiet.refang.RefangURLgained support for the bracketed-scheme defang form ([://], as inhxxps[://]censys[.]com), which the dns feature otherwise does not touch: it was unhandled by any command, and dns showed it worst (a malformed name reaching the API). The fix is in the shared package, so it also benefitsview,history,tags, andcenseye.dnstemplate prints API strings with triple braces (no HTML escaping), unlike the other shipped templates: TXT, SPF, and DKIM values contain quotes and ampersands that HTML escaping would mangle on a terminal. Every string is stripped of control characters before rendering, so nothing unsafe reaches the terminal.--domainis a usage error, where tags treats a blank filter value as absent:--domainonly narrows an IP timeline, so an empty value (for example an unset shell variable) must not silently widen the lookup to every domain.Testing
go test ./...: 1858 tests pass.go test ./cmd/cencli/e2e/...,CENCLI_ENABLE_E2E_TESTS=true, credentials unset): all no-auth cases pass — help golden, invalid port, bracketed IPv6, CIDR (plain, defanged, and a defanged slash), too many inputs, an invalid list member,@in a name, an empty label, a non-positive duration, an invalid/mixed record type, and both--domainmisuse cases.--input-file, a comma-containing file line kept as one input, combined response metadata for several inputs, JSON with theinputfield),--timeline(name and IP, plus--domain), no-data output, every flag (record type valid/invalid/mixed, explicit window, zero/negative duration, json/yaml/template output (including a check that each template record prints only its own values), streaming, quiet vs. showing note, fetch-all-pages),--domainmisuse, regression checks againsthistory/tags/view, and the newview→dnshint (plain and--quiet).d081959): all checks passing, includinglint,tests-and-coverage,e2e-native, ande2e-smokeon macOS/Linux/Windows.examples/dns/dns.gif,examples/dns/dns-timeline.gif) recorded withvhsviago run ./cmd/examples dns.Not in this PR
//at all (for examplehxxps:[/][/]host) are not recognized as URLs, so a comma in their path splits them as a list.dnstemplate entry toconfig.yaml, an older cencli that reads the same file fails to load it. This PR does not change config loading; Config load fails on an unknown template entity, which breaks older versions after an upgrade #98 tracks the fix for future versions.🤖 Generated with Claude Code