dio-lookup 0.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 +59 -0
- package/dio-lookup.ts +148 -0
- package/package.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 disclose.io
|
|
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
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# dio-lookup
|
|
2
|
+
|
|
3
|
+
Pipe internet assets to [lookup.disclose.io](https://lookup.disclose.io) and get the right **security-disclosure contact** for each — as JSONL, built for recon pipelines.
|
|
4
|
+
|
|
5
|
+
Give it a domain, IP, ASN, URL, email, package, repo, container, cloud resource, mobile app, hardware, browser extension, or org name; get back the owner and where to report a vulnerability (security.txt, bug bounty program, VDP, PSIRT, national CERT).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
subfinder -d example.com | httpx -silent | dio-lookup > contacts.jsonl
|
|
9
|
+
cat hosts.txt | dio-lookup -c 8
|
|
10
|
+
dio-lookup cloudflare.com npm:express gh:facebook/react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
**With [Bun](https://bun.sh) (run from source):**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun install -g dio-lookup # or: git clone + bun link
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Prebuilt binary** (no runtime needed) — grab the right asset from [Releases](https://github.com/disclose/dio-lookup/releases), then:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
chmod +x dio-lookup && sudo mv dio-lookup /usr/local/bin/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
dio-lookup [options] [asset ...]
|
|
31
|
+
cat hosts.txt | dio-lookup [options]
|
|
32
|
+
|
|
33
|
+
-c, --concurrency N parallel requests (default 5)
|
|
34
|
+
-k, --key KEY API key (raises rate limits); or set DIO_API_KEY
|
|
35
|
+
--api URL API endpoint (default https://lookup.disclose.io/api/lookup)
|
|
36
|
+
--full emit the full LookupResult instead of the compact summary
|
|
37
|
+
-V, --version print version
|
|
38
|
+
-h, --help help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
One JSON object per asset on stdout. Compact form:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{"input":"cloudflare.com","assetType":"domain","status":"complete","organization":"Cloudflare","jurisdiction":"US","contacts":[{"type":"security_txt","value":"https://www.cloudflare.com/.well-known/security.txt","confidence":"high"}]}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Pull just the reporting channels with `jq`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cat hosts.txt | dio-lookup | jq -r 'select(.status=="complete") | "\(.input)\t\(.contacts[0].value)"'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Notes
|
|
54
|
+
|
|
55
|
+
- **Free and anonymous.** A free API key only raises rate limits — request one by emailing [hello@disclose.io](mailto:hello@disclose.io). Pass it with `-k` or `DIO_API_KEY`.
|
|
56
|
+
- Honors the API's `Retry-After` on 429 and retries transient failures.
|
|
57
|
+
- `--full` emits the complete `LookupResult` (attribution, contacts, resolution chain, data sources) — see the [OpenAPI spec](https://lookup.disclose.io/openapi.yaml).
|
|
58
|
+
|
|
59
|
+
A [disclose.io](https://disclose.io) project. MIT licensed.
|
package/dio-lookup.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* dio-lookup — pipe internet assets to lookup.disclose.io, get security
|
|
4
|
+
* disclosure contacts back as JSONL.
|
|
5
|
+
*
|
|
6
|
+
* Built for recon pipelines:
|
|
7
|
+
* subfinder -d example.com | httpx -silent | dio-lookup
|
|
8
|
+
* cat hosts.txt | dio-lookup --concurrency 8 > contacts.jsonl
|
|
9
|
+
* dio-lookup cloudflare.com npm:express gh:facebook/react
|
|
10
|
+
*
|
|
11
|
+
* Each input asset (domain, IP, ASN, URL, email, package, repo, container,
|
|
12
|
+
* cloud resource, mobile app, hardware, extension, org name) becomes one JSON
|
|
13
|
+
* object on stdout. Anonymous + free; an optional API key raises rate limits.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const VERSION = '0.1.0';
|
|
17
|
+
const DEFAULT_API = 'https://lookup.disclose.io/api/lookup';
|
|
18
|
+
|
|
19
|
+
interface Options {
|
|
20
|
+
concurrency: number;
|
|
21
|
+
api: string;
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
full: boolean;
|
|
24
|
+
inputs: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function parseArgs(argv: string[]): Options | { help: true } | { version: true } {
|
|
28
|
+
const o: Options = { concurrency: 5, api: DEFAULT_API, full: false, inputs: [] };
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const a = argv[i];
|
|
31
|
+
if (a === '-h' || a === '--help') return { help: true };
|
|
32
|
+
if (a === '-V' || a === '--version') return { version: true };
|
|
33
|
+
else if (a === '-c' || a === '--concurrency') o.concurrency = Math.max(1, parseInt(argv[++i] ?? '5', 10) || 5);
|
|
34
|
+
else if (a === '--api') o.api = argv[++i] ?? DEFAULT_API;
|
|
35
|
+
else if (a === '-k' || a === '--key') o.apiKey = argv[++i];
|
|
36
|
+
else if (a === '--full') o.full = true;
|
|
37
|
+
else if (a.startsWith('-')) { process.stderr.write(`unknown flag: ${a}\n`); return { help: true }; }
|
|
38
|
+
else o.inputs.push(a);
|
|
39
|
+
}
|
|
40
|
+
return o;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const HELP = `dio-lookup ${VERSION} — security-disclosure contacts for any internet asset
|
|
44
|
+
|
|
45
|
+
USAGE
|
|
46
|
+
dio-lookup [options] [asset ...]
|
|
47
|
+
cat hosts.txt | dio-lookup [options]
|
|
48
|
+
|
|
49
|
+
OPTIONS
|
|
50
|
+
-c, --concurrency N parallel requests (default 5)
|
|
51
|
+
-k, --key KEY API key (raises rate limits); or set DIO_API_KEY
|
|
52
|
+
--api URL API endpoint (default ${DEFAULT_API})
|
|
53
|
+
--full emit the full LookupResult instead of the compact summary
|
|
54
|
+
-V, --version print version
|
|
55
|
+
-h, --help this help
|
|
56
|
+
|
|
57
|
+
OUTPUT
|
|
58
|
+
One JSON object per asset on stdout (JSONL). Compact form:
|
|
59
|
+
{"input","assetType","status","organization","jurisdiction","contacts":[{type,value,confidence}]}
|
|
60
|
+
|
|
61
|
+
EXAMPLES
|
|
62
|
+
dio-lookup cloudflare.com
|
|
63
|
+
subfinder -d example.com | httpx -silent | dio-lookup -c 8 > contacts.jsonl
|
|
64
|
+
echo npm:express | dio-lookup --full | jq .
|
|
65
|
+
|
|
66
|
+
A disclose.io project — https://lookup.disclose.io`;
|
|
67
|
+
|
|
68
|
+
async function readStdinLines(): Promise<string[]> {
|
|
69
|
+
if (process.stdin.isTTY) return [];
|
|
70
|
+
const text = await new Response(Bun.stdin.stream()).text();
|
|
71
|
+
return text.split('\n').map(l => l.trim()).filter(Boolean);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface Contact { type: string; value: string; confidence: string }
|
|
75
|
+
interface LookupResult {
|
|
76
|
+
input: string; assetType?: string; status?: string;
|
|
77
|
+
attribution?: { organization?: string; jurisdiction?: string };
|
|
78
|
+
contacts?: Contact[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function lookupOne(input: string, o: Options): Promise<Record<string, unknown>> {
|
|
82
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json', 'User-Agent': `dio-lookup/${VERSION}` };
|
|
83
|
+
const key = o.apiKey ?? process.env.DIO_API_KEY;
|
|
84
|
+
if (key) headers['Authorization'] = `Bearer ${key}`;
|
|
85
|
+
|
|
86
|
+
// Up to 3 attempts, honoring Retry-After on 429.
|
|
87
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(o.api, { method: 'POST', headers, body: JSON.stringify({ input }) });
|
|
90
|
+
if (res.status === 429) {
|
|
91
|
+
const wait = Math.min(30, parseInt(res.headers.get('retry-after') ?? '2', 10) || 2);
|
|
92
|
+
await Bun.sleep(wait * 1000);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const body = await res.json() as LookupResult;
|
|
96
|
+
if (o.full) return body as unknown as Record<string, unknown>;
|
|
97
|
+
return {
|
|
98
|
+
input,
|
|
99
|
+
assetType: body.assetType ?? null,
|
|
100
|
+
status: body.status ?? null,
|
|
101
|
+
organization: body.attribution?.organization ?? null,
|
|
102
|
+
jurisdiction: body.attribution?.jurisdiction ?? null,
|
|
103
|
+
contacts: (body.contacts ?? []).map(c => ({ type: c.type, value: c.value, confidence: c.confidence })),
|
|
104
|
+
};
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (attempt === 2) return { input, error: String(err).slice(0, 200) };
|
|
107
|
+
await Bun.sleep(1000);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { input, error: 'exhausted retries' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Bounded-concurrency worker pool over the input list, preserving nothing about
|
|
114
|
+
// order (recon pipelines don't need it) — emit as each completes.
|
|
115
|
+
export async function run(inputs: string[], o: Options): Promise<number> {
|
|
116
|
+
let idx = 0;
|
|
117
|
+
let failures = 0;
|
|
118
|
+
const out = (obj: Record<string, unknown>) => {
|
|
119
|
+
if (obj.error) failures++;
|
|
120
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
121
|
+
};
|
|
122
|
+
const worker = async () => {
|
|
123
|
+
while (idx < inputs.length) {
|
|
124
|
+
const i = idx++;
|
|
125
|
+
out(await lookupOne(inputs[i], o));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
await Promise.all(Array.from({ length: Math.min(o.concurrency, inputs.length) }, worker));
|
|
129
|
+
return failures;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { VERSION, HELP };
|
|
133
|
+
export type { Options, LookupResult, Contact };
|
|
134
|
+
|
|
135
|
+
// Only run the CLI when executed directly (`bun dio-lookup.ts`), not when imported
|
|
136
|
+
// by the test suite. This keeps parseArgs/lookupOne/run unit-testable in isolation.
|
|
137
|
+
if (import.meta.main) {
|
|
138
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
139
|
+
if ('help' in parsed) { console.log(HELP); process.exit(0); }
|
|
140
|
+
if ('version' in parsed) { console.log(VERSION); process.exit(0); }
|
|
141
|
+
|
|
142
|
+
const stdinInputs = await readStdinLines();
|
|
143
|
+
const inputs = [...parsed.inputs, ...stdinInputs];
|
|
144
|
+
if (inputs.length === 0) { console.error('no input assets (pass as args or pipe via stdin); --help for usage'); process.exit(2); }
|
|
145
|
+
|
|
146
|
+
const failures = await run(inputs, parsed);
|
|
147
|
+
process.exit(failures > 0 && failures === inputs.length ? 1 : 0);
|
|
148
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dio-lookup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pipe internet assets to lookup.disclose.io and get security-disclosure contacts as JSONL.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dio-lookup": "./dio-lookup.ts"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dio-lookup.ts"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "bun dio-lookup.ts",
|
|
14
|
+
"build": "bun build ./dio-lookup.ts --compile --outfile dist/dio-lookup"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"bun": ">=1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://lookup.disclose.io",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/disclose/dio-lookup.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": ["security", "vulnerability-disclosure", "security.txt", "recon", "bug-bounty", "disclose.io", "osint"]
|
|
26
|
+
}
|