knock 0.1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +188 -10
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +154 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.js +278 -0
- package/dist/mcp.d.ts +14 -0
- package/dist/mcp.js +319 -0
- package/dist/psl.d.ts +36 -0
- package/dist/psl.js +125 -0
- package/lists/public_suffix_list.dat +16501 -0
- package/llms.txt +70 -0
- package/package.json +53 -19
- package/.npmignore +0 -1
- package/knock.js +0 -10
- package/lib.js +0 -49
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011-2026 Carter Cole
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,17 +1,195 @@
|
|
|
1
|
-
|
|
2
|
-
=========
|
|
1
|
+
# knock
|
|
3
2
|
|
|
4
|
-
Knock,
|
|
3
|
+
> Knock, knock. Who's there?
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Subdomain enumeration for Node.js. Pulls candidate names from **Certificate
|
|
6
|
+
Transparency logs** (passive, via [crt.sh](https://crt.sh)) and a bundled
|
|
7
|
+
**31k-entry wordlist** (active DNS brute force), detects wildcard DNS so you
|
|
8
|
+
don't drown in false positives, and verifies everything with real lookups.
|
|
9
|
+
|
|
10
|
+
Use it as a CLI, a library, or an **MCP server** for AI agents. Zero
|
|
11
|
+
runtime dependencies. Great for attack-surface audits, pen-test recon,
|
|
12
|
+
or checking what a new client actually has exposed.
|
|
13
|
+
|
|
14
|
+
[](https://www.npmjs.com/package/knock)
|
|
15
|
+
[](https://github.com/neopunisher/node-knock/actions/workflows/ci.yml)
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g knock # CLI
|
|
21
|
+
npm install knock # library
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or run the CLI without installing:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx knock example.com
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Requires Node.js >= 22.
|
|
31
|
+
|
|
32
|
+
## CLI
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
knock example.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
www.example.com 93.184.216.34 [ct+list]
|
|
40
|
+
api.example.com 203.0.113.10 [ct]
|
|
41
|
+
mail.example.com 198.51.100.7 [list]
|
|
42
|
+
# example.com: 3 found from 31307 candidates, 18 ct names (2 unresolved) in 41.3s
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Useful flags:
|
|
46
|
+
|
|
47
|
+
| Flag | Effect |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `-p, --passive` | Certificate Transparency only — no brute force |
|
|
50
|
+
| `--no-ct` | wordlist brute force only |
|
|
51
|
+
| `--no-verify` | report CT names without resolving them |
|
|
52
|
+
| `--full-host` | scan the exact host given (skip the registrable-domain reduction) |
|
|
53
|
+
| `-l, --list <path\|subs\|org>` | custom wordlist, or a bundled one by name |
|
|
54
|
+
| `-c, --concurrency <n>` | parallel DNS queries (default 64) |
|
|
55
|
+
| `-s, --server <ip>` | DNS server to query (repeatable) |
|
|
56
|
+
| `-6, --ipv6` | also resolve AAAA records |
|
|
57
|
+
| `-w, --web` | probe http/https on each found host |
|
|
58
|
+
| `-j, --json` | full report as JSON |
|
|
59
|
+
| `-q, --quiet` | hostnames only (pipe-friendly) |
|
|
60
|
+
| `--mcp` | run as an MCP server on stdio (see below) |
|
|
61
|
+
|
|
62
|
+
Found hosts stream to stdout as they resolve; diagnostics go to stderr, so
|
|
63
|
+
`knock -q example.com | sort` does what you'd hope.
|
|
64
|
+
|
|
65
|
+
## Library
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { knock } from 'knock';
|
|
69
|
+
|
|
70
|
+
const report = await knock('example.com', { concurrency: 128 });
|
|
71
|
+
|
|
72
|
+
for (const { name, addresses, sources } of report.results) {
|
|
73
|
+
console.log(name, addresses, sources); // 'www.example.com', ['93.184.216.34'], ['ct', 'wordlist']
|
|
74
|
+
}
|
|
75
|
+
console.log(report.wildcard); // { detected: false, addresses: [] }
|
|
76
|
+
console.log(report.ct); // { enabled: true, names: 18, unresolved: [...], error: null }
|
|
77
|
+
console.log(report.stats); // { words, candidates, queried, found, errors, durationMs }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Stream results as they're discovered, or go passive-only:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
await knock('example.com', { onResult: (r) => console.log(r.name) });
|
|
84
|
+
|
|
85
|
+
// Just the Certificate Transparency names, one HTTPS request, no DNS:
|
|
86
|
+
import { certSubdomains } from 'knock';
|
|
87
|
+
const names = await certSubdomains('example.com');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Options
|
|
91
|
+
|
|
92
|
+
| Option | Default | Description |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `words` | — | array of labels to try (`[]` for passive-only) |
|
|
95
|
+
| `wordlist` | bundled `subs` | path to a wordlist file |
|
|
96
|
+
| `ct` | `true` | pull candidates from Certificate Transparency logs |
|
|
97
|
+
| `ctTimeout` | `15000` | crt.sh request timeout (ms) |
|
|
98
|
+
| `verify` | `true` | resolve CT names over DNS |
|
|
99
|
+
| `concurrency` | `64` | parallel DNS queries |
|
|
100
|
+
| `timeout` / `tries` | `5000` / `2` | per-query DNS timeout (ms) and retries |
|
|
101
|
+
| `servers` | system | DNS servers to query |
|
|
102
|
+
| `family` | `4` | `4`, `6`, or `'any'` for A + AAAA |
|
|
103
|
+
| `wildcardTests` | `3` | random probes for wildcard detection (`0` disables) |
|
|
104
|
+
| `web` / `webTimeout` | `false` / `5000` | probe http/https on found hosts |
|
|
105
|
+
| `baseDomainOnly` | `true` | reduce the target to its registrable domain first |
|
|
106
|
+
| `icannOnly` | `false` | ignore the PSL's PRIVATE section (github.io, …) |
|
|
107
|
+
| `signal` | — | `AbortSignal` to cancel the run |
|
|
108
|
+
| `onResult` | — | callback fired per discovery |
|
|
109
|
+
| `resolver` / `fetch` / `psl` | built-in | injectable for testing |
|
|
110
|
+
|
|
111
|
+
The bundled wordlists are exposed as `wordlists.subs` (~31k labels,
|
|
112
|
+
popularity-ordered) and `wordlists.org`, and helpers `loadWordlist(path)` /
|
|
113
|
+
`parseWordlist(text)` / `normalizeDomain(input)` are exported too. Full
|
|
114
|
+
TypeScript types ship with the package.
|
|
115
|
+
|
|
116
|
+
## MCP server
|
|
117
|
+
|
|
118
|
+
knock ships a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
119
|
+
server, so AI agents (Claude Code, Claude Desktop, Cursor, …) can enumerate
|
|
120
|
+
subdomains directly. It runs over stdio:
|
|
8
121
|
|
|
9
122
|
```bash
|
|
10
|
-
|
|
123
|
+
claude mcp add knock -- npx -y knock --mcp
|
|
11
124
|
```
|
|
12
125
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
126
|
+
Or in any client's JSON config:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"mcpServers": {
|
|
131
|
+
"knock": { "command": "npx", "args": ["-y", "knock", "--mcp"] }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
17
134
|
```
|
|
135
|
+
|
|
136
|
+
| Tool | Does |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `knock_enumerate` | full run (CT + brute force, or `passive: true`), returns the report |
|
|
139
|
+
| `knock_ct_lookup` | Certificate Transparency names only — one HTTPS request, no DNS |
|
|
140
|
+
| `knock_registrable_domain` | offline Public Suffix List lookup |
|
|
141
|
+
|
|
142
|
+
All tools are read-only, return structured JSON, stream progress per found
|
|
143
|
+
host, and honor cancellation. The server is implemented without the MCP SDK,
|
|
144
|
+
so the zero-dependency promise holds.
|
|
145
|
+
|
|
146
|
+
## AI docs
|
|
147
|
+
|
|
148
|
+
[`llms.txt`](llms.txt) is a compact, agent-oriented reference to the whole
|
|
149
|
+
API (library, CLI and MCP tools) and ships in the npm package at
|
|
150
|
+
`node_modules/knock/llms.txt`. Contributors' coding agents get repo
|
|
151
|
+
conventions from [`AGENTS.md`](AGENTS.md).
|
|
152
|
+
|
|
153
|
+
## How it works
|
|
154
|
+
|
|
155
|
+
0. **Public Suffix List** — input like `https://deep.www.example.co.uk/x` is
|
|
156
|
+
first reduced to its registrable domain (`example.co.uk`) using a vendored
|
|
157
|
+
[PSL](https://publicsuffix.org) snapshot (refresh with `npm run
|
|
158
|
+
update-psl`), so private suffixes like `github.io` are handled correctly.
|
|
159
|
+
`--full-host` / `baseDomainOnly: false` scans the host exactly as given,
|
|
160
|
+
and the PSL helpers (`getRegistrableDomain`, `getPublicSuffix`,
|
|
161
|
+
`loadPublicSuffixList`) are exported for standalone use.
|
|
162
|
+
1. **Certificate Transparency** — every publicly trusted TLS certificate is
|
|
163
|
+
logged; querying the logs for `%.example.com` reveals names that were ever
|
|
164
|
+
certified, including ones DNS brute forcing would never guess. Names that
|
|
165
|
+
no longer resolve are reported in `ct.unresolved` — often the most
|
|
166
|
+
interesting ones.
|
|
167
|
+
2. **Wildcard detection** — a few random labels are resolved first; if the
|
|
168
|
+
zone answers for anything, wordlist hits that merely echo the wildcard
|
|
169
|
+
answer are suppressed (CT-sourced names are kept: the certificate is
|
|
170
|
+
evidence on its own).
|
|
171
|
+
3. **Brute force** — wordlist labels are resolved through a bounded worker
|
|
172
|
+
pool using `node:dns` directly against your system resolvers (or servers
|
|
173
|
+
you pick with `-s`).
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
TypeScript sources live in `src/` and run directly on Node's native type
|
|
178
|
+
stripping — `npm test` executes the `.ts` tests with `node --test`, no build
|
|
179
|
+
step needed. `npm run build` compiles `dist/` (what actually ships), and
|
|
180
|
+
`npm run knock -- example.com` runs the CLI from source.
|
|
181
|
+
|
|
182
|
+
## Responsible use
|
|
183
|
+
|
|
184
|
+
DNS brute forcing generates thousands of queries and CT lookups are logged.
|
|
185
|
+
Only scan domains you own or are explicitly authorized to assess.
|
|
186
|
+
|
|
187
|
+
## Publishing
|
|
188
|
+
|
|
189
|
+
Releases are published to npm from GitHub Actions via
|
|
190
|
+
[trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) — no
|
|
191
|
+
long-lived npm tokens — with provenance attestations generated automatically.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT © [Carter Cole](https://github.com/neopunisher)
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { knock, wordlists } from './index.js';
|
|
6
|
+
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
7
|
+
const HELP = `knock ${version} — subdomain enumeration
|
|
8
|
+
|
|
9
|
+
Usage
|
|
10
|
+
knock [options] <domain> [domain ...]
|
|
11
|
+
|
|
12
|
+
Sources
|
|
13
|
+
Certificate Transparency logs (crt.sh, passive) and a wordlist DNS brute
|
|
14
|
+
force run by default; wildcard DNS answers are detected and filtered.
|
|
15
|
+
|
|
16
|
+
Options
|
|
17
|
+
-l, --list <path|subs|org> wordlist file, or a bundled list by name
|
|
18
|
+
(default: "subs", ~31k entries)
|
|
19
|
+
-p, --passive skip the wordlist brute force (CT lookup only)
|
|
20
|
+
--no-ct skip the Certificate Transparency lookup
|
|
21
|
+
--no-verify report CT names without resolving them
|
|
22
|
+
--full-host scan the exact host given, without reducing it
|
|
23
|
+
to the registrable domain (Public Suffix List)
|
|
24
|
+
-c, --concurrency <n> parallel DNS queries (default: 64)
|
|
25
|
+
-t, --timeout <ms> per-query DNS timeout (default: 5000)
|
|
26
|
+
-s, --server <ip> DNS server to use, repeatable
|
|
27
|
+
-6, --ipv6 also resolve AAAA records
|
|
28
|
+
-w, --web probe http/https on found hosts
|
|
29
|
+
--mcp run as an MCP server on stdio (for AI agents)
|
|
30
|
+
-j, --json print the full report as JSON
|
|
31
|
+
-q, --quiet hostnames only, no summary
|
|
32
|
+
-h, --help show this help
|
|
33
|
+
-V, --version print the version
|
|
34
|
+
|
|
35
|
+
Examples
|
|
36
|
+
knock example.com
|
|
37
|
+
knock --passive --json example.com
|
|
38
|
+
knock -l org -s 1.1.1.1 -c 128 example.com
|
|
39
|
+
claude mcp add knock -- npx -y knock --mcp
|
|
40
|
+
|
|
41
|
+
Only scan domains you own or are authorized to assess.`;
|
|
42
|
+
function fail(message) {
|
|
43
|
+
process.stderr.write(`knock: ${message}\n`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
function parseCliArgs() {
|
|
47
|
+
try {
|
|
48
|
+
return parseArgs({
|
|
49
|
+
allowPositionals: true,
|
|
50
|
+
options: {
|
|
51
|
+
list: { type: 'string', short: 'l' },
|
|
52
|
+
passive: { type: 'boolean', short: 'p', default: false },
|
|
53
|
+
'no-ct': { type: 'boolean', default: false },
|
|
54
|
+
'no-verify': { type: 'boolean', default: false },
|
|
55
|
+
'full-host': { type: 'boolean', default: false },
|
|
56
|
+
concurrency: { type: 'string', short: 'c', default: '64' },
|
|
57
|
+
timeout: { type: 'string', short: 't', default: '5000' },
|
|
58
|
+
server: { type: 'string', short: 's', multiple: true },
|
|
59
|
+
ipv6: { type: 'boolean', short: '6', default: false },
|
|
60
|
+
web: { type: 'boolean', short: 'w', default: false },
|
|
61
|
+
mcp: { type: 'boolean', default: false },
|
|
62
|
+
json: { type: 'boolean', short: 'j', default: false },
|
|
63
|
+
quiet: { type: 'boolean', short: 'q', default: false },
|
|
64
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
65
|
+
version: { type: 'boolean', short: 'V', default: false },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const { values: flags, positionals: domains } = parseCliArgs();
|
|
74
|
+
if (flags.help) {
|
|
75
|
+
console.log(HELP);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
if (flags.version) {
|
|
79
|
+
console.log(version);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
if (flags.mcp) {
|
|
83
|
+
const { serveMcp } = await import('./mcp.js');
|
|
84
|
+
await serveMcp();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
if (domains.length === 0) {
|
|
88
|
+
fail('no domain given (try: knock example.com, or --help)');
|
|
89
|
+
}
|
|
90
|
+
const integer = (name, value) => {
|
|
91
|
+
const n = Number(value);
|
|
92
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
93
|
+
fail(`--${name} expects a positive integer, got "${value}"`);
|
|
94
|
+
return n;
|
|
95
|
+
};
|
|
96
|
+
const options = {
|
|
97
|
+
concurrency: integer('concurrency', flags.concurrency),
|
|
98
|
+
timeout: integer('timeout', flags.timeout),
|
|
99
|
+
servers: flags.server,
|
|
100
|
+
family: flags.ipv6 ? 'any' : 4,
|
|
101
|
+
ct: !flags['no-ct'],
|
|
102
|
+
verify: !flags['no-verify'],
|
|
103
|
+
baseDomainOnly: !flags['full-host'],
|
|
104
|
+
web: flags.web,
|
|
105
|
+
};
|
|
106
|
+
if (flags.passive)
|
|
107
|
+
options.words = [];
|
|
108
|
+
if (flags.list === 'subs' || flags.list === 'org')
|
|
109
|
+
options.wordlist = wordlists[flags.list];
|
|
110
|
+
else if (flags.list)
|
|
111
|
+
options.wordlist = flags.list;
|
|
112
|
+
if (flags.passive && !options.ct)
|
|
113
|
+
fail('--passive together with --no-ct leaves nothing to do');
|
|
114
|
+
const sourceTag = (sources) => sources.length > 1 ? 'ct+list' : sources[0] === 'ct' ? 'ct' : 'list';
|
|
115
|
+
const printResult = (result) => {
|
|
116
|
+
if (flags.quiet) {
|
|
117
|
+
console.log(result.name);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const addresses = result.addresses.length > 0 ? result.addresses.join(' ') : '-';
|
|
121
|
+
const web = result.web
|
|
122
|
+
? ` https:${result.web.https ?? '-'} http:${result.web.http ?? '-'}`
|
|
123
|
+
: '';
|
|
124
|
+
console.log(`${result.name} ${addresses} [${sourceTag(result.sources)}]${web}`);
|
|
125
|
+
};
|
|
126
|
+
const reports = [];
|
|
127
|
+
let failed = false;
|
|
128
|
+
for (const domain of domains) {
|
|
129
|
+
try {
|
|
130
|
+
const report = await knock(domain, {
|
|
131
|
+
...options,
|
|
132
|
+
onResult: flags.json ? undefined : printResult,
|
|
133
|
+
});
|
|
134
|
+
reports.push(report);
|
|
135
|
+
if (!flags.json && !flags.quiet) {
|
|
136
|
+
const { stats, wildcard, ct } = report;
|
|
137
|
+
if (wildcard.detected) {
|
|
138
|
+
process.stderr.write(`! wildcard DNS on ${report.domain} (${wildcard.addresses.join(', ')}) — matching wordlist hits suppressed\n`);
|
|
139
|
+
}
|
|
140
|
+
if (ct.error)
|
|
141
|
+
process.stderr.write(`! ct lookup failed: ${ct.error}\n`);
|
|
142
|
+
const ctNote = ct.enabled ? `, ${ct.names} ct names (${ct.unresolved.length} unresolved)` : '';
|
|
143
|
+
process.stderr.write(`# ${report.domain}: ${stats.found} found from ${stats.candidates} candidates${ctNote} in ${(stats.durationMs / 1000).toFixed(1)}s\n`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
failed = true;
|
|
148
|
+
process.stderr.write(`knock: ${domain}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (flags.json && reports.length > 0) {
|
|
152
|
+
console.log(JSON.stringify(reports.length === 1 ? reports[0] : reports, null, 2));
|
|
153
|
+
}
|
|
154
|
+
process.exitCode = failed ? 1 : 0;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { PublicSuffixRules } from './psl.ts';
|
|
2
|
+
export type KnockSource = 'wordlist' | 'ct';
|
|
3
|
+
export interface KnockWebStatuses {
|
|
4
|
+
https: number | null;
|
|
5
|
+
http: number | null;
|
|
6
|
+
}
|
|
7
|
+
export interface KnockResult {
|
|
8
|
+
/** Fully qualified hostname, e.g. "www.example.com". */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Addresses the name resolved to (empty for unverified CT names). */
|
|
11
|
+
addresses: string[];
|
|
12
|
+
/** Where the candidate came from. */
|
|
13
|
+
sources: KnockSource[];
|
|
14
|
+
/** HTTP status codes per protocol; only present when `web: true`. */
|
|
15
|
+
web?: KnockWebStatuses;
|
|
16
|
+
}
|
|
17
|
+
export interface KnockWildcard {
|
|
18
|
+
detected: boolean;
|
|
19
|
+
/** Addresses returned for random non-existent labels. */
|
|
20
|
+
addresses: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface KnockCtReport {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
/** Unique in-scope names found in Certificate Transparency logs. */
|
|
25
|
+
names: number;
|
|
26
|
+
/** CT names that no longer resolve (stale or internal-only certificates). */
|
|
27
|
+
unresolved: string[];
|
|
28
|
+
/** Error message when the CT lookup failed; the run continues without it. */
|
|
29
|
+
error: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface KnockStats {
|
|
32
|
+
words: number;
|
|
33
|
+
candidates: number;
|
|
34
|
+
queried: number;
|
|
35
|
+
found: number;
|
|
36
|
+
errors: number;
|
|
37
|
+
durationMs: number;
|
|
38
|
+
}
|
|
39
|
+
export interface KnockReport {
|
|
40
|
+
domain: string;
|
|
41
|
+
wildcard: KnockWildcard;
|
|
42
|
+
ct: KnockCtReport;
|
|
43
|
+
results: KnockResult[];
|
|
44
|
+
stats: KnockStats;
|
|
45
|
+
}
|
|
46
|
+
/** Minimal resolver surface; satisfied by dns.promises.Resolver or a test double. */
|
|
47
|
+
export interface KnockResolver {
|
|
48
|
+
resolve4(name: string): Promise<string[]>;
|
|
49
|
+
resolve6(name: string): Promise<string[]>;
|
|
50
|
+
}
|
|
51
|
+
/** Minimal fetch surface; satisfied by globalThis.fetch or a test double. */
|
|
52
|
+
export type KnockFetch = (url: string, init: {
|
|
53
|
+
signal: AbortSignal;
|
|
54
|
+
headers: Record<string, string>;
|
|
55
|
+
}) => Promise<{
|
|
56
|
+
ok: boolean;
|
|
57
|
+
status: number;
|
|
58
|
+
json(): Promise<unknown>;
|
|
59
|
+
}>;
|
|
60
|
+
export interface CertSubdomainsOptions {
|
|
61
|
+
/** Request timeout in milliseconds. Default 15000. */
|
|
62
|
+
timeout?: number;
|
|
63
|
+
signal?: AbortSignal;
|
|
64
|
+
/** Injectable fetch implementation, mainly for testing. */
|
|
65
|
+
fetch?: KnockFetch;
|
|
66
|
+
}
|
|
67
|
+
export interface KnockOptions {
|
|
68
|
+
/** Labels to try. Takes precedence over `wordlist`. Pass [] for passive-only runs. */
|
|
69
|
+
words?: string[];
|
|
70
|
+
/** Path to a wordlist file. Defaults to the bundled `wordlists.subs`. */
|
|
71
|
+
wordlist?: string;
|
|
72
|
+
/** Parallel DNS queries. Default 64. */
|
|
73
|
+
concurrency?: number;
|
|
74
|
+
/** Per-query DNS timeout in milliseconds. Default 5000. */
|
|
75
|
+
timeout?: number;
|
|
76
|
+
/** DNS retry attempts per query. Default 2. */
|
|
77
|
+
tries?: number;
|
|
78
|
+
/** DNS servers to query instead of the system resolvers. */
|
|
79
|
+
servers?: string[];
|
|
80
|
+
/** Address family: 4 (A), 6 (AAAA) or 'any' for both. Default 4. */
|
|
81
|
+
family?: 4 | 6 | 'any';
|
|
82
|
+
/** Random probes used to detect wildcard DNS. 0 disables. Default 3. */
|
|
83
|
+
wildcardTests?: number;
|
|
84
|
+
/** Pull candidates from Certificate Transparency logs. Default true. */
|
|
85
|
+
ct?: boolean;
|
|
86
|
+
/** CT request timeout in milliseconds. Default 15000. */
|
|
87
|
+
ctTimeout?: number;
|
|
88
|
+
/** Resolve CT-discovered names over DNS. Default true. */
|
|
89
|
+
verify?: boolean;
|
|
90
|
+
/** Probe http/https on each found host. Default false. */
|
|
91
|
+
web?: boolean;
|
|
92
|
+
/** Per-request web probe timeout in milliseconds. Default 5000. */
|
|
93
|
+
webTimeout?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Reduce the target to its registrable domain via the Public Suffix List
|
|
96
|
+
* before enumerating (so "dev.example.co.uk" is scanned as "example.co.uk").
|
|
97
|
+
* Default true; set false to enumerate under the exact host given.
|
|
98
|
+
*/
|
|
99
|
+
baseDomainOnly?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* When reducing to the registrable domain, use only the ICANN section of the
|
|
102
|
+
* Public Suffix List, ignoring private suffixes like github.io. Default false.
|
|
103
|
+
*/
|
|
104
|
+
icannOnly?: boolean;
|
|
105
|
+
/** Injectable Public Suffix List rules, mainly for testing. */
|
|
106
|
+
psl?: PublicSuffixRules;
|
|
107
|
+
/** Abort the run; knock() rejects with the abort reason. */
|
|
108
|
+
signal?: AbortSignal;
|
|
109
|
+
/** Injectable resolver, mainly for testing. */
|
|
110
|
+
resolver?: KnockResolver;
|
|
111
|
+
/** Injectable fetch implementation used for the CT lookup. */
|
|
112
|
+
fetch?: KnockFetch;
|
|
113
|
+
/** Called with each result as it is discovered. */
|
|
114
|
+
onResult?: (result: KnockResult) => void;
|
|
115
|
+
}
|
|
116
|
+
/** Absolute paths to the wordlists bundled with the package. */
|
|
117
|
+
export declare const wordlists: {
|
|
118
|
+
subs: string;
|
|
119
|
+
org: string;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Reduce user input like "HTTPS://www.Example.com/path" to a bare hostname.
|
|
123
|
+
* Throws TypeError when no usable hostname can be extracted.
|
|
124
|
+
*/
|
|
125
|
+
export declare function normalizeDomain(input: string): string;
|
|
126
|
+
/** Parse wordlist text into unique, lowercased labels. Blank lines and #comments are skipped. */
|
|
127
|
+
export declare function parseWordlist(text: string): string[];
|
|
128
|
+
/** Read and parse a wordlist file. */
|
|
129
|
+
export declare function loadWordlist(path: string): Promise<string[]>;
|
|
130
|
+
/**
|
|
131
|
+
* Query Certificate Transparency logs (via crt.sh) for names certified under
|
|
132
|
+
* `domain`. Purely passive: one HTTPS request, no DNS traffic to the target.
|
|
133
|
+
* Wildcard entries like "*.dev.example.com" are reported as "dev.example.com".
|
|
134
|
+
* Resolves to a sorted array of hostnames, the apex excluded.
|
|
135
|
+
*/
|
|
136
|
+
export declare function certSubdomains(domain: string, options?: CertSubdomainsOptions): Promise<string[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Enumerate subdomains of `domain`.
|
|
139
|
+
*
|
|
140
|
+
* Two sources feed the candidate set: Certificate Transparency logs
|
|
141
|
+
* (passive, `ct` option) and a wordlist brute force over DNS. Candidates
|
|
142
|
+
* are resolved concurrently; hosts that only echo a wildcard DNS answer are
|
|
143
|
+
* suppressed unless Certificate Transparency vouches for them.
|
|
144
|
+
*/
|
|
145
|
+
export declare function knock(domain: string, options?: KnockOptions): Promise<KnockReport>;
|
|
146
|
+
export { publicSuffixListPath, parsePublicSuffixList, loadPublicSuffixList, getPublicSuffix, getRegistrableDomain, } from './psl.ts';
|
|
147
|
+
export type { PublicSuffixRule, PublicSuffixRules, PublicSuffixOptions } from './psl.ts';
|
|
148
|
+
export default knock;
|