Skip to content

Releases: ATTACKSimulator/events

v1.4.0

Choose a tag to compare

@daniesy daniesy released this 21 Oct 18:48

v1.4.0

Two new events for the ClickFix and OAuth consent attack types.

New

  • command_copied: on ClickFix pages. When the user clicks an element marked data-ats-copy-command, the library copies the command to the clipboard and sends the event with type: "command". The command comes from the attribute value, or from the element's text if the value is empty. The page then redirects to redirectUrl.
  • consent_granted: on OAuth consent pages. When the user clicks an element marked data-ats-consent, the library sends the event with its data-scopes as type (e.g. "mail.read files.read"). It blocks the element's own action, so a submit button with the attribute no longer submits its form, then redirects to redirectUrl.
  • Tutorial: both events get their own tutorial step in all 10 locales. A copied command ends with the malware lesson, granted consent with the permissions lesson.
<button data-ats-copy-command="cmd /c echo verified">Copy</button>
<button data-ats-consent data-scopes="mail.read files.read">Allow</button>

Changed

  • button_clicked ignores elements that carry data-ats-copy-command or data-ats-consent, so a single click is never recorded twice. data-ignore still opts an element out.

Internal

  • Unit tests now run with Vitest in jsdom (npm test).

Platform side: red-hafen, the stats writers and the reports count both events as affected. command_copied is recorded as infected damage. consent_granted damage depends on the scopes: mail scopes count as emails, anything else as sensitive.

v1.3.2

Choose a tag to compare

@daniesy daniesy released this 13 Oct 11:27

Fixed

  • Redirects no longer drop query params (notably the tk token). performRedirect() built the target by concatenating redirectUrl with window.location.search. When redirectUrl already carried a query string — e.g. a router link with edu/pill_id/locale — the concatenation produced a second ?, so the appended tk token was folded into the preceding value and lost. The post-click redirect then landed unauthenticated and in the wrong locale. The destination is now assembled with URL + URLSearchParams, preserving params already on redirectUrl and merging the current ones in with &.

v1.3.0

Choose a tag to compare

@daniesy daniesy released this 31 Jul 01:43

v1.3.0 — Teach users what they missed

Until now, a user who fell for a simulated phishing page was redirected straight to the oops page. They learned that they failed, but not what they should have noticed. This release adds an interactive tutorial that inspects the page they fell for, walks them through the warning signs they missed, and then performs the same redirect.

It is off by default. Existing integrations behave exactly as before.

const ev = new ATSEvents({
  apiKey: "YOUR_API_KEY",
  tutorial: true,
});
ev.listen();

What the tutorial explains

The page is scanned the moment the event fires, and every dangerous item found becomes its own step with the element highlighted in place.

  • The address — not a generic warning, but why this URL should have raised suspicion: a raw IP, a lookalike built from special characters, a brand parked in a subdomain, a throwaway TLD, or an address long enough to push the real name out of sight. The part that decides ownership is highlighted, and the readout keeps the deceptive bits other tools hide, such as the user@host trick and non-default ports.
  • Credential and card forms — recognised from labels, ARIA text and surrounding copy, so forms with no <form> element and password fields typed as text are still caught.
  • Downloads — links and buttons that would have delivered a file.
  • Links that lie — where the visible text and the real destination differ.
  • Pressure tactics — the deadline or account-suspension threat is quoted back to the user in their own language.
  • Brand impersonation — when a page presents itself as an organisation the address does not belong to.
  • Permissions — microphone, camera, location, notifications, clipboard and browser extensions each get their own explanation of what was just granted.

Brand detection without a list

Rather than shipping a list that would always be out of date, the tutorial works out which organisation a page claims to be by scoring independent signals — the copyright line, og:site_name, logo alt text and file names, inline SVG wordmarks, the title and headings — and only names a brand once they agree. That works for organisations nobody bundled a list for, in any language.
If your campaign already knows what the landing page imitates, send it as ats_brand in the campaign token and that value is used instead of any guess.

Languages

Ten locales, each reviewed by a native speaker: en-US, ro-RO, es-ES, ca-ES, es-419 (also accepted as es-LATAM), pt-BR, pt-PT, fr-FR, de-DE, el-GR. The language comes from the locale option, then the campaign token, then the browser, falling back to en-US.

New options

Option Type Default Description
tutorial boolean false Show the tutorial instead of redirecting immediately
tutorialSkippable boolean true Show a skip link
locale string auto Force the tutorial language

Smaller bundle

Source maps were being inlined as base64 into the shipped file, where they accounted for three quarters of every byte. They are now emitted as separate .map files, which browsers only fetch when devtools are open.

Before After
bundle.min.js 244 KB 190 KB
gzipped ~73 KB 61 KB

The bundle is smaller than the previous release despite carrying the whole feature.

Reliability

The redirect is the one thing that must never be lost, so it is now guaranteed on every path. A throwing event subscriber, a failed page scan, or an overlay that the host page hides or removes all fall back to navigating. Since landing pages are attacker-authored, the overlay is checked after opening and the redirect happens anyway if it is not genuinely on screen.

Toolchain

Dependencies were updated: ESLint 10 (flat config), TypeScript 6,
@typescript-eslint 8, webpack-cli 7 and ua-parser-js 2.

Contributors should note that TypeScript stays on 6.x. TypeScript 7 is the Go rewrite and exposes a different JS API, which ts-loader cannot drive; moving to it would mean switching to a different loader.

There are no API changes for consumers of the package.

v1.2.15

Choose a tag to compare

@daniesy daniesy released this 17 Jun 22:35

Changed

  • Added per-input deduplication for input_filled.

  • Each input field now sends input_filled only once per page session.

  • Multiple different input fields can still each send their own event.

  • Later edits to the same input are ignored.

  • Replaced the old debounce-based input handling with explicit deduplication.

  • Removed the unused debounce helper and shouldDebounce event contract.

  • Updated README documentation for the new input_filled behavior.

  • Added a retrying event sender.

  • New EventSender manages in-memory pending, sending, sent, and failed queues.

  • sg_event_id is assigned once per queued event and reused across retries.

  • Retries transient failures: network errors, 408, 429, and 5xx.

  • Non-retryable 4xx responses fail immediately.

  • Improved remote response handling.

  • Remote.post() now returns response status metadata.

  • Invalid JSON responses no longer break response handling.

Notes

  • Redirecting events use a single send attempt before navigation to avoid delaying user redirects.

v1.2.14

Choose a tag to compare

@daniesy daniesy released this 16 Jun 21:35

What's Changed

  • Added frontend client IP collection to browser event data.
  • Included data.ip before data.ua in event payloads when IP lookup succeeds.
  • Switched IP lookup to https://ipinfo.io/ip since only the IP value is needed.
  • Added a 1500ms abort timeout and no-referrer policy for the IP lookup.
  • Preserved event delivery when IP lookup fails or times out.
  • Cached browser info lookup in Manager while keeping listener registration and subscriptions synchronous.
  • Fixed the package main entrypoint to point to dist/bundle.js.
  • Cleaned up lint-blocking TypeScript declarations and formatting.
  • Regenerated dist/bundle.js and dist/bundle.min.js.
  • Updated the local test page for the current event testing flow.

Compatibility Notes

  • Public ATSEvents.listen() and trigger() behavior is unchanged.
  • Events still send without data.ip if ipinfo.io is blocked, unavailable, or times out.
  • Existing browser, OS, device, CPU, screen size, and UA fields remain in payload.data.

Validation

  • TypeScript check passed.
  • Production bundle build passed.

v1.2.13

Choose a tag to compare

@daniesy daniesy released this 04 Mar 19:45

Bug Fixes

  • Fixed memory leak where the manual_page_read event listener could not be properly removed due to using an anonymous function reference
  • Manual trigger now correctly bypasses scroll/visibility checks, ensuring page_read fires immediately when dispatched manually
  • Removed stray console.log from PageRead initialization

Code Quality

  • Formatting and style cleanup across PageRead (consistent spacing, semicolons, brace style)

v1.2.12

Choose a tag to compare

@daniesy daniesy released this 30 Oct 19:40

fix: resolve event listener memory leaks and add manual trigger support

  • Convert event handler methods to arrow functions to fix 'this' binding
  • Remove unnecessary .bind(this) calls for cleaner code
  • Add support for manual page read triggering via window.isManualTrigger
  • Fix event listener cleanup to prevent memory leaks

v1.2.11

Choose a tag to compare

@daniesy daniesy released this 28 Dec 01:00

Checking if parents are an anchor or button when validating the click event

v1.2.10

Choose a tag to compare

@daniesy daniesy released this 28 Dec 00:49

Updated the @click event detection: we're using Alpine.js on our landing pages; buttons with this event listener should be ignored because they most likely do something on the page.

v1.2.9

Choose a tag to compare

@daniesy daniesy released this 28 Dec 00:27

Added extraPayload
Set the click event as blocking (the page will redirect after a click).