Hulo Global
Plugins Roadmap

Fraud Prevention changelog

Every release of @huloglobal/vendure-plugin-fraud-prevention. Latest release: v0.19.2 — 28 September 2026.

v0.19.2

28 September 2026 Latest
Added
  • IPv6 CIDR ranges on the block and allow lists (2001:db8::/32, ::ffff:203.0.113.0/120, a bare address is a /128). Matching runs over the expanded 128-bit form, so :: compression, zone ids and IPv4-mapped forms all match; an IPv4 client is also checked against IPv4-mapped ranges and an IPv4-mapped client against IPv4 ranges. IPv4 ranges keep their integer fast path. Entry validation accepts either family.
  • Allowlisted ip_range entries are now honoured (previously only exact ip entries were checked when deciding whether an address bypasses the checks).
Changed
  • A best-effort idx_fp_payment_created index on Vendure's payment(createdAt), created on boot, for the failed-payments signal and the 24 h failed-payment count (Vendure never indexes that column). Failure to create it is logged at debug level and never blocks start-up.
Fixed
  • PostgreSQL: every camelCase column read from Vendure's tables (order, customer, payment, order_channels_channel, channel) is quoted; SUM(condition) counts are written as SUM(CASE WHEN … THEN 1 ELSE 0 END); order.active is compared as a boolean; the customer-history query no longer sends an untyped null parameter; the feed-sync cross-process lock uses a Postgres advisory lock (it was MariaDB GET_LOCK only).
  • The feed-sync lock is taken and released on one connection held for the duration of the sync. Session locks released through the pool could land on a different connection and leave the lock behind, making later syncs report "already running in another process".
  • On a fresh install the fraud_blocked_orders(orderId, status) index was requested before the table existed and silently skipped; it is now created after the table.
  • A PostgreSQL corpus test (tests/pg-corpus.test.ts, runs when HULO_PG_URL is set) extracts every SQL statement in src/, translates it with the SDK's dialect adapter and prepares it against a scratch database with quoted stand-ins for the Vendure tables, so a quoting or dialect regression fails the unit suite.

v0.19.1

24 September 2026
Fixed
  • Nightly threat-feed sync no longer deadlocks: fraud_blocklist gains a source index, feeds that have not changed (same content hash) are skipped, changed feeds are loaded under a staging key and swapped in id-sized chunks with deadlock retries, and a sync cannot overlap another (in-process flag plus a database lock across server and worker).
  • The review case is written before the assessment log in enforce mode, closing the window in which a host fulfilment run could release an order about to be held; a re-delivered event can no longer open a second case.
  • IPv4-mapped addresses (::ffff:1.2.3.4) are normalised, IPv6 addresses reach the IP-intelligence lookup, blocklisted emails match their canonical form, list entries are validated (type, CIDR syntax, email, domain), thresholds are clamped and ordered, and the public pre-check uses the caller's own address.
  • Ops webhooks treat a non-2xx reply as a failure and log it at warn level.
Changed
  • fraud_log(orderId) and fraud_blocked_orders(orderId, status) indexes for the host fulfilment gate and order panel.
  • The IP-intelligence and MX lookups start at the beginning of an assessment and run in parallel with the database signals; a lookup timeout is 2.5 s; failed lookups are remembered for 15 minutes and calls are rate-limited to 40/min so a wave of new addresses cannot stall scoring or exhaust the provider.
  • Assessments run at most six at a time; channel/notification config and dashboard stats are memoised for 30 s; the customer-history queries no longer wrap the email in LOWER() so the index is used.
  • One pooled SMTP transport with 10 s connect / 25 s send deadlines; admin and customer emails are sent after the case row is written, off the assessment path.
  • Audit-log pruning runs in 5,000-row chunks; feed downloads are buffered as bytes.

v0.19.0

11 September 2026
Added
  • Reject really cancels. Rejecting a review case now cancels the order through Vendure's OrderService.cancelOrder (state machine, order history and every OrderStateTransitionEvent subscriber see it), voids Authorized payments (card holds, bank transfers) and refunds every settled payment in full through the payment handler's createRefund. Previously only order.active was cleared, so a rejected order was still fulfilled by hosts gating on open cases alone. New plugin options cancelOnReject and refundOnReject (both default true); POST /fraud-prevention/cases/:id/reject accepts per-case cancel / refund overrides and returns cancelled, orderState, refunds and warnings. Vendure refusals never block the decision — they are reported in warnings, the audit log, the ops alert and the admin toast.
  • Stripe Radar and 3-D Secure signals. The existing Stripe lookup also reads outcome.risk_level and payment_method_details.card.three_d_secure: radar_risk_elevated (15), radar_risk_highest (35) and three_ds_failed (10 — 3DS ran and the cardholder did not authenticate; attempt_acknowledged / exempted / not_supported are benign). three_ds_failed is gated by the channel's existing *3-D Secure* rule, which now does something. The Simulate tab has Radar / 3DS inputs; card-network verdicts (AVS, Radar, 3DS) are highlighted together in the review queue and the order panel.
  • Checkout-guard failed payments. The *Failed payments from IP* signal also counts failed / client_declined rows from checkout_guard_payment_event (written by @huloglobal/vendure-plugin-checkout-guard before any Vendure Payment row exists) when that table is present. Detected automatically; nothing to configure.
  • Fulfilment gating helpers. FraudPreventionService.isAssessed(orderId) / assessedOrderIds(ids) — true once the order guard has scored the order — close the race between OrderPlacedEvent and the asynchronous assessment; heldOrderIds() returns pending and rejected cases. pendingOrderIds() is unchanged.
  • Exports: CardChecks (AvsResult stays as an alias), RadarRiskLevel, ThreeDsResult, ResolveCaseResult, cardChecksFromStripeCharge, cardChecksFromMetadata, fetchStripeCardChecks, parseRiskLevel, parseThreeDsAuthenticated, threeDsFailed.
Changed
  • avsResolver may return Radar / 3DS fields alongside the AVS ones; payment metadata readers accept riskLevel / threeDsAuthenticated / threeDsResult (canonical avs object), Stripe-style outcome / three_d_secure objects and flat keys.
  • FraudPreventionService now injects OrderService and RequestContextService (both provided by PluginCommonModule).

v0.18.4

9 September 2026
Added
  • Activity log AVS filter. The same *AVS fails* toggle as the review queue, next to the level and action pickers; combines with both, and the CSV export follows the filtered rows. GET /fraud-prevention/log accepts signal=avs.

v0.18.3

9 September 2026
Added
  • Review queue AVS filter. An *AVS fails* toggle next to the status filters narrows the queue to cases where the card issuer reported a postcode or street-address mismatch; it combines with pending / approved / rejected. GET /fraud-prevention/cases accepts signal=avs (any signal-key prefix).

v0.18.2

9 September 2026
Changed
  • Review queue & order page: card AVS verdicts are shown as red card chips, sorted first in each case's signal list, with the points in the tooltip; the order-page fraud panel highlights the same rows. The bank's verdict is the first thing a reviewer sees.

v0.18.1

8 September 2026
Fixed
  • Stripe lookup pinned to API version 2022-11-15. Without a Stripe-Version header the request ran at the account's default version, and on accounts created before November 2022 expand[]=latest_charge is rejected with HTTP 400 — the lookup silently produced nothing. The GET is now pinned (read-only, so it never changes the account), and the unreachable charges.data fallback is gone.
  • Multi-channel hosts: the Stripe payment method is now resolved for the order's channel, so two channels with different Stripe accounts each use their own key.
  • postcode_mismatch no longer fires when one postcode is a partial form of the other (5-digit ZIP vs ZIP+4, UK outward code vs full postcode).

v0.18.0

8 September 2026
Added
  • Postcode / AVS mismatch signals. Three new weighted signals: avs_postcode_fail (35) and avs_address_fail (20) fire when the card issuer's AVS verdict on the billing postcode / street address is an explicit *fail*; postcode_mismatch (8) fires when the typed billing and shipping postcodes differ within the same country. AVS verdicts come from a host avsResolver option (any gateway), from Payment.metadata (avs: { postalCode, line1 }, Stripe-style checks, or flat keys), or — for orders paid through Vendure's StripePlugin — automatically, by fetching the PaymentIntent's latest charge with the payment method's own API key. Fails open; unavailable / unchecked never score.
  • Rules → Signals: *Check card AVS with Stripe* toggle per channel (avsLookup, default on).
  • Simulate: billing / shipping postcode and card AVS postcode / street result inputs, so the new signals can be tried against live data.
  • Exports: AvsCheck, AvsResult, avsFromMetadata, avsFromStripeCharge, fetchStripeAvs, normalisePostcode, parseAvsCheck, postcodesDiffer.
Notes
  • Stripe only runs AVS when the checkout sends the billing address with the payment. The Payment Element does not collect a street address (and often no postcode) by itself, so pass the order's address in confirmPayment → confirmParams.payment_method_data.billing_details — see README → Postcode / AVS. Wallet payments (Apple Pay, Google Pay, Link) carry no AVS checks.
Changed
  • AssessInput gains billingPostalCode, shippingPostalCode and avs; the order guard fills them from the order's addresses and FraudPreventionService.resolveAvsForOrder().

v0.17.1

2 September 2026
Changed
  • Branding. The bundled hulo-global-logo.svg is now the HG wordmark on the HULO black rounded square (the same mark as the huloglobal.com header), with explicit -light and -dark variants alongside the auto-switching default.

v0.17.0

2 September 2026
Added
  • Licence & billing card in the admin. Always visible: the current state (free tier, free trial with first-charge date, monthly/annual subscription, lifetime, or master licence) with the actions that apply — start the 14-day free trial or subscribe, buy lifetime, Manage billing (Stripe customer portal: update card, cancel, switch plan) and Upgrade to lifetime (the old subscription stops billing at the end of its paid period). Requires licence SDK ^0.14.0.

v0.16.1

2 September 2026
Changed
  • The 14-day free trial is now card-backed. Unlicensed installs run in the free tier; start the trial from the admin banner (monthly or annual → *Start 14-day free trial*) — Stripe collects a card, nothing is charged until day 15, cancel any time before then, one trial per customer — and the licence installs itself within a minute. The automatic no-card evaluation window is retired (licence SDK ^0.13.0).

v0.16.0

2 September 2026
Added
  • Buy licence from the admin. The evaluation / free-tier banner now has a plan picker and a Buy licence button: checkout opens in a new tab and, once payment completes, the licence installs itself — no email round-trip, no .env edit, no restart. Renewed subscription keys are picked up automatically too. New admin endpoints licence/purchase-link and licence/claim-status.
Changed
  • Requires @huloglobal/vendure-licence-sdk ^0.12.0.
  • The 7-day card trial at checkout has been retired: every install already gets the 14-day no-card evaluation, and paid plans now bill from day one.

v0.15.1

2 September 2026
Changed
  • Licence SDK ^0.11.0. Master licences (one key that activates every HULO plugin) and hardware-bound keys are now accepted by the runtime licence check.
  • Branding. Refreshed HULO Global logo (inline HG monogram) in the admin UI.

v0.15.0

25 August 2026
Added
  • PostgreSQL support. All of the plugin's SQL now runs on Postgres as well as MySQL/MariaDB — the licence SDK's new dialect adapter translates queries transparently at runtime, so no configuration is needed: the plugin follows whatever database your Vendure dbConnectionOptions use. Verified against PostgreSQL 17. MySQL/MariaDB installs are unaffected (byte-identical passthrough).

v0.14.1

25 August 2026
Added
  • "What's new" in the update banner. The banner now links straight to this plugin's changelog page on huloglobal.com, so you can read exactly what a release contains before updating.

v0.14.0

23 August 2026
Added
  • One-click in-app updates. The update banner now has an "Update now" button: the plugin installs the new version via your project's own package manager (yarn/npm/pnpm auto-detected), verifies it landed, and gracefully restarts under your process supervisor (pm2/systemd). Admin-only; the target version is verified against the npm registry; a failed install never restarts anything. Disable with HULO_SELF_UPDATE=off; force restart without a detected supervisor with HULO_SELF_UPDATE=force. Note: a separate worker process picks the update up on its next restart, and the admin UI itself refreshes after your next admin build.

v0.13.0

22 August 2026
Changed
  • "Off" no longer means "blind". When protection is disabled the engine still evaluates every rule and records a shadow assessment (action 'shadow', never any holds). Risky shadow-scored orders warn in the server log, fan out to the ops channels (new 'shadow.risky' event), optionally email the admin, and the order-detail panel shows a "scored while protection was off" notice — so switching protection off never silently loses the risk picture.

v0.12.0

22 August 2026
Added
  • Fraud panel on the admin order page. Every paid order's detail page now shows the risk score (/100), level, contributing signals and review-case status, colour-coded and adapted to the admin light/dark theme. Only rendered once the order has a settled payment. New GET order-assessment/:orderId admin endpoint.

v0.11.0

21 August 2026
Added
  • One-click threat-feed presets. The Custom feeds section now offers seven curated, well-known public lists (IPsum L3+, blocklist.de, FireHOL L2/L3, Emerging Threats compromised, CINS Army, StopForumSpam toxic domains) as add-with-one-click presets — no more hunting URLs. All URLs verified live at publish time; already-added feeds are hidden from the preset row.
Fixed
  • Premium buttons (enforce mode, feed sync, custom feeds) were still disabled during the full-featured evaluation — gating now keys off the tier, so trial installs can use everything the server allows.

v0.10.0

21 August 2026
Added
  • In-admin licence activation. Paste your key into the plugin's admin page and it verifies (signature, plugin id, domain binding, expiry, revocation) and activates instantly — no .env edit, no redeploy. The key persists in the shared hulo_licence_store table and is re-applied on every boot; an explicitly configured env/init key always wins. POST licence/activate + licence/deactivate endpoints.

v0.9.1

21 August 2026
Added
  • Evaluation pings now include anonymous usage aggregates (counts only, never personal data) so the opt-in reminder emails can say what the plugin actually did during the trial.

v0.9.0

21 August 2026
Added
  • 14-day full-featured evaluation. Unlicensed installs now get the complete feature set for 14 days instead of the restricted free tier. Enforce mode, review-queue holds, threat feeds and alerts now also run during the evaluation window. The clock is anchored server-side (a hashed instance id — no personal data), so reinstalling does not restart it, and it fails open: if the licence server is unreachable the plugin keeps running fully. After the window the plugin drops to the free tier; all configuration is kept and reactivates instantly with a key.
  • Admin-UI evaluation banner with live countdown and an optional "email me before it ends" reminder opt-in (explicit consent — no email is sent anywhere otherwise).

v0.8.0

4 August 2026
Added
  • Upload image / asset library button in the message editor — opens Vendure's asset picker (browse or upload) and inserts the image, stored in your asset library. Editor selection is saved/restored so toolbar and colour actions apply reliably after a dialog opens.

v0.7.0

4 August 2026
Added
  • Full editor toolbar for the customer messages: text + highlight colour, font size, underline/strikethrough, headings/quotes, numbered lists, indent/outdent, image insert, custom button, divider, alignment, clear-formatting and undo/redo.
Fixed
  • Editor text showed grey in dark mode (admin theme colouring bare block elements). Canvas content now forces dark ink on the white paper.

v0.6.1

4 August 2026
Fixed
  • Dark mode: the customer-message editor canvas now reads as intentional white paper (framed, visible caret), and the message preview renders on white — it was showing on a dark surface, which misrepresented the email and clashed with its inline colours.

v0.6.0

4 August 2026
Added
  • Visual email editor for the customer messages (held / approved / rejected): formatting toolbar, drag-and-drop variable chips, and a Visual / HTML toggle. Message bodies are now HTML-aware — the plain-text defaults still render as before, but you can build a fully styled HTML email and it passes through untouched.

v0.5.0

3 August 2026
Added
  • Custom threat feeds. Add your own public blocklist URLs in the Lists tab — any line-based list (one entry per line, # comments ignored), typed as IP / CIDR range / email / email-domain. They sync nightly alongside the built-ins and are matched identically (CIDR included), with per-feed enable/disable, on-demand sync, and last sync count / error surfaced. Licensed feature.
  • Endpoints: GET/POST /fraud-prevention/feeds/custom, POST /feeds/custom/:id (edit / {sync:true}), DELETE /feeds/custom/:id.
Security
  • Feed fetches reject non-http(s) schemes and internal/private targets (localhost, RFC-1918, link-local) as a basic SSRF guard, and cap the response at 30 MB.

v0.4.3

2 August 2026
Fixed
  • Public POST /fraud-prevention/check returned Nest's default 201 for a POST; it's a read-only risk check, so it now returns 200.
Added
  • End-to-end test suite (@vendure/testing, real MariaDB dialect): admin auth gating, public-check shape + rate limiting, and the assessment engine (disposable email, order value, returning-customer trust credit, blocklist incl. CIDR ranges, allowlist bypass, unlicensed enforce downgrade). 11 e2e tests.

v0.4.2

2 August 2026
Fixed
  • Webhook integration row showed nothing, Telegram row was wrong. A regex repair back in 0.3.3 over-matched and deleted the Telegram chat-ID field and the entire Webhook case, leaving the Telegram row bound to the webhook secret. Both integration rows are restored: Telegram (bot token + chat ID) and Webhook (URL + signing secret).
Added
  • Customer messages: multi-select + bulk reset. Each of the three templates (held / approved / rejected) shows a default / customised badge; tick any combination and "Reset selected to default", or "Reset all to default" in one click. Per-template reset stays.

v0.4.1

2 August 2026
Fixed
  • Review queue froze the browser tab ("page unresponsive") when toggling the per-case "email customer" / "blocklist" checkboxes. The checkboxes bound to method calls ([ngModel]="caseNotify(id)") which Angular re-evaluated every change-detection pass. The per-case ticks are now seeded from the global defaults when cases load and bound to plain state. The Settings integrations list likewise iterates a stable array instead of one rebuilt each cycle.

v0.4.0

2 August 2026
Added
  • Silent resolution. Approve and Reject each carry a per-case "email customer" tick — untick to resolve without telling the customer anything. Defaults follow the global settings.
  • Silent identity blocklist on reject. A per-case "blocklist" tick (and a global default) quietly adds the rejected email + canonical + IP to the blocklist, so a fraudster is turned away next time and never learns why — the classic don't-tip-them-off move.
  • Global notification defaults: email-on-approval, email-on-rejection and blocklist-on-reject are all channel-wide toggles in Settings; the per-case ticks pre-fill from them and override for the single case.

v0.3.3

2 August 2026
Fixed
  • 0.3.2's @ escape was corrupted by the release tooling (sed & back-reference) and still shipped a bare @ — admin builds kept failing. Repaired and verified: no bare @ remains in any template text node.

v0.3.2

2 August 2026
Fixed
  • Admin UI failed to compile under Angular 17+ block syntax: the literal @BotFather in the Telegram help text is now the @ entity.
Changed
  • New logo: shield + amber pulse trace (live risk monitoring), also used in the admin hero.

v0.3.1

2 August 2026
Changed
  • Rules tab simplified. New Protection level presets — Relaxed / Balanced (recommended) / Strict — set thresholds and velocity limits in one click; every numeric field now lives behind an Advanced settings toggle. Editing any value flips the level to Custom.
  • Notifications redesigned as an integrations list: one row per channel (Email, Slack, Discord, Teams, Telegram, Webhook) with a connected-status dot, expanding in place to its few fields — instead of seven URL inputs spread across the page.

v0.3.0

2 August 2026
Added
  • Ops notification fan-out: Slack, Discord, Microsoft Teams, Telegram (bot token + chat id) and a generic signed webhook (JSON POST, HMAC-SHA256 in X-Hulo-Signature) — every held, approved, rejected and auto-released case pings all configured channels. Each transport fails independently.
  • Customisable customer messages, per channel: the three gating outcomes (held / approved / rejected) are now editable templates with {{orderCode}}, {{firstName}}, {{supportEmail}} and {{reviewHours}} variables, live preview and one-click reset. Bodies are plain text (blank lines = paragraphs) so tone is editable without HTML foot-guns. Defaults rewritten to be honest without being alarming — a held order is "a quick security check", never an accusation, and a rejection includes a human-appeal path.
  • Hold-notice policy per channel: tell the customer never / only at block level (default) / on every held order; plus a configurable reviewHours promise surfaced in the templates.
  • Auto-released cases now email the customer with the approved template and post an ops event.

v0.2.0

2 August 2026
Added
  • IP intelligence (ip-api.com, cached 30 days in fraud_ip_intel, fails open): VPN/proxy and datacentre-IP signals — the blockVpnProxy toggle finally does something — plus IP vs billing-country mismatch.
  • Email MX validation: a domain that can't receive mail can't receive licence keys either. Authoritative NXDOMAIN/no-MX scores; DNS timeouts fail open. 24h in-memory cache.
  • Identity fan-out: ≥3 distinct customer emails ordering from one IP inside 24h — the classic card-testing pattern.
  • Customer trust credit: returning customers earn NEGATIVE points (−12 for 1–2 settled orders, −25 for 3+), counted by canonical email so plus-tag variants share one track record. Score floors at 0. High-value first orders still score as before.
  • Gibberish-email heuristic (digit-heavy or keyboard-mash local parts; deliberately low-weight) and billing/shipping country mismatch signal.
  • Customer Lookup tab: full dossier per email — orders, lifetime value, settled/cancelled split, failed payments, prior cases, assessment history, list status, one-click allow/block.
  • Slack alerts: optional webhook pinged on every held order.
  • Auto-release timer (per channel, default off): pending cases older than N hours auto-approve so held orders don't strand over a weekend.
  • CSV export of the activity log.

v0.1.0

2 August 2026
Added
  • Server-side order guard. Every OrderPlacedEvent is assessed — the old /check endpoint required the storefront to call it, and nothing did.
  • Signal-based scoring engine with per-channel weights: velocity (IP/hour, IP/day, email/day, email daily value), order value, disposable emails, block/allow lists, high-risk countries, failed payments, plus-addressing detection (emails canonicalised before velocity counting), first-order-high-value.
  • CIDR range matching. Spamhaus DROP and FireHOL ship ranges; the old implementation stored them but never matched them. Prefix-filtered SQL + exact ipInCidr verification (unit-tested).
  • Enforcement modes per channel: off / monitor / enforce, with separate review and block thresholds and a plain-English status sentence.
  • Review queue workflow. Pending cases hold fulfilment (host hook: pendingOrderIds()); approve releases + emails the customer, reject cancels + emails. All decisions audited.
  • Multi-tab admin dashboard on the verified HULO admin design system (WCAG AA both themes): Overview (KPIs, daily chart, top IPs with one-click block), Rules, Review queue (count badge), Lists (manual + feeds), Simulate (full signal breakdown, dry-run), Activity (filterable audit log), Settings (notifications).
  • Licensing via the HULO licence SDK: free tier = monitor + manual lists + simulate; enforce, feed sync and alerts require a licence. Update banner + heartbeat as across the HULO suite.
  • Retention: audit log pruned nightly (default 180 days).
Changed
  • Table names are inherited from the pre-plugin implementation (fraud_config, fraud_log, fraud_blocked_orders, fraud_blocklist, fraud_whitelist, fraud_notification_config) — upgrading preserves all live data; new columns are added automatically.
Security
  • Admin REST surface now requires an authenticated admin session (ReadCatalog / UpdateCatalog) — previously these endpoints were mounted without any auth guard. The public /check endpoint is rate-limited and returns a minimal shape (no signal internals to probe).