@profullstack/fleet 0.1.0 → 0.1.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/README.md CHANGED
@@ -60,7 +60,7 @@ matchAgents(inv, threatcrushServers); // { devices: [{ device, agents: [...] }
60
60
 
61
61
  ### Observation rules
62
62
 
63
- Only what anyone could see without the device's credentials, and only for devices you run. Private addresses (RFC 1918, CGNAT, link-local, ULA) are never probed. Client profiles (desktop, phone, tablet) are not network-probed.
63
+ Only what anyone could see without the device's credentials, and only for devices you run. A device the observer runs on is marked `from_self` and not port-scanned: from inside, loopback skips the firewall. Observe each box from another one. Private addresses (RFC 1918, CGNAT, link-local, ULA) are never probed. Client profiles (desktop, phone, tablet) are not network-probed.
64
64
 
65
65
  ## License
66
66
 
package/observe.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Device, Inventory } from './index.js';
2
2
  export const PORTS: number[];
3
3
  export const WELLKNOWN: string[];
4
+ export function localAddresses(): Set<string>;
4
5
  export function observe(device: Device, opts?: { timeoutMs?: number; ports?: number[]; now?: () => Date }): Promise<Record<string, unknown> & { at: string }>;
5
6
  export function observeInventory(inv: Inventory, opts?: { concurrency?: number; timeoutMs?: number; ports?: number[] }): Promise<Inventory>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/fleet",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Read an OpenDevice inventory (servers, desktops, phones, tablets), add up what the fleet costs a month, observe each box's public metadata, and export the costs for CoinPay. Used by ThreatCrush and w3bs.org/fleet.",
6
6
  "keywords": ["fleet", "opendevice", "openserver", "inventory", "hosting", "cost", "coinpay", "threatcrush", "w3bs"],
package/src/observe.js CHANGED
@@ -7,6 +7,7 @@
7
7
  // Only observe devices you run. Node-only.
8
8
  import { promises as dns } from 'node:dns';
9
9
  import net from 'node:net';
10
+ import { networkInterfaces } from 'node:os';
10
11
  import tls from 'node:tls';
11
12
 
12
13
  export const PORTS = [22, 25, 53, 80, 443, 465, 587, 993, 2202, 3000, 5432, 6379, 8080, 8443];
@@ -26,6 +27,13 @@ function timeout(ms) {
26
27
 
27
28
  const race = (p, ms) => Promise.race([p, timeout(ms)]);
28
29
 
30
+ // Addresses of the machine running the observer. Probing one of these goes over
31
+ // loopback and skips the firewall, so open ports would be reported that the
32
+ // internet cannot reach.
33
+ export function localAddresses() {
34
+ return new Set(Object.values(networkInterfaces()).flat().filter(Boolean).map((i) => i.address.toLowerCase()));
35
+ }
36
+
29
37
  function isPrivate(ip) {
30
38
  if (net.isIPv4(ip)) {
31
39
  const [a, b] = ip.split('.').map(Number);
@@ -128,10 +136,14 @@ export async function observe(device, { timeoutMs = 4000, ports = PORTS, now = (
128
136
  if (!publicIps.length) return out;
129
137
  const target = [...a].find((ip) => !isPrivate(ip)) ?? publicIps[0];
130
138
 
139
+ const self = publicIps.some((ip) => localAddresses().has(ip.toLowerCase()));
140
+ if (self) out.from_self = true;
141
+
131
142
  out.ptr = [];
132
143
  for (const ip of publicIps) for (const n of (await settle(race(dns.reverse(ip), timeoutMs), [])) ?? []) out.ptr.includes(n) || out.ptr.push(n);
133
144
  out.asn = await settle(race(asnOf(target), timeoutMs));
134
145
 
146
+ if (self) return out; // ports, banners and TLS would be seen from inside; observe from another box
135
147
  const open = await Promise.all(ports.map((p) => portOpen(target, p, timeoutMs).then((ok) => (ok ? p : null))));
136
148
  out.ports = open.filter((p) => p != null);
137
149