Security Sentinel skill

Security Sentinel is an agent skill for AI coding assistants (Claude Code, OpenClaw, Cursor, Codex). Runtime threat intel — URL/phishing scans, wallet scam detection, domain reputation, email-header validation, smart-contract risk, calibrated multi-source scoring. Use when an agent must vet an unknown link, address, contract, or sender before trusting it. Siblings: security-hardening, security-pentester, virustotal. Install with: npx skills-ws install security-sentinel.

devv1.0.0Updated
copied ✓
openclawclaude-codecursorcodex
0 installsSecurity scan: cleanSource code

Security Sentinel

Disambiguation: this skill = runtime threat intel. For defensive code patterns see security-hardening. For active offensive testing see security-pentester. For deep VirusTotal API workflows see the optional virustotal skill (this skill works standalone without it).

Autonomous threat detection and response. Scan URLs, wallets, domains, emails, and contracts before trusting them.

Decision Framework

When an agent encounters untrusted input, classify it and run ALL matching checks in parallel:

Classification (applied independently — input may match multiple):
─────────────────────────────────────────────────────────────────
Contains URL pattern        → URL scan + domain threat check
Contains wallet address     → Wallet reputation + contract scan (if contract)
Contains email headers      → Header analysis + sender domain check
Contains domain name        → WHOIS age + DNS + SSL + typosquatting check
Contains contract address   → Bytecode analysis + honeypot detection
Contains IP/hash/IOC        → Threat intelligence lookup

Example: A URL with a wallet address as a query parameter triggers
BOTH a URL scan AND a wallet reputation check.

Final severity = highest severity across all matched checks.

Severity responses:

  • Clean → proceed normally
  • Suspicious → warn the user, explain why, let them decide
  • Malicious → block the action, explain the threat, suggest alternatives

1. URL & Phishing Detection

Scan Before Clicking

# VirusTotal URL scan
vt url "https://example.com" --include=last_analysis_stats,reputation

# Google Safe Browsing — v4 threatMatches:find still works until 2027-03-31.
# Migrate to v5 (real-time SearchHashes / hash-prefix lookups) for new builds; see §9.
curl -s "https://safebrowsing.googleapis.com/v4/threatMatches:find?key=$GSB_API_KEY" \
  -d '{
    "threatInfo": {
      "threatTypes": ["MALWARE", "SOCIAL_ENGINEERING", "UNWANTED_SOFTWARE"],
      "platformTypes": ["ANY_PLATFORM"],
      "threatEntryTypes": ["URL"],
      "threatEntries": [{"url": "https://example.com"}]
    }
  }'

Phishing Indicators (Heuristic)

Check URLs against these red flags:

IndicatorRiskExample
Homoglyph charactersHighgoog1e.com (1 instead of l)
Excessive subdomainsMediumlogin.secure.account.example.xyz
Recently registered (<30 days)HighWHOIS creation_date check
Free hosting/URL shortenerMediumbit.ly, 000webhostapp.com
IP address as URLHighhttp://192.168.1.1/login
Misspelled brand namesHighpaypa1.com, arnazon.com
HTTP (no TLS) for login pageCriticalhttp://bank.example.com/login
Suspicious TLDMedium.xyz, .top, .buzz, .tk

Typosquatting Detection

A correct check must (1) decode punycode (xn--) to catch IDN homograph attacks, (2) fold Unicode confusables to their ASCII skeleton (so раура1.com with Cyrillic letters collapses onto paypal), and (3) compare the eTLD+1 (registrable domain), not split('.')[0] — otherwise paypal.com.evil.ru and paypal.attacker.io slip through, and multi-label brands like co.uk confuse the base extraction. Compare both the edit-distance ratio AND an exact skeleton match (skeleton match = almost certainly malicious).

# pip install tldextract idna confusable_homoglyphs
from difflib import SequenceMatcher
import idna, tldextract
from confusable_homoglyphs import confusables

KNOWN_BRANDS = [  # store as registrable domains (eTLD+1)
    "google.com", "facebook.com", "paypal.com", "amazon.com",
    "microsoft.com", "apple.com", "netflix.com", "coinbase.com",
    "binance.com", "metamask.io", "uniswap.org", "opensea.io",
]
# Fold a label to its ASCII/Latin "skeleton" so cross-script lookalikes collapse onto
# their Latin form. We pass preferred_aliases=['latin'] so is_confusable() returns the
# LATIN homoglyph of a non-Latin char (e.g. Cyrillic 'а' U+0430 -> 'a'); pure-ASCII chars
# return False and pass through unchanged.
def skeleton(s: str) -> str:
    out = []
    for ch in s:
        try:
            m = confusables.is_confusable(ch, greedy=True, preferred_aliases=["latin"])
        except Exception:
            m = False
        if m and m[0].get("homoglyphs"):
            out.append(m[0]["homoglyphs"][0]["c"])  # Latin canonical form
        else:
            out.append(ch)
    return "".join(out).lower()

BRAND_SKELETONS = {b: skeleton(tldextract.extract(b).domain) for b in KNOWN_BRANDS}

def registrable(domain: str) -> str:
    """Decode punycode, return eTLD+1 (handles co.uk, .com.br, etc.)."""
    try:
        domain = idna.decode(domain.encode("ascii")) if "xn--" in domain else domain
    except idna.IDNAError:
        pass
    ext = tldextract.extract(domain.lower())
    return f"{ext.domain}.{ext.suffix}" if ext.suffix else ext.domain

def check_typosquat(domain: str, threshold: float = 0.85) -> list:
    alerts = []
    reg = registrable(domain)            # e.g. paypal.com.evil.ru -> evil.ru
    label = tldextract.extract(reg).domain
    label_skel = skeleton(label)
    for brand, brand_skel in BRAND_SKELETONS.items():
        if reg == brand:
            continue
        if label_skel == brand_skel:     # confusable/homoglyph hit
            alerts.append(f"CRITICAL: '{domain}' confusable-matches '{brand}' (homoglyph skeleton)")
            continue
        ratio = SequenceMatcher(None, label, tldextract.extract(brand).domain).ratio()
        if ratio >= threshold:
            alerts.append(f"'{domain}' (reg: {reg}) resembles '{brand}' (similarity: {ratio:.0%})")
    # brand name present but NOT the registrable domain → impersonation in a subdomain/path host
    for brand in KNOWN_BRANDS:
        bl = tldextract.extract(brand).domain
        if bl in domain.lower() and registrable(domain) != brand:
            alerts.append(f"HIGH: '{brand}' label appears in '{domain}' but it is not {brand}")
    return alerts

2. Wallet & Address Reputation

Before Transacting

# 1) Chainabuse — community scam reports (Public API v1.2; verify at docs.chainabuse.com)
# Endpoint: GET /v0/reports (the "/v0/addresses/{addr}" path is gone). Screen by ?address=&chain=.
# Auth: HTTP Basic — put the SAME API key in BOTH the username and password fields.
#   "Authorization: Basic base64(API_KEY:API_KEY)" — curl -u does this for you.
# chain ∈ {ETH, BTC, TRON, SOL, POLYGON, BSC, ARBITRUM, BASE, ...}
curl -s -u "$CHAINABUSE_API_KEY:$CHAINABUSE_API_KEY" \
  "https://api.chainabuse.com/v0/reports?address=$ADDRESS&chain=ETH&perPage=50"
# Interpret the JSON: each item has `category` (PHISHING, RUG_PULL, SCAM, RANSOMWARE,
# SEXTORTION, ...), `checked` (moderator-verified), `trustedReporter` (vetted source),
# `createdAt`, and `addresses[]`. Treat checked OR trustedReporter reports as high-signal;
# unverified single reports as Suspicious, not Malicious. NOTE: standard free keys are
# capped at ~10 calls/month (1 call = up to 50 reports) — cache aggressively (see §8).

# 2) OFAC / sanctions screening. Chainabuse is DEPRECATING its sanctions endpoint; use a
#    sanctions oracle instead. On-chain: Chainalysis free Sanctions Oracle (read isSanctioned).
#    Off-chain: TRM / Chainalysis sanctions API, or match against the OFAC SDN crypto list.
#    Mainnet oracle 0x40C57923924B5c5c5455c48D93317139ADDaC8fb — call isSanctioned(address).
#    (Same address on Polygon/BSC; verify at go.chainalysis.com/chainalysis-oracle-docs.html)
cast call 0x40C57923924B5c5c5455c48D93317139ADDaC8fb \
  "isSanctioned(address)(bool)" "$ADDRESS" --rpc-url "$ETH_RPC_URL"

# 3) Etherscan V2 (multichain, single key). V1 was deprecated in 2025 — V2 REQUIRES chainid.
#    Base URL: https://api.etherscan.io/v2/api   (chainid 1=ETH, 8453=Base, 42161=Arbitrum,
#    137=Polygon, 56=BSC, 10=Optimism). Same key works on all 50+ supported chains.

# Activity / age signal — does the address have history, or is it freshly funded?
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=$ADDRESS&startblock=0&endblock=99999999&page=1&offset=10&sort=asc&apikey=$ETHERSCAN_API_KEY"

# Public name tag / label (e.g. "Phish/Hack", "Fake_Phishing", exchange labels).
# NOTE: module=nametag is a PRO endpoint (Pro Plus tier only, 2 req/sec); on a free key
# this call fails, so treat the label signal as unavailable and lean on getsourcecode
# plus Chainabuse instead.
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=nametag&action=getaddresstag&address=$ADDRESS&apikey=$ETHERSCAN_API_KEY"

# Is the address a verified contract? (unverified source on a "token" = elevated risk)
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode&address=$ADDRESS&apikey=$ETHERSCAN_API_KEY"

Scam Wallet Red Flags

SignalRisk LevelWhat to Check
Chainabuse report, checked or trustedReporterCriticalModerator-verified / vetted-source scam report
Chainabuse report, single unverifiedSuspiciousOne unverified victim report — corroborate, don't auto-block
OFAC/SDN sanctioned addressCriticalSanctions oracle isSanctioned / SDN crypto list
Etherscan name tag = Phish/Hack or Fake_PhishingCriticalExplorer-applied malicious label
Tornado Cash interactionContext-dependentSee mixer assessment below
High-frequency small txsMediumDust attack / address poisoning pattern
Contract with no verified sourceMediumEtherscan getsourcecode returns empty SourceCode
Recently created + high value receivedHighPotential rug pull collection wallet

Address Poisoning Detection

Attacker creates addresses that look like your recent contacts:

Real:    0xAbC1234567890DEF1234567890abcdef12345678
Fake:    0xAbC12...............different............45678
                                                    ^^^^^ same prefix/suffix

Defense: Always verify the FULL address, not just first/last characters.

Mixer / Privacy Protocol Assessment

Do NOT automatically flag all mixer interactions as suspicious. Apply contextual analysis:

HIGH RISK (flag as Suspicious):
- Direct deposits/withdrawals > $10,000 equivalent
- Multiple mixer interactions within 24 hours
- Mixer usage immediately followed by transfers to exchanges
- Address appears on OFAC SDN list regardless of mixer use

LOWER RISK (note but do not flag):
- Single small-value mixer interaction
- Interaction via intermediary contract (indirect)
- Known privacy-preserving DeFi protocols (not mixers)

When mixer interaction is detected, include this context: "This address has interacted with [protocol]. Privacy tool usage alone is not inherently malicious. Risk assessment considers transaction patterns, volume, and regulatory context."


3. Smart Contract Risk Assessment

Honeypot Detection

# Quick honeypot check (token contracts)
# A honeypot lets you buy but blocks selling

# Check with honeypot.is API
curl -s "https://api.honeypot.is/v2/IsHoneypot?address=$TOKEN_ADDRESS&chainID=1"

Rug Pull Indicators

CheckHowRed Flag
OwnershipRead owner() or OwnableOwner can mint unlimited tokens
RenouncedCheck if owner is 0x0Not renounced = owner can rug
Liquidity lockCheck LP token holderLP tokens not locked or short lock
Proxy contractCheck for delegatecall patternsOwner can change logic at will
Hidden mintSearch for _mint outside constructorCan inflate supply post-launch
Transfer restrictionsCheck _transfer overridesMay block selling
Fee manipulationCheck setFee/setTax functionsOwner can set 100% sell tax
Blacklist functionSearch for blacklist/isBlacklistedOwner can freeze your tokens

Automated Contract Scan Checklist

1. Is source code verified on block explorer?          → No = HIGH RISK
2. Is ownership renounced (owner == 0x0)?              → No = CHECK FURTHER
3. Are there mint functions callable by owner?          → Yes = HIGH RISK
4. Are there blacklist/whitelist functions?              → Yes = MEDIUM RISK
5. Is there a max transaction/wallet limit?             → Check if owner-adjustable
6. Are LP tokens locked? For how long?                  → <30 days = HIGH RISK
7. Are there pausable functions?                        → Yes = MEDIUM RISK (could be legitimate)
8. Does the contract use upgradeable proxy?             → Yes = CHECK proxy admin

4. Email Header Analysis

Validate Sender Authenticity

# Check SPF record
dig TXT example.com | grep "v=spf1"

# Check DKIM selector (replace "selector" with the real one from the email's
# DKIM-Signature header s= tag — common defaults: google, default, k1, s1)
dig TXT selector._domainkey.example.com

# Check DMARC policy
dig TXT _dmarc.example.com

Header Red Flags

Header FieldCheckRed Flag
Return-PathMatch with FromDifferent domain = spoofing attempt
Received chainTrace hopsUnexpected mail servers
Authentication-ResultsSPF/DKIM/DMARCfail or none on any
X-MailerSoftware usedBulk mailer or suspicious client
Reply-ToMatch with FromDifferent address = phishing likely
Message-ID domainMatch with senderMismatch = forged email

Interpreting Authentication Results

Authentication-Results: mx.google.com;
  dkim=pass header.d=example.com;        ← GOOD: signed by claimed domain
  spf=pass (google.com: domain of noreply@example.com designates 1.2.3.4 as permitted sender);
  dmarc=pass (p=REJECT)                  ← GOOD: strict DMARC policy

If ANY of dkim/spf/dmarc = fail → SUSPICIOUS
If sender domain has no DMARC record → MEDIUM RISK (no spoofing protection)
If DMARC policy = none → LOW protection (monitoring only, not enforcing)

5. Domain Intelligence

WHOIS Age Check

# Check domain registration age
whois example.com | grep -i "creation date"

# Age is a RISK MULTIPLIER, never a verdict on its own. Legitimate new domains exist:
# product launches, marketing campaigns, startups, and incident-response/takedown domains
# are routinely days old. Weight age UP only when combined with another signal (typosquat,
# a credential/login or "connect wallet" page, or a threat-intel hit — see §6 STEP 2/3).
# < 7 days    → strong risk signal; CRITICAL only if it ALSO impersonates a brand or
#               collects credentials/funds. Bare new domain = elevated, not confirmed-bad.
# < 30 days   → HIGH contribution to score
# < 90 days   → MEDIUM (commonly a legitimate startup or campaign)
# > 1 year    → LOW (age is reassuring but not proof — aged domains get hijacked too)

SSL/TLS Assessment

# Check certificate details
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -text -noout
Key checks (TLS is weak signal for malice — calibrate to context):
- Issuer: free CAs (Let's Encrypt, ZeroSSL, Google Trust) are the norm now, NOT a red flag.
- Subject / SAN: does CN/SAN actually cover the host? A mismatch or a wildcard that does
  not include the brand it claims to be = real signal.
- Validity window: SHORT-LIVED CERTS ARE NORMAL in 2026 (ACME automation; the CA/Browser
  Forum is driving max lifetimes toward ~47 days by 2029). Do NOT flag short rotation as
  abuse. A LONG-lived cert with a brand mismatch is more suspicious than a fresh ACME cert.
- Self-signed / private CA: expected for internal, *.internal, RFC-1918, and corp-PKI hosts
  — DOWN-weight there (see §6 STEP 3). Treat as a real problem ONLY on a PUBLIC site that
  presents itself as a bank/exchange/brand or collects credentials, where a browser would
  show a trust error. Combine with the host's public reputation before concluding.

DNS Anomalies

# Check for suspicious DNS patterns
dig A example.com +short          # IP resolution
dig MX example.com +short         # Mail servers
dig NS example.com +short         # Name servers
dig TXT example.com +short        # SPF, verification records

# Red flags:
# - Cloudflare/hosting IP resolving to a brand-impersonating domain
# - No MX records for a domain claiming to send email
# - Recently changed NS records (domain hijack indicator)

6. Threat Intelligence Lookups

IOC Enrichment

# AbuseIPDB — check IP reputation (use a placeholder IP; never hardcode a real target)
curl -s "https://api.abuseipdb.com/api/v2/check?ipAddress=203.0.113.10&maxAgeInDays=90" \
  -H "Key: $ABUSEIPDB_API_KEY" \
  -H "Accept: application/json"

# OpenPhish — current phishing-URL feed (PhishTank's public API is deprecated; see §9).
# Free community feed, no key; refreshed frequently. Match the target against the feed.
curl -s "https://openphish.com/feed.txt" | grep -Fxq "https://suspicious.example.com" \
  && echo "OPENPHISH: listed (high-confidence phishing)" || echo "OPENPHISH: not listed"

# OTX AlienVault — threat indicators (free, key required)
curl -s "https://otx.alienvault.com/api/v1/indicators/domain/example.com/general" \
  -H "X-OTX-API-KEY: $OTX_API_KEY"

Threat Intelligence Decision (calibrated, not a naive sum)

Summing heterogeneous vendor weights and blocking at a fixed threshold produces false blocks (e.g. a year-old AbuseIPDB note + a 0.5 OTX prior would "block" a clean target). Use this ordered decision procedure instead. Run it AFTER the allowlist gate.

STEP 0 — Known-good gate (prevents the worst false positives):
  - Target is an org-maintained allowlist entry (your own domains/contracts)? → CLEAN, stop.
  - Domain on a major reputable list (e.g. Tranco/Cisco-Umbrella top ~10k) AND not
    flagged by any AUTHORITATIVE source below? → CLEAN, lower the weight of weak signals.

STEP 1 — Authoritative override (any ONE ⇒ verdict immediately):
  - Google Safe Browsing v5 match (MALWARE / SOCIAL_ENGINEERING / UNWANTED) → MALICIOUS.
  - OpenPhish / verified Chainabuse (checked|trustedReporter) listing → MALICIOUS.
  - OFAC/SDN sanctioned address → MALICIOUS (legal, not heuristic).
  - VirusTotal ≥ 5 engines flagging, OR ≥ 3 reputable engines agreeing → MALICIOUS.
  These are high-precision; a single hit is sufficient. Do NOT average them away.

STEP 2 — Corroboration tier (needs ≥ 2 independent signals OR 1 strong + recency):
  Score each source, then require AGREEMENT rather than a raw sum:
    VirusTotal      = engines_flagging / total_engines        (1–4 engines = weak)
    AbuseIPDB       = abuseConfidenceScore/100, ×0.5 if newest report > 90d old
    OTX pulses      = 0.4 (prior/context only — never decisive alone)
    Chainabuse      = 0.8 if checked|trusted else 0.3 (unverified)
    Domain age      = +0.3 if eTLD+1 < 30d (see §5 — age alone is NOT proof)
    Typosquat hit   = skeleton match 0.9 / edit-distance 0.5 (see §1)
  - ≥ 2 independent sources each ≥ 0.4  → SUSPICIOUS (warn, show every source).
  - 1 source ≥ 0.4 AND report age < 7d  → SUSPICIOUS (fresh single-vendor signal).
  - exactly 1 weak source (< 0.4)        → LOW-CONFIDENCE note, proceed with caution.
  - 0 signals                             → CLEAN.

STEP 3 — Target-category weighting:
  - Money-moving target (wallet, contract, "connect wallet"/login page) → escalate one
    band on uncertainty (SUSPICIOUS→treat as block-worthy until confirmed).
  - Internal/private host (RFC-1918, .internal, corp CA) → DOWN-weight TLS/age/self-signed
    heuristics; private PKI and fresh certs are normal there.

STEP 4 — False-positive escalation (before blocking anything high-impact):
  - Conflict (authoritative CLEAN vs heuristic MALICIOUS)? Surface BOTH, do not auto-block;
    ask the user or require a second authoritative source.
  - Always record: source name, exact verdict field, report timestamp, and your confidence.
    New threats often start with one vendor — log uncertainty, never fabricate corroboration.

Output Templates (evidence, source, timestamp, uncertainty — always)

CLEAN
  ✅ <target> — no threats found.
  Checked: VirusTotal (0/72), Google Safe Browsing v5 (no match), Chainabuse (0 reports).
  Sources current as of <ISO-8601 ts>. Absence of reports ≠ proof of safety.

SUSPICIOUS
  ⚠️  <target> — proceed with caution. Confidence: MEDIUM.
  Evidence:
    • VirusTotal: 3/72 engines (Fortinet, Sophos, Kaspersky) flag "phishing" [scanned <ts>]
    • Domain age: registered 5 days ago (eTLD+1 <reg-domain>)
  No authoritative source confirms. Recommend not entering credentials/funds until verified.

MALICIOUS
  🛑 <target> — BLOCKED. Confidence: HIGH.
  Authoritative match:
    • Google Safe Browsing v5: SOCIAL_ENGINEERING [<ts>]
    • Chainabuse: 4 reports, category RUG_PULL, moderator-checked [oldest <ts>]
  Action taken: <blocked tx / blocked navigation>. Alternative: <safe path>.

7. Continuous Monitoring Playbook

Agent-Initiated Security Checks

An autonomous security agent should proactively scan at these trigger points:

TRIGGER                          ACTION                         FREQUENCY
──────────────────────────────── ────────────────────────────── ──────────
User shares a URL                → url_scan + domain_threat     Every time
User provides wallet address     → wallet_check                 Every time
New dependency added             → npm audit + snyk check       On change
Pre-deployment                   → header_scan + ssl_audit      Per deploy
Weekly maintenance               → full domain posture check    Weekly
Email campaign setup             → SPF/DKIM/DMARC validation   On setup
Smart contract interaction       → contract_scan + honeypot     Every time
File download from external      → VirusTotal file hash check   Every time

Incident Response Quick Actions

1. PHISHING DETECTED
   → Block URL in security headers (CSP)
   → Notify affected users
   → Report it: Google Safe Browsing (safebrowsing.google.com/safebrowsing/report_phish),
     APWG (reportphishing@apwg.org), and the impersonated brand's abuse contact
   → Check if credentials were entered → force password reset

2. SCAM WALLET DETECTED
   → Block transaction
   → Warn user with specific evidence
   → Report to Chainabuse (chainabuse.com/report)
   → Check transaction history for prior interactions

3. COMPROMISED DOMAIN DETECTED
   → Revoke any API keys associated with domain
   → Update DNS if you control it
   → Notify users who may have visited
   → Check for data exfiltration in logs

4. MALICIOUS CONTRACT DETECTED
   → Revoke token approvals (approve(0))
   → Warn user with contract analysis
   → Check for pending transactions to cancel
   → Report to block explorer

8. Result Caching

Cache scan results to preserve API quota and avoid redundant checks:

Check TypeCache TTLCache Key
URL scan1 hourNormalized URL (strip tracking params)
Domain WHOIS24 hoursDomain name
Wallet reputation15 minutesAddress (lowercased)
Contract scan1 hourContract address + chain ID
Threat intel IOC30 minutesIOC value
  • Cache is in-memory only (no persistence across sessions)
  • Force refresh available via user request: "rescan [target]"
  • Cache hit returns cached result with age note (e.g., "cached 12 min ago")

9. API Quick Reference

Free Tier APIs

Free-tier terms change — figures below are "as of Jun 2026, verify at the linked page."

ServiceFree Limit (verify)Best ForNotes
VirusTotal (v3)4/min, 500/day, 15.5k/monthURL, file, domain, IP scansPublic API; verify docs.virustotal.com/reference/public-vs-premium-api
AbuseIPDB~1,000 checks/dayIP reputationverify abuseipdb.com/pricing
PhishTankDeprecatedPublic API restricted; do not rely on it. Use OpenPhish instead
OpenPhishCommunity feedPhishing URL feedFree, no key; openphish.com/feed.txt. PhishTank replacement
OTX AlienVaultFree, key requiredThreat indicators, IOCs"Unlimited" no longer guaranteed — verify otx.alienvault.com
Google Safe Browsing v5Free, default quota (raise via Cloud Console)URL safety checkv4 ends 2027-03-31; migrate to v5. No published hard 10k/day cap
Etherscan API V2~5 req/sec, ~100k/dayMultichain contract/tx lookupsOne key, 50+ chains via chainid; nametag/label endpoint is Pro Plus only; verify etherscan.io/apis
Chainabuse (Public API v1.2)~10 calls/month (≤50 reports each)Crypto scam reportsBasic auth; very low free quota — cache hard. docs.chainabuse.com
Honeypot.isGenerous free tierToken honeypot detectionverify honeypot.is
WHOIS / RDAP (CLI)~30-50/min per registrarDomain age and registrarRDAP is the modern replacement for port-43 WHOIS; backoff on failures

Environment Variables

VT_API_KEY=          # VirusTotal (v3 public API)
GSB_API_KEY=         # Google Safe Browsing (v5; v4 sunsets 2027-03-31)
ABUSEIPDB_API_KEY=   # AbuseIPDB
OTX_API_KEY=         # AlienVault OTX
ETHERSCAN_API_KEY=   # Etherscan API V2 — single key covers all chains via chainid
ETH_RPC_URL=         # JSON-RPC endpoint (for cast calls, e.g. the sanctions oracle)
CHAINABUSE_API_KEY=  # Chainabuse v1.2 — same key used as BOTH Basic-auth user AND password
# PhishTank removed: public API deprecated; OpenPhish needs no key (see §6).
# Never commit real keys — use a .env file or secrets manager; rotate if exposed.

Graceful Degradation

Not all API keys are required. The agent should adapt based on what's available:

Keys configured   Capability level   Behavior
─────────────── ─────────────────── ────────────────────────────────────────────
All keys          Full                All checks enabled
4-6 keys          Partial             Run available checks, warn about gaps
1-3 keys          Degraded            Heuristic-heavy mode, warn prominently
0 keys            Heuristic-only      Pattern matching only, no external lookups

On startup, log which checks are unavailable:

  • Example: "VT_API_KEY not set — URL reputation checks will use heuristics only"

On API errors during operation:

  • Timeout (>5s): skip source, note in output, continue with other sources
  • Rate limited (429): queue and retry with exponential backoff, warn user of delay
  • Server error (5xx): skip source, note in output, continue
  • All external sources fail: switch to heuristic mode and warn explicitly