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/llms.txt
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# knock
|
|
2
|
+
|
|
3
|
+
> Subdomain enumeration for Node.js (>= 22), as a library, a CLI, and an MCP server. Combines Certificate Transparency logs (passive, via crt.sh) with a wordlist DNS brute force (bundled ~31k labels), detects and filters wildcard DNS, and verifies names with real lookups. ESM-only, TypeScript types included, zero runtime dependencies. Only scan domains you own or are authorized to assess.
|
|
4
|
+
|
|
5
|
+
Install with `npm install knock` (library) or run `npx knock example.com` (CLI). Targets are reduced to their registrable domain via a bundled Public Suffix List before scanning ("https://a.www.example.co.uk/x" -> "example.co.uk") unless told otherwise.
|
|
6
|
+
|
|
7
|
+
## Library
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { knock, certSubdomains } from 'knock';
|
|
11
|
+
|
|
12
|
+
// Full run: CT + wordlist brute force. Takes ~30-90s with the default wordlist.
|
|
13
|
+
const report = await knock('example.com', {
|
|
14
|
+
concurrency: 64, // parallel DNS queries
|
|
15
|
+
onResult: (r) => console.log(r.name), // streamed as found
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Passive only: CT names, verified over DNS, no brute force.
|
|
19
|
+
await knock('example.com', { words: [] });
|
|
20
|
+
|
|
21
|
+
// Just the CT names, unverified: one HTTPS request, no DNS.
|
|
22
|
+
const names = await certSubdomains('example.com'); // string[], sorted, apex excluded
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`knock(domain, options?)` resolves to a `KnockReport`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
{
|
|
29
|
+
domain: string; // the domain actually scanned
|
|
30
|
+
results: { name: string; addresses: string[]; sources: ('wordlist' | 'ct')[];
|
|
31
|
+
web?: { https: number | null; http: number | null } }[];
|
|
32
|
+
wildcard: { detected: boolean; addresses: string[] };
|
|
33
|
+
ct: { enabled: boolean; names: number; unresolved: string[]; error: string | null };
|
|
34
|
+
stats: { words: number; candidates: number; queried: number; found: number;
|
|
35
|
+
errors: number; durationMs: number };
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Options (all optional): `words` (string[], overrides `wordlist`; `[]` = passive), `wordlist` (file path; default `wordlists.subs`), `ct` (true), `ctTimeout` (15000), `verify` (true; resolve CT names), `concurrency` (64), `timeout` (5000 ms per DNS query), `tries` (2), `servers` (DNS server IPs), `family` (4 | 6 | 'any'; default 4), `wildcardTests` (3; 0 disables), `web` (false; HEAD http/https on found hosts), `webTimeout` (5000), `baseDomainOnly` (true), `icannOnly` (false; ignore private PSL suffixes like github.io), `signal` (AbortSignal), `onResult` (callback), and test injectables `resolver`, `fetch`, `psl`.
|
|
40
|
+
|
|
41
|
+
Behavior worth knowing:
|
|
42
|
+
- A CT lookup failure does not reject; it is reported in `report.ct.error` and the run continues with the wordlist.
|
|
43
|
+
- Wordlist hits that only return the wildcard DNS answer are dropped; CT-sourced names are kept even under a wildcard.
|
|
44
|
+
- `report.ct.unresolved` lists CT names that no longer resolve (stale or internal hosts).
|
|
45
|
+
- `knock()` rejects with the abort reason when `signal` aborts, and with a `TypeError` for unusable input.
|
|
46
|
+
|
|
47
|
+
Other exports: `normalizeDomain(input)`, `parseWordlist(text)`, `loadWordlist(path)`, `wordlists` (`{ subs, org }` absolute paths), `getRegistrableDomain(host, rules, { icannOnly })`, `getPublicSuffix(host, rules, opts)`, `loadPublicSuffixList(path?)`, `parsePublicSuffixList(text)`, `publicSuffixListPath`, and all the types (`KnockOptions`, `KnockReport`, `KnockResult`, ...). Default export is `knock`.
|
|
48
|
+
|
|
49
|
+
## CLI
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
knock [options] <domain> [domain ...]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`-p/--passive` (CT only), `--no-ct`, `--no-verify`, `--full-host` (skip registrable-domain reduction), `-l/--list <path|subs|org>`, `-c/--concurrency <n>`, `-t/--timeout <ms>`, `-s/--server <ip>` (repeatable), `-6/--ipv6`, `-w/--web`, `-j/--json` (full report), `-q/--quiet` (hostnames only), `--mcp`. Results go to stdout, diagnostics to stderr; exit code 1 if any domain failed.
|
|
56
|
+
|
|
57
|
+
## MCP server
|
|
58
|
+
|
|
59
|
+
`knock --mcp` serves the Model Context Protocol over stdio. Register it with, for example, `claude mcp add knock -- npx -y knock --mcp`, or in a JSON client config: `{"command": "npx", "args": ["-y", "knock", "--mcp"]}`.
|
|
60
|
+
|
|
61
|
+
Tools (all read-only; results include `structuredContent` plus a JSON text block):
|
|
62
|
+
- `knock_enumerate` — full `knock()` run. Args: `domain` (required), `passive`, `ct`, `verify`, `fullHost`, `wordlist` ("subs" | "org"), `words`, `concurrency`, `timeout`, `servers`, `ipv6`, `web`. Returns the `KnockReport`. Sends progress notifications per found host when the call carries a progress token, and supports cancellation.
|
|
63
|
+
- `knock_ct_lookup` — `certSubdomains()`. Args: `domain`, `timeout`. Returns `{ domain, names }`.
|
|
64
|
+
- `knock_registrable_domain` — offline PSL lookup. Args: `host`, `icannOnly`. Returns `{ host, publicSuffix, registrableDomain }`.
|
|
65
|
+
|
|
66
|
+
## Links
|
|
67
|
+
|
|
68
|
+
- [README](https://github.com/neopunisher/node-knock#readme): full documentation
|
|
69
|
+
- [Changelog](https://github.com/neopunisher/node-knock/blob/main/CHANGELOG.md)
|
|
70
|
+
- [npm](https://www.npmjs.com/package/knock)
|
package/package.json
CHANGED
|
@@ -1,29 +1,63 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "knock",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
2
|
+
"name": "knock",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Knock, knock. Who's there? Subdomain enumeration via Certificate Transparency and wordlist DNS brute force, with wildcard detection. CLI, library and MCP server. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
5
6
|
"domain",
|
|
6
7
|
"subdomain",
|
|
7
8
|
"dns",
|
|
9
|
+
"enumeration",
|
|
10
|
+
"recon",
|
|
11
|
+
"osint",
|
|
12
|
+
"pentest",
|
|
13
|
+
"security",
|
|
8
14
|
"util",
|
|
9
|
-
"utility"
|
|
15
|
+
"utility",
|
|
16
|
+
"mcp",
|
|
17
|
+
"mcp-server",
|
|
18
|
+
"model-context-protocol"
|
|
10
19
|
],
|
|
11
|
-
"author":
|
|
12
|
-
"
|
|
13
|
-
"homepage": "
|
|
20
|
+
"author": "Carter Cole <node@cartercole.com>",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"homepage": "https://github.com/neopunisher/node-knock#readme",
|
|
14
23
|
"repository": {
|
|
15
|
-
"type": "git",
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/neopunisher/node-knock.git"
|
|
16
26
|
},
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/neopunisher/node-knock/issues"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"bin": {
|
|
41
|
+
"knock": "dist/cli.js"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist/",
|
|
45
|
+
"lists/",
|
|
46
|
+
"llms.txt"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
53
|
+
"typecheck": "tsc",
|
|
54
|
+
"test": "node --test \"test/**/*.test.ts\"",
|
|
55
|
+
"knock": "node src/cli.ts",
|
|
56
|
+
"update-psl": "node scripts/update-psl.mjs",
|
|
57
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^22.20.4",
|
|
61
|
+
"typescript": "^7.0.2"
|
|
28
62
|
}
|
|
29
63
|
}
|
package/.npmignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
node_modules/*
|
package/knock.js
DELETED
package/lib.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
var _ = require('underscore')._,
|
|
2
|
-
glob = require('glob'),
|
|
3
|
-
tldtools = require('tldtools').init(function () {
|
|
4
|
-
//console.log('tldsready')
|
|
5
|
-
}),
|
|
6
|
-
dns = require('dns'),
|
|
7
|
-
request = require('request'),
|
|
8
|
-
Step = require('step'),
|
|
9
|
-
fs = require('fs'),
|
|
10
|
-
dnstest = function () {},
|
|
11
|
-
webtest = function () {},
|
|
12
|
-
basedomain = function (domain) {
|
|
13
|
-
if (domain.indexOf('http') != 0) {
|
|
14
|
-
domain = 'http://' + domain + '/'
|
|
15
|
-
}
|
|
16
|
-
var res = tldtools.extract(domain);
|
|
17
|
-
return res.inspect.useful() ? res.domain + '.' + res.tld : false;
|
|
18
|
-
}
|
|
19
|
-
module.exports.opts = {
|
|
20
|
-
type: dnstest,
|
|
21
|
-
types: [dnstest, webtest],
|
|
22
|
-
verify: true,
|
|
23
|
-
wildcard: {
|
|
24
|
-
tests: 1,
|
|
25
|
-
neverthere: 'knocknpmknocknpm'
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
//dns.resolve4(domain, cb);
|
|
30
|
-
|
|
31
|
-
module.exports = function knock(domain, opts, cb) {
|
|
32
|
-
if (_.isFunction(opts)) {
|
|
33
|
-
console.log('swaped for cb', module.exports.opts);
|
|
34
|
-
cb = opts;
|
|
35
|
-
opts = module.export.opts
|
|
36
|
-
} else if (_.isObject(opts)) {
|
|
37
|
-
opts = _.extend(module.exports.opts, opts)
|
|
38
|
-
console.log('extended', opts)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
var domain = basedomain(domain);
|
|
42
|
-
if (domain === false) {
|
|
43
|
-
cb('invalid domain');
|
|
44
|
-
} else {
|
|
45
|
-
console.log('basedomain', domain);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|