Add optional Fingerbank device fingerprinting to deep-check
CI / web (push) Successful in 16s
CI / api (push) Successful in 21s

Folded into the existing on-demand Deep check button: queries
Fingerbank's interrogate API with the device's MAC plus the SSDP
SERVER header when deep-check-device.sh finds one, showing the
confidence band alongside the result. Runs directly from the API
container (no host-level access needed, just an outbound HTTPS call),
unlike the SSDP/mDNS steps.

Confirmed via direct testing: without DHCP fingerprint data (which we
structurally don't have, not being the DHCP server), MAC-only queries
often can't get past manufacturer-level confidence -- same info the
free OUI lookup already provides. Documented honestly in
docs/device-discovery.md rather than overselling it. Still worth
having as opt-in enrichment for devices that do expose richer signals.

Gated behind optional FINGERBANK_API_KEY -- missing key, API errors,
or no match all degrade gracefully without affecting the rest of
deep-check's local findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 23:35:35 -06:00
parent ee9b6d15cd
commit d0d6ae95f1
11 changed files with 183 additions and 13 deletions
+8
View File
@@ -79,18 +79,26 @@ export function clearDeviceLabel(mac: string): Promise<{ ok: true }> {
return request(`/api/devices/${encodeURIComponent(mac)}/label`, { method: "DELETE" });
}
export interface FingerbankResult {
deviceName: string | null;
score: number | null;
version: string | null;
}
export interface DeepCheckResult {
ip: string;
mdnsHostname: string | null;
reverseDns: string | null;
ssdp: {
location: string;
server: string | null;
friendlyName: string | null;
manufacturer: string | null;
modelName: string | null;
} | null;
openPorts: number[];
http: { port: number; title: string | null; server: string | null }[];
fingerbank: FingerbankResult | null;
}
export function deepCheckDevice(ip: string): Promise<DeepCheckResult> {
+27 -1
View File
@@ -90,9 +90,23 @@ function NameCell({ device, onDeviceChanged }: { device: Device; onDeviceChanged
);
}
// Bands per Fingerbank's own docs: below 30 "very little confidence", 31-50
// moderate, 51-75 high, 76+ very high. Shown alongside the result so a
// manufacturer-only guess isn't mistaken for a confirmed identification.
function confidenceLabel(score: number): string {
if (score < 30) return "low confidence";
if (score <= 50) return "moderate confidence";
if (score <= 75) return "high confidence";
return "very high confidence";
}
function DeepCheckResultPanel({ result }: { result: DeepCheckResult }) {
const nothingFound =
!result.mdnsHostname && !result.reverseDns && !result.ssdp && result.openPorts.length === 0;
!result.mdnsHostname &&
!result.reverseDns &&
!result.ssdp &&
result.openPorts.length === 0 &&
!result.fingerbank?.deviceName;
if (nothingFound) {
return <p className="deep-check-empty">No additional information found for {result.ip}.</p>;
@@ -100,6 +114,18 @@ function DeepCheckResultPanel({ result }: { result: DeepCheckResult }) {
return (
<dl className="deep-check-results">
{result.fingerbank?.deviceName && (
<>
<dt>Fingerbank ID</dt>
<dd>
{result.fingerbank.deviceName}
{result.fingerbank.version && ` (${result.fingerbank.version})`}
{result.fingerbank.score !== null && (
<span className="name-hint"> {confidenceLabel(result.fingerbank.score)}</span>
)}
</dd>
</>
)}
{result.mdnsHostname && (
<>
<dt>mDNS hostname</dt>