Skip to content

About

Official Node.js SDK for the mlab.sh threat intelligence API: lookups, domain and file scans, CVE, SBOM and threat actors

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@mlabsh/mlab-sh

Official Node.js SDK for mlab.sh, the threat intelligence platform.

It covers what the mlab CLI does: IP, URL, email, hash, MAC, phone and crypto lookups, IOC extraction, shell script analysis, domain scans (v2 report), SSL certificates, quotas, file analysis, CVE search, SBOM and package scans on vuln.mlab.sh, and threat actors on actors.mlab.sh.

  • TypeScript, ESM and CommonJS builds, with types
  • Node.js 18 or later, native fetch, no runtime dependencies
  • Typed errors, quota headers, AbortSignal on every call
  • Helpers that wait for a domain or file scan to finish

This package is a library. For a command line, install mlab-cli.

Install

npm install @mlabsh/mlab-sh

Quick start

import { MlabClient } from '@mlabsh/mlab-sh';

const mlab = new MlabClient(); // reads MLAB_API_KEY
const ip = await mlab.lookupIp('8.8.8.8');
console.log(ip.country, ip.isp);

CommonJS works the same way:

const { MlabClient } = require('@mlabsh/mlab-sh');

Configuration

const mlab = new MlabClient({
  apiKey: 'mlab_…',                        // default: MLAB_API_KEY
  baseUrl: 'https://mlab.sh',              // site root of a self-hosted instance; default: MLAB_HOSTNAME
  cveBaseUrl: 'https://vuln.mlab.sh',      // default: MLAB_CVE_HOSTNAME
  actorsBaseUrl: 'https://actors.mlab.sh', // default: MLAB_ACTORS_HOSTNAME
  vulnToken: '…',                          // personal vuln.mlab.sh token, raises the SBOM quota; default: MLAB_VULN_TOKEN
  timeoutMs: 60_000,                       // per request
  maxRetries: 2,                           // extra attempts for GET requests
  userAgent: 'my-soar/2.1',                // appended to mlab-sh-node/1.0.0
  fetch,                                   // custom fetch (proxy, tests)
});

The environment variables are the ones mlab-cli reads. The SDK does not read the CLI's config file or its profiles. Pass the values in, or set the variables.

The key is sent as Authorization: token <key> to mlab.sh only. It is never sent to vuln.mlab.sh, actors.mlab.sh or GitHub, and console.log(client) and JSON.stringify(client) do not show it. Create a key in the organization settings on mlab.sh. Each key carries scopes: lookups need scan.lookup, domain scans scan.domain, file and shell script analysis scan.file. Keys expire after 90 days by default.

Lookups also work without a key, at the anonymous quota.

Cancellation

Every method takes a final options object with signal:

const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5_000);
await mlab.lookupUrl('https://example.com', { signal: ctrl.signal });

The promise rejects with the signal's reason, and no retry follows.

Account and quota

const me = await mlab.whoami();          // { organization, plan, auth: 'api_key' }
const ip = await mlab.getLimit('ip');    // { scan_type: 'ip', remaining, total }
const all = await mlab.getLimits();      // { domain, ip, file, crypto }

await mlab.lookupIp('1.1.1.1');
mlab.lastQuota; // { limit, remaining, resource: 'ip', windowSeconds: 86400 }, from X-RateLimit-*

lastQuota is updated by every response that carries the X-RateLimit-* headers. The server sends them on scans made with an API key.

Lookups

await mlab.lookupIp('8.8.8.8');
await mlab.lookupUrl('https://example.com/login');
await mlab.lookupUrl('https://bit.ly/x', { resolve: true }); // the server fetches the link and follows redirects
await mlab.lookupHash('d41d8cd98f00b204e9800998ecf8427e');
await mlab.lookupHashes(['d41d8…', 'da39a3…']);             // bulk: results, invalid, not_queryable…
await mlab.lookupEmail('abuse@example.com');
await mlab.lookupPhone('+33612345678');                      // E.164, with the +
await mlab.lookupMac('00:11:22:33:44:55');
await mlab.lookupCrypto('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq');
await mlab.lookupCrypto('D8vFz…', { chain: 'doge' });       // override detection only when needed
await mlab.lookupCryptoAddresses(['0x742d…', '1A1zP1…']);   // bulk

Find out what a value is

import { detectTarget } from '@mlabsh/mlab-sh';

detectTarget('CVE-2024-3094'); // 'cve'
const { target, result } = await mlab.search('8.8.8.8'); // runs the matching lookup
mlab.reportUrl('example.com'); // 'https://mlab.sh/domain/example.com'

search routes a domain to its last report. It never starts a scan.

IOC extraction

const text = await readFile('report.txt', 'utf8');
const r = await mlab.extractIocs(text);          // up to 1 MB, defanged forms included
r.iocs.ip;                                       // [{ value, link }]
const sms = await mlab.extractIocs(message, { risk: 'fast', country: 'fr' }); // SMS threat scoring
sms.risk?.band;                                  // 'clean' | 'suspect' | 'high' | 'critical'

risk: 'deep' also unmasks short links and checks raw-IP hosts. It makes outbound requests, so keep it off a blocking path.

Shell scripts

const report = await mlab.analyzeBash(await readFile('install.sh'), { filename: 'install.sh' });
report.suspicious;
await mlab.getBashReport(report.file.sha256); // read a stored report again

Network capture

const capture = await mlab.captureNetwork('https://example.com'); // a real browser loads the page; slow
capture.requests?.filter((r) => r.failed);

SSL certificates

const certs = await mlab.getSsl('example.com'); // [{ common_name, issuer_name, not_before, not_after, … }]

Domain scans

await mlab.scanDomain('example.com'); // spends one domain scan
const report = await mlab.waitForDomainScan('example.com', {
  timeoutMs: 15 * 60_000, // default
  onProgress: (s) => console.log(s.status, s.done, s.total),
});
report.schema; // 'domain-report/2'
report.score;  // { value: 72, grade: 'C' }
report.findings.filter((f) => f.severity === 'high');

The wait helper polls every 2 s, then backs off to 15 s. It tolerates up to 5 transient errors in a row. It throws ScanFailedError when the scan failed and ScanTimeoutError when the time runs out. A scan that timed out keeps running on the server, so you can read it later:

await mlab.getDomainStatus('example.com');  // { status: 'pending' | 'scanning' | 'success', … }
await mlab.getDomainReport('example.com');  // v2 report
await mlab.getDomainResults('example.com'); // legacy v1 payload, frozen upstream

File analysis

const up = await mlab.uploadFile('./sample.exe'); // a path, a Blob, a Uint8Array or an ArrayBuffer
const results = await mlab.waitForFileScan(up.sha256, {
  onProgress: (r) => console.log(`${r.tools_done}/${r.tools_total}`),
});
results.status;   // 'completed' or 'partial'
results.findings;

const out = await mlab.getFileToolOutput(up.sha256, 'strings'); // one tool's raw output
const data = out.is_json ? JSON.parse(out.output) : out.output;

The upload goes to <baseUrl>/upload/file, at the site root, not under /api/v1. The size limit is 10 MB on the Free plan and 100 MB on paid plans. A failed analysis throws ScanFailedError.

CVE (vuln.mlab.sh)

No key needed.

await mlab.cve.search('openssl', { severity: 'CRITICAL', kevOnly: true, minCvss: 9, page: 0, limit: 20 });
await mlab.cve.latest();                        // last 7 days
await mlab.cve.get('CVE-2024-3094');            // CVSS, EPSS, KEV, references
await mlab.cve.advisories({ cve: 'CVE-2024-3094' });
await mlab.cve.vendorMonths('microsoft', 2025); // { months: [12 counts] }
await mlab.cve.stats();
await mlab.cve.sources();
await mlab.cve.dump({ dateStart: '2025-01-01', minCvss: 7 });
const csv = await mlab.cve.export('openssl', { format: 'csv' }); // or 'rss'; returns text

Filters: severity, dateStart, dateEnd, vendor, cwe, minCvss, kevOnly, exact.

SBOM and package scans (vuln.mlab.sh)

const scan = await mlab.vuln.scanManifest(await readFile('package-lock.json'), { filename: 'package-lock.json' });
await mlab.vuln.scanManifestUrl('https://raw.githubusercontent.com/org/repo/main/Cargo.lock');
await mlab.vuln.query('pkg:npm/lodash', { version: '4.17.20' });
await mlab.vuln.query('crates.io/time');

The server detects the format: npm, Cargo, pip, Go, Composer, Gemfile, CycloneDX and others. Pass format to force one. The scan cooldown answers with a short 429, which the SDK waits out.

Threat actors (actors.mlab.sh)

await mlab.actors.list({ origin: 'Russia', sector: 'Government', limit: 50 }); // { items, total, offset, limit }
await mlab.actors.get('apt28');
await mlab.actors.byCve('CVE-2023-23397');
await mlab.actors.stix('apt28');               // STIX 2.1 bundle
await mlab.actors.export({ format: 'jsonl' }); // or 'csv'; returns text

Data: ETDA Threat Group Cards and MITRE ATT&CK, CC BY-NC-SA 4.0.

mlab-cli modules

const { modules } = await mlab.modules.available(); // UniFi, MikroTik, Proxmox, Cloudflare, Scaleway…

The modules are standalone binaries. Install them with mlab module install <name>.

Errors

Every error is an MlabError, with kind, status, body, url and quota:

Class When
AuthenticationError 401: no key, or a key that is unknown, expired or revoked
ForbiddenError 403: the key lacks a scope (err.scope, e.g. scan.domain) or the route is closed to keys; the server message is kept
QuotaError 429, or a "limit reached" 400 (err.retryAfter in seconds)
InputError the value was refused as malformed
NotFoundError nothing on record (no scan for this domain, unknown CVE…)
MaintenanceError the platform is in maintenance
NetworkError no HTTP answer: DNS, TLS, connection refused, timeout
ScanFailedError a domain or file scan ended in failed
ScanTimeoutError a wait helper gave up
import { QuotaError, ForbiddenError } from '@mlabsh/mlab-sh';

try {
  await mlab.scanDomain('example.com');
} catch (e) {
  if (e instanceof QuotaError) console.log(`retry in ${e.retryAfter}s`, e.quota);
  else if (e instanceof ForbiddenError) console.log(`add the ${e.scope} permission to the key`);
  else throw e;
}

GET requests are retried up to twice on network errors and 5xx, and on a 429 whose Retry-After is 30 s or less. POST requests launch metered scans, so they are never replayed.

Development

npm ci
npm run typecheck
npm run lint
npm test

Integration tests run against a real instance, with read-only calls only:

MLAB_INTEGRATION_URL=http://localhost:8080 MLAB_API_KEY=… npm run test:integration

License

See LICENSE.

About

Official Node.js SDK for the mlab.sh threat intelligence API: lookups, domain and file scans, CVE, SBOM and threat actors

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages