azrael-antismurf
v1.0.0
Published
Lightweight smurf/dupe-detection scoring for signups: disposable domains, fuzzy matches, entropy, IP/device reuse. Includes a daily disposable-domain refresher, CLI, and multi-source merge.
Maintainers
Readme
azrael
Lightweight, dependency-free risk scoring to spot smurf / duplicate accounts during signup.
Signals included:
- Disposable domains (pluggable list)
- Email & username normalization
- Fuzzy similarity (Levenshtein)
- Shannon entropy (randomness) & vowel ratio heuristic
- IP/device fingerprint reuse
- Optional MX check for email domains
- Daily disposable-domain refresher from one or multiple URLs (newline- or JSON-array formatted) with merge + dedup
- CLI:
npx azrael refresh <urls...>to fetch, merge, and print or write to file
Install
npm i azraelQuick start (CommonJS)
const az = require('azrael');
// Optional: start daily refresh from multiple curated lists
az.startDisposableRefresher({
urls: [
'https://your.cdn.example/disposable_1.txt',
'https://another.example/domains.json'
],
intervalMs: 24 * 60 * 60 * 1000,
logger: console,
});
const score = az.scoreAccount({ email: '[email protected]', username: 'userone_' });
console.log({ score, decision: az.decideAction(score) });CLI
Fetch, merge, dedup, and output a combined list.
npx azrael refresh https://list1.txt https://list2.json \
--out disposable_domains.txt # optional: write to file
--json # output JSON array instead of newline list
--silent # suppress logsWithout --out, prints to stdout.
Refresher options
az.startDisposableRefresher({
urls: [url1, url2, ...], // preferred
url: url1, // still supported for single source
intervalMs: 86_400_000,
initial: true,
logger: console,
parse, // optional custom parser
});
az.stopDisposableRefresher();
// Manual one-off refresh
await az.refreshDisposableDomainsFromUrls([url1, url2], { logger, parse });Accepted formats:
- Newline-separated
domain.tldper line (comments starting with#ignored) - JSON array of strings:
["mailinator.com", "yopmail.com"]
API
scoreAccount(input, weights?) => number // 0..100
decideAction(score, thresholds?) => 'allow' | 'challenge' | 'review' | 'block'
// helpers
normalizeEmail, normalizeUsername, levenshtein, normalizedDistance,
shannonEntropy, vowelRatio, isDisposableDomain, getEmailParts, hasMXRecord,
setDisposableDomains, addDisposableDomains, getDisposableDomains,
startDisposableRefresher, stopDisposableRefresher,
refreshDisposableDomainsFromUrl, refreshDisposableDomainsFromUrls,
mergeDisposableDomainLists,
defaultWeights, defaultThresholds