company-dossier 0.2.0 → 0.2.1

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/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { promises as dns } from "dns";
6
6
  // src/utils.ts
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
+ import { lookup } from "dns/promises";
9
10
  var USER_AGENT = "company-dossier/0.1 (+https://companydossier.lol)";
10
11
  function mkdirp(dirPath) {
11
12
  if (!fs.existsSync(dirPath)) {
@@ -22,18 +23,67 @@ function todayISO() {
22
23
  function titleCase(str) {
23
24
  return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
24
25
  }
26
+ function isPrivateIp(ip) {
27
+ const v = ip.replace(/^\[|\]$/g, "").toLowerCase();
28
+ if (v.includes(":")) {
29
+ if (v === "::1" || v === "::") return true;
30
+ if (v.startsWith("fe80") || v.startsWith("fc") || v.startsWith("fd")) return true;
31
+ const m = v.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
32
+ if (m) return isPrivateIp(m[1]);
33
+ return false;
34
+ }
35
+ const p = v.split(".").map(Number);
36
+ if (p.length !== 4 || p.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
37
+ const [a, b] = p;
38
+ if (a === 10 || a === 127 || a === 0) return true;
39
+ if (a === 172 && b >= 16 && b <= 31) return true;
40
+ if (a === 192 && b === 168) return true;
41
+ if (a === 169 && b === 254) return true;
42
+ if (a === 100 && b >= 64 && b <= 127) return true;
43
+ if (a === 192 && b === 0) return true;
44
+ return false;
45
+ }
46
+ async function assertPublicUrl(url) {
47
+ let u;
48
+ try {
49
+ u = new URL(url);
50
+ } catch {
51
+ throw new Error("Invalid URL");
52
+ }
53
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`Blocked protocol: ${u.protocol}`);
54
+ const host = u.hostname.toLowerCase();
55
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
56
+ throw new Error(`Blocked host: ${host}`);
57
+ }
58
+ if (/^[0-9.]+$/.test(host) || host.includes(":")) {
59
+ if (isPrivateIp(host)) throw new Error(`Blocked private address: ${host}`);
60
+ return;
61
+ }
62
+ const addrs = await lookup(host, { all: true });
63
+ for (const a of addrs) if (isPrivateIp(a.address)) throw new Error(`Blocked private address for ${host}: ${a.address}`);
64
+ }
25
65
  async function fetchText(url, timeoutMs = 1e4) {
26
66
  const controller = new AbortController();
27
67
  const timer = setTimeout(() => controller.abort(), timeoutMs);
28
68
  try {
29
- const resp = await fetch(url, {
30
- signal: controller.signal,
31
- headers: { "User-Agent": USER_AGENT }
32
- });
33
- if (!resp.ok) {
34
- throw new Error(`HTTP ${resp.status}`);
69
+ let current = url;
70
+ for (let hop = 0; hop < 5; hop++) {
71
+ await assertPublicUrl(current);
72
+ const resp = await fetch(current, {
73
+ signal: controller.signal,
74
+ redirect: "manual",
75
+ headers: { "User-Agent": USER_AGENT }
76
+ });
77
+ if (resp.status >= 300 && resp.status < 400) {
78
+ const loc = resp.headers.get("location");
79
+ if (!loc) throw new Error(`HTTP ${resp.status} without Location`);
80
+ current = new URL(loc, current).toString();
81
+ continue;
82
+ }
83
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
84
+ return await resp.text();
35
85
  }
36
- return await resp.text();
86
+ throw new Error("Too many redirects");
37
87
  } finally {
38
88
  clearTimeout(timer);
39
89
  }
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import * as path2 from "path";
4
4
  // src/utils.ts
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
+ import { lookup } from "dns/promises";
7
8
  var USER_AGENT = "company-dossier/0.1 (+https://companydossier.lol)";
8
9
  function mkdirp(dirPath) {
9
10
  if (!fs.existsSync(dirPath)) {
@@ -23,18 +24,67 @@ function slugify(name) {
23
24
  function titleCase(str) {
24
25
  return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25
26
  }
27
+ function isPrivateIp(ip) {
28
+ const v = ip.replace(/^\[|\]$/g, "").toLowerCase();
29
+ if (v.includes(":")) {
30
+ if (v === "::1" || v === "::") return true;
31
+ if (v.startsWith("fe80") || v.startsWith("fc") || v.startsWith("fd")) return true;
32
+ const m = v.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
33
+ if (m) return isPrivateIp(m[1]);
34
+ return false;
35
+ }
36
+ const p = v.split(".").map(Number);
37
+ if (p.length !== 4 || p.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
38
+ const [a, b] = p;
39
+ if (a === 10 || a === 127 || a === 0) return true;
40
+ if (a === 172 && b >= 16 && b <= 31) return true;
41
+ if (a === 192 && b === 168) return true;
42
+ if (a === 169 && b === 254) return true;
43
+ if (a === 100 && b >= 64 && b <= 127) return true;
44
+ if (a === 192 && b === 0) return true;
45
+ return false;
46
+ }
47
+ async function assertPublicUrl(url) {
48
+ let u;
49
+ try {
50
+ u = new URL(url);
51
+ } catch {
52
+ throw new Error("Invalid URL");
53
+ }
54
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`Blocked protocol: ${u.protocol}`);
55
+ const host = u.hostname.toLowerCase();
56
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
57
+ throw new Error(`Blocked host: ${host}`);
58
+ }
59
+ if (/^[0-9.]+$/.test(host) || host.includes(":")) {
60
+ if (isPrivateIp(host)) throw new Error(`Blocked private address: ${host}`);
61
+ return;
62
+ }
63
+ const addrs = await lookup(host, { all: true });
64
+ for (const a of addrs) if (isPrivateIp(a.address)) throw new Error(`Blocked private address for ${host}: ${a.address}`);
65
+ }
26
66
  async function fetchText(url, timeoutMs = 1e4) {
27
67
  const controller = new AbortController();
28
68
  const timer = setTimeout(() => controller.abort(), timeoutMs);
29
69
  try {
30
- const resp = await fetch(url, {
31
- signal: controller.signal,
32
- headers: { "User-Agent": USER_AGENT }
33
- });
34
- if (!resp.ok) {
35
- throw new Error(`HTTP ${resp.status}`);
70
+ let current = url;
71
+ for (let hop = 0; hop < 5; hop++) {
72
+ await assertPublicUrl(current);
73
+ const resp = await fetch(current, {
74
+ signal: controller.signal,
75
+ redirect: "manual",
76
+ headers: { "User-Agent": USER_AGENT }
77
+ });
78
+ if (resp.status >= 300 && resp.status < 400) {
79
+ const loc = resp.headers.get("location");
80
+ if (!loc) throw new Error(`HTTP ${resp.status} without Location`);
81
+ current = new URL(loc, current).toString();
82
+ continue;
83
+ }
84
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
85
+ return await resp.text();
36
86
  }
37
- return await resp.text();
87
+ throw new Error("Too many redirects");
38
88
  } finally {
39
89
  clearTimeout(timer);
40
90
  }
package/dist/mcp-http.js CHANGED
@@ -16,6 +16,7 @@ import { promises as dns } from "dns";
16
16
  // src/utils.ts
17
17
  import * as fs from "fs";
18
18
  import * as path from "path";
19
+ import { lookup } from "dns/promises";
19
20
  var USER_AGENT = "company-dossier/0.1 (+https://companydossier.lol)";
20
21
  function todayISO() {
21
22
  return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -23,18 +24,67 @@ function todayISO() {
23
24
  function titleCase(str) {
24
25
  return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25
26
  }
27
+ function isPrivateIp(ip) {
28
+ const v = ip.replace(/^\[|\]$/g, "").toLowerCase();
29
+ if (v.includes(":")) {
30
+ if (v === "::1" || v === "::") return true;
31
+ if (v.startsWith("fe80") || v.startsWith("fc") || v.startsWith("fd")) return true;
32
+ const m = v.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
33
+ if (m) return isPrivateIp(m[1]);
34
+ return false;
35
+ }
36
+ const p = v.split(".").map(Number);
37
+ if (p.length !== 4 || p.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
38
+ const [a, b] = p;
39
+ if (a === 10 || a === 127 || a === 0) return true;
40
+ if (a === 172 && b >= 16 && b <= 31) return true;
41
+ if (a === 192 && b === 168) return true;
42
+ if (a === 169 && b === 254) return true;
43
+ if (a === 100 && b >= 64 && b <= 127) return true;
44
+ if (a === 192 && b === 0) return true;
45
+ return false;
46
+ }
47
+ async function assertPublicUrl(url) {
48
+ let u;
49
+ try {
50
+ u = new URL(url);
51
+ } catch {
52
+ throw new Error("Invalid URL");
53
+ }
54
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`Blocked protocol: ${u.protocol}`);
55
+ const host = u.hostname.toLowerCase();
56
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
57
+ throw new Error(`Blocked host: ${host}`);
58
+ }
59
+ if (/^[0-9.]+$/.test(host) || host.includes(":")) {
60
+ if (isPrivateIp(host)) throw new Error(`Blocked private address: ${host}`);
61
+ return;
62
+ }
63
+ const addrs = await lookup(host, { all: true });
64
+ for (const a of addrs) if (isPrivateIp(a.address)) throw new Error(`Blocked private address for ${host}: ${a.address}`);
65
+ }
26
66
  async function fetchText(url, timeoutMs = 1e4) {
27
67
  const controller = new AbortController();
28
68
  const timer = setTimeout(() => controller.abort(), timeoutMs);
29
69
  try {
30
- const resp = await fetch(url, {
31
- signal: controller.signal,
32
- headers: { "User-Agent": USER_AGENT }
33
- });
34
- if (!resp.ok) {
35
- throw new Error(`HTTP ${resp.status}`);
70
+ let current = url;
71
+ for (let hop = 0; hop < 5; hop++) {
72
+ await assertPublicUrl(current);
73
+ const resp = await fetch(current, {
74
+ signal: controller.signal,
75
+ redirect: "manual",
76
+ headers: { "User-Agent": USER_AGENT }
77
+ });
78
+ if (resp.status >= 300 && resp.status < 400) {
79
+ const loc = resp.headers.get("location");
80
+ if (!loc) throw new Error(`HTTP ${resp.status} without Location`);
81
+ current = new URL(loc, current).toString();
82
+ continue;
83
+ }
84
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
85
+ return await resp.text();
36
86
  }
37
- return await resp.text();
87
+ throw new Error("Too many redirects");
38
88
  } finally {
39
89
  clearTimeout(timer);
40
90
  }
@@ -1396,10 +1446,17 @@ function createServer() {
1396
1446
 
1397
1447
  // src/mcp-http.ts
1398
1448
  var PORT = Number(process.env.PORT) || 8787;
1449
+ var HOST = process.env.HOST || "0.0.0.0";
1450
+ var AUTH_TOKEN = process.env.COMPANY_DOSSIER_MCP_TOKEN || "";
1451
+ var ALLOWED_ORIGINS = (process.env.COMPANY_DOSSIER_MCP_ALLOWED_ORIGINS || "").split(",").map((s) => s.trim()).filter(Boolean);
1399
1452
  var transports = /* @__PURE__ */ new Map();
1400
1453
  function applyCors(req, res) {
1401
1454
  const origin = req.headers.origin;
1402
- res.setHeader("Access-Control-Allow-Origin", origin || "*");
1455
+ if (ALLOWED_ORIGINS.length) {
1456
+ if (origin && ALLOWED_ORIGINS.includes(origin)) res.setHeader("Access-Control-Allow-Origin", origin);
1457
+ } else {
1458
+ res.setHeader("Access-Control-Allow-Origin", origin || "*");
1459
+ }
1403
1460
  res.setHeader("Vary", "Origin");
1404
1461
  res.setHeader(
1405
1462
  "Access-Control-Allow-Methods",
@@ -1498,6 +1555,13 @@ var httpServer = createHttpServer((req, res) => {
1498
1555
  return;
1499
1556
  }
1500
1557
  if (pathname === "/mcp") {
1558
+ if (AUTH_TOKEN) {
1559
+ const auth = req.headers.authorization || "";
1560
+ if (auth !== `Bearer ${AUTH_TOKEN}`) {
1561
+ sendJson(res, 401, { jsonrpc: "2.0", error: { code: -32001, message: "Unauthorized" }, id: null });
1562
+ return;
1563
+ }
1564
+ }
1501
1565
  if (req.method === "POST") {
1502
1566
  await handleMcpPost(req, res);
1503
1567
  return;
@@ -1528,10 +1592,11 @@ var httpServer = createHttpServer((req, res) => {
1528
1592
  }
1529
1593
  })();
1530
1594
  });
1531
- httpServer.listen(PORT, () => {
1595
+ httpServer.listen(PORT, HOST, () => {
1532
1596
  process.stdout.write(
1533
- `company-dossier remote MCP server listening on http://0.0.0.0:${PORT}
1534
- MCP endpoint: POST/GET/DELETE /mcp
1597
+ `company-dossier remote MCP server listening on http://${HOST}:${PORT}
1598
+ MCP endpoint: POST/GET/DELETE /mcp${AUTH_TOKEN ? " (bearer auth required)" : " (no auth \u2014 set COMPANY_DOSSIER_MCP_TOKEN)"}
1599
+ CORS: ${ALLOWED_ORIGINS.length ? ALLOWED_ORIGINS.join(", ") : "reflect (set COMPANY_DOSSIER_MCP_ALLOWED_ORIGINS to restrict)"}
1535
1600
  Health check: GET /health
1536
1601
  `
1537
1602
  );
@@ -8,6 +8,7 @@ import { promises as dns } from "dns";
8
8
  // src/utils.ts
9
9
  import * as fs from "fs";
10
10
  import * as path from "path";
11
+ import { lookup } from "dns/promises";
11
12
  var USER_AGENT = "company-dossier/0.1 (+https://companydossier.lol)";
12
13
  function todayISO() {
13
14
  return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -15,18 +16,67 @@ function todayISO() {
15
16
  function titleCase(str) {
16
17
  return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
17
18
  }
19
+ function isPrivateIp(ip) {
20
+ const v = ip.replace(/^\[|\]$/g, "").toLowerCase();
21
+ if (v.includes(":")) {
22
+ if (v === "::1" || v === "::") return true;
23
+ if (v.startsWith("fe80") || v.startsWith("fc") || v.startsWith("fd")) return true;
24
+ const m = v.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
25
+ if (m) return isPrivateIp(m[1]);
26
+ return false;
27
+ }
28
+ const p = v.split(".").map(Number);
29
+ if (p.length !== 4 || p.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
30
+ const [a, b] = p;
31
+ if (a === 10 || a === 127 || a === 0) return true;
32
+ if (a === 172 && b >= 16 && b <= 31) return true;
33
+ if (a === 192 && b === 168) return true;
34
+ if (a === 169 && b === 254) return true;
35
+ if (a === 100 && b >= 64 && b <= 127) return true;
36
+ if (a === 192 && b === 0) return true;
37
+ return false;
38
+ }
39
+ async function assertPublicUrl(url) {
40
+ let u;
41
+ try {
42
+ u = new URL(url);
43
+ } catch {
44
+ throw new Error("Invalid URL");
45
+ }
46
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`Blocked protocol: ${u.protocol}`);
47
+ const host = u.hostname.toLowerCase();
48
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
49
+ throw new Error(`Blocked host: ${host}`);
50
+ }
51
+ if (/^[0-9.]+$/.test(host) || host.includes(":")) {
52
+ if (isPrivateIp(host)) throw new Error(`Blocked private address: ${host}`);
53
+ return;
54
+ }
55
+ const addrs = await lookup(host, { all: true });
56
+ for (const a of addrs) if (isPrivateIp(a.address)) throw new Error(`Blocked private address for ${host}: ${a.address}`);
57
+ }
18
58
  async function fetchText(url, timeoutMs = 1e4) {
19
59
  const controller = new AbortController();
20
60
  const timer = setTimeout(() => controller.abort(), timeoutMs);
21
61
  try {
22
- const resp = await fetch(url, {
23
- signal: controller.signal,
24
- headers: { "User-Agent": USER_AGENT }
25
- });
26
- if (!resp.ok) {
27
- throw new Error(`HTTP ${resp.status}`);
62
+ let current = url;
63
+ for (let hop = 0; hop < 5; hop++) {
64
+ await assertPublicUrl(current);
65
+ const resp = await fetch(current, {
66
+ signal: controller.signal,
67
+ redirect: "manual",
68
+ headers: { "User-Agent": USER_AGENT }
69
+ });
70
+ if (resp.status >= 300 && resp.status < 400) {
71
+ const loc = resp.headers.get("location");
72
+ if (!loc) throw new Error(`HTTP ${resp.status} without Location`);
73
+ current = new URL(loc, current).toString();
74
+ continue;
75
+ }
76
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
77
+ return await resp.text();
28
78
  }
29
- return await resp.text();
79
+ throw new Error("Too many redirects");
30
80
  } finally {
31
81
  clearTimeout(timer);
32
82
  }
package/dist/mcp.js CHANGED
@@ -13,6 +13,7 @@ import { promises as dns } from "dns";
13
13
  // src/utils.ts
14
14
  import * as fs from "fs";
15
15
  import * as path from "path";
16
+ import { lookup } from "dns/promises";
16
17
  var USER_AGENT = "company-dossier/0.1 (+https://companydossier.lol)";
17
18
  function todayISO() {
18
19
  return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -20,18 +21,67 @@ function todayISO() {
20
21
  function titleCase(str) {
21
22
  return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
22
23
  }
24
+ function isPrivateIp(ip) {
25
+ const v = ip.replace(/^\[|\]$/g, "").toLowerCase();
26
+ if (v.includes(":")) {
27
+ if (v === "::1" || v === "::") return true;
28
+ if (v.startsWith("fe80") || v.startsWith("fc") || v.startsWith("fd")) return true;
29
+ const m = v.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
30
+ if (m) return isPrivateIp(m[1]);
31
+ return false;
32
+ }
33
+ const p = v.split(".").map(Number);
34
+ if (p.length !== 4 || p.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
35
+ const [a, b] = p;
36
+ if (a === 10 || a === 127 || a === 0) return true;
37
+ if (a === 172 && b >= 16 && b <= 31) return true;
38
+ if (a === 192 && b === 168) return true;
39
+ if (a === 169 && b === 254) return true;
40
+ if (a === 100 && b >= 64 && b <= 127) return true;
41
+ if (a === 192 && b === 0) return true;
42
+ return false;
43
+ }
44
+ async function assertPublicUrl(url) {
45
+ let u;
46
+ try {
47
+ u = new URL(url);
48
+ } catch {
49
+ throw new Error("Invalid URL");
50
+ }
51
+ if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`Blocked protocol: ${u.protocol}`);
52
+ const host = u.hostname.toLowerCase();
53
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
54
+ throw new Error(`Blocked host: ${host}`);
55
+ }
56
+ if (/^[0-9.]+$/.test(host) || host.includes(":")) {
57
+ if (isPrivateIp(host)) throw new Error(`Blocked private address: ${host}`);
58
+ return;
59
+ }
60
+ const addrs = await lookup(host, { all: true });
61
+ for (const a of addrs) if (isPrivateIp(a.address)) throw new Error(`Blocked private address for ${host}: ${a.address}`);
62
+ }
23
63
  async function fetchText(url, timeoutMs = 1e4) {
24
64
  const controller = new AbortController();
25
65
  const timer = setTimeout(() => controller.abort(), timeoutMs);
26
66
  try {
27
- const resp = await fetch(url, {
28
- signal: controller.signal,
29
- headers: { "User-Agent": USER_AGENT }
30
- });
31
- if (!resp.ok) {
32
- throw new Error(`HTTP ${resp.status}`);
67
+ let current = url;
68
+ for (let hop = 0; hop < 5; hop++) {
69
+ await assertPublicUrl(current);
70
+ const resp = await fetch(current, {
71
+ signal: controller.signal,
72
+ redirect: "manual",
73
+ headers: { "User-Agent": USER_AGENT }
74
+ });
75
+ if (resp.status >= 300 && resp.status < 400) {
76
+ const loc = resp.headers.get("location");
77
+ if (!loc) throw new Error(`HTTP ${resp.status} without Location`);
78
+ current = new URL(loc, current).toString();
79
+ continue;
80
+ }
81
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
82
+ return await resp.text();
33
83
  }
34
- return await resp.text();
84
+ throw new Error("Too many redirects");
35
85
  } finally {
36
86
  clearTimeout(timer);
37
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "company-dossier",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Build a complete, sourced intelligence dossier on any company from public data — CLI, library and MCP server.",
5
5
  "license": "MIT",
6
6
  "author": "EVERJUST",