@sutraa/mudra
v0.1.0
Published
Hardware-level device fingerprinting for Node.js — a stable machine ID derived from firmware, TPM, and kernel identity signals, with confidence scoring and honest degradation in VMs and containers.
Maintainers
Readme
@sutraa/mudra
mudra (मुद्रा) — seal, signet. A stable machine identity for Node.js, sealed from hardware.
Hardware-level device fingerprinting via a native N-API addon. mudra reads multiple firmware, kernel, and hardware identity signals — DMI/SMBIOS UUIDs and serials, OS machine IDs, disk serials, MAC addresses, TPM / Secure Enclave presence — normalizes them, and hashes them into one stable 256-bit machine ID with a confidence score that tells you how much to trust it.
- Stable — same ID across runs, reboots, and (by default) NIC/disk swaps.
- Tamper-resistant, honestly scoped — multiple independent signals hashed together; changing the ID requires changing firmware-level identifiers, not editing a config file. See What this is — and is not.
- Prebuilt binaries for 8 targets (Linux glibc/musl, macOS, Windows — x64 & arm64) ship inside the package: no compiler needed at install time.
- Pure-JS fallback — on platforms without a prebuild, installation never fails; the library degrades gracefully and tells you (
fingerprint().native === false). - VM/container aware — virtualized environments deliberately virtualize hardware identity, so
mudradetects them and caps the confidence score instead of pretending. - Privacy-conscious — raw serials are never exposed unless you ask (
raw: true), and an applicationsaltderives unlinkable per-app IDs via HMAC.
Install
npm install @sutraa/mudraQuick start
const mudra = require('@sutraa/mudra');
// or: import mudra from '@sutraa/mudra';
const id = await mudra.id();
// '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
const fp = await mudra.fingerprint();
// {
// id: '9f86d081…',
// confidence: 0.85,
// virtualized: false,
// container: false,
// platform: 'linux',
// native: true,
// sources: {
// os_machine_id: { available: true, tier: 'core', hash: '3c363836cf4e1666' },
// firmware_uuid: { available: false, tier: 'core' }, // needs root on Linux
// firmware_serial: { available: true, tier: 'core', hash: '8a331fdde7032f33' },
// disk_serials: { available: true, tier: 'volatile', hash: 'aec070645fe53ee3' },
// mac_primary: { available: true, tier: 'volatile', hash: 'b51d18b551043c1f' },
// tpm: { available: true, tier: 'presence', hash: '0263829989b6fd95' }
// }
// }API
| Function | Returns | |
|---|---|---|
| id(opts?) | Promise<string> | Stable machine ID (64-char hex) |
| idSync(opts?) | string | Sync variant |
| fingerprint(opts?) | Promise<Fingerprint> | Full structured report |
| fingerprintSync(opts?) | Fingerprint | Sync variant |
Options
salt?: string— derive the ID withHMAC-SHA-256(salt)instead of plain SHA-256. Two applications using different salts get unlinkable IDs from the same machine. Recommended for anything privacy-adjacent.strict?: boolean— also mix volatile sources (primary MAC, disk serials) into the ID. Binds tighter to the exact hardware configuration, but the ID changes when a NIC or disk is swapped. Defaultfalse.raw?: boolean(fingerprint*only) — include raw source values. By default only short per-source hashes are exposed so fingerprints can be logged without leaking serial numbers.
How the ID is built
Each source value is normalized (trimmed, lowercased, vendor placeholder values like To be filled by O.E.M. and all-zero UUIDs rejected), then present core-tier sources are serialized in sorted order and hashed with SHA-256:
mudra/v1
firmware_serial=pf2ab3cd
firmware_uuid=4c4c4544-0042-3510-8043-b7c04f503232
os_machine_id=b08dfa6083e7567a1921a715000001fbMissing sources are omitted, never hashed as empty — so a source that was unreadable yesterday and unreadable today keeps the ID identical, and the ID only shifts when actual identity changes.
Hardware sources by platform
| Source | Linux | macOS | Windows | Tier |
|---|---|---|---|---|
| OS machine ID | /etc/machine-id | IOPlatformUUID (IOKit) | MachineGuid (registry, 64-bit view) | core |
| Firmware UUID | DMI product_uuid ¹ | — | SMBIOS Type-1 UUID | core |
| Firmware serial | DMI product_serial ¹ | IOPlatformSerialNumber | SMBIOS system serial | core |
| Disk identity | /dev/disk/by-id | IOKit storage serials | IOCTL_STORAGE_QUERY_PROPERTY | volatile |
| Primary MAC | sysfs (physical NICs) | getifaddrs | GetAdaptersAddresses | volatile |
| Trusted hardware | TPM 2.0 presence | Secure Enclave presence | TPM via TBS (roadmap) | presence |
¹ Root-only on most kernels; silently absent otherwise (and the ID stays stable either way).
Tiers: core sources form the default ID · volatile sources join it only with strict: true · presence sources never enter the ID — they only raise confidence.
Confidence score
confidence ∈ [0, 1] is the weighted share of the platform's possible sources that produced a usable value, then capped by environment:
- Running under a hypervisor → capped at 0.5 (the host can clone or forge every identifier below it).
- Running in a container → capped at 0.25 (the "hardware" identity is largely an image artifact).
- Pure-JS fallback path → small penalty (it reaches fewer sources than the native addon).
Treat the score as how much this ID means, and pick your own threshold per use case (e.g. license binding might require >= 0.6 && !container).
What this is — and is not
This is a best-effort hardware identity, not a hardware root of trust. Userspace — any userspace, in any language — can read stable hardware identifiers but cannot enforce their immutability:
- root/admin can change most sources (
/etc/machine-idis a file;MachineGuidis a registry value; MACs can be spoofed; even SMBIOS can be rewritten with vendor tools). - VMs virtualize everything — the hypervisor decides what UUID and serials the guest sees, and can give two VMs identical identities.
- Containers share the host kernel but see namespaced, image-supplied identity.
What mudra does give you: an attacker must forge multiple independent identifiers at once (firmware UUID + machine ID + serials) rather than editing one value; casual tampering and copy-paste cloning are detectable; and the confidence score plus virtualized/container flags tell you when the ID is weak instead of lying to you.
Genuine tamper-resistance requires hardware-backed keys — TPM 2.0 attestation and Secure Enclave signing. v0.1 detects those chips and scores their presence; key-backed attestation is the roadmap for the enhanced tier.
Good fits: license binding, device-count enforcement, fraud signals, telemetry dedup, cache keys. Poor fits: sole authentication factor, anything adversarial against a root-level attacker.
Prebuilds & fallback
Prebuilt binaries (N-API 8, so one binary per platform serves all Node ≥ 18) are bundled for:
| | x64 | arm64 | |---|---|---| | Linux (glibc) | ✅ | ✅ | | Linux (musl/Alpine) | ✅ | ✅ | | macOS | ✅ | ✅ | | Windows | ✅ | ✅ |
Anything else: install tries a source compile (needs a C++ toolchain), and if that fails the package still installs and runs on the pure-JS fallback — fewer sources, lower confidence, native: false. Set MUDRA_FORCE_FALLBACK=1 to force the fallback path (useful for testing).
The two paths never disagree about the same hardware, but the native addon reaches more sources than the fallback on some platforms — notably on Windows, where the fallback reads only the OS MachineGuid (no SMBIOS), so its ID and confidence are lower than the native addon's. Callers doing strict hardware binding should require fingerprint().native === true and gate on confidence.
Roadmap
- TPM 2.0 EK identity — hash of the endorsement key public part as a burned-in, unforgeable source (Linux
/dev/tpmrm0, Windows TBS). - Secure Enclave–backed keys on macOS for a signing-capable identity.
- FreeBSD sources (
kern.hostuuid).
Contributing
Issues and PRs welcome. Note the toolchain quirk: this repo intentionally has no lockfile and all native builds/tests run in the CI matrix (.github/workflows/prebuild.yml) — there's no need to compile locally to contribute a source module.
Author
Mohsin Raja — portfolio · GitHub · LinkedIn · Instagram
