Vincent CassiauBackend engineer

Sanctuary: protecting World of Warcraft players from online harassment

Sanctuary, a World of Warcraft addon against online harassment: what it blocks and how it was designed.

Sanctuary is a World of Warcraft addon that protects players from harassment. It stops unwanted invitations, whispers, duels, trades and messages before they are displayed, with no popup, no chat line and no sound. By default, only people the player already knows can reach them. I built it because the game’s own tools cannot keep up with someone who creates new accounts to keep harassing.

The project started from one person’s very real problem, and other players face the same thing. An MMO is a game people invest in socially, and many look for a safe place in it. Online harassment can follow them there like anywhere else.

This article walks through the design choices that followed, the edge cases Blizzard does not document, and how I tested an addon whose behaviour partly exists only inside the game.

What /ignore does not cover

World of Warcraft has /ignore. Since patch 9.1.5, released in late 2021, it blocks every character on the ignored player’s WoW account, so switching characters no longer gets around it (patch 9.1.5 notes). It does not follow someone who creates a new account, though. Free accounts make that easy, and a determined person can start over indefinitely.

Some addons already filter whispers or invitations from strangers. In my view, none of them covered every kind of interaction a player may need protection from.

So Sanctuary starts from the opposite question: who is allowed to reach me? In the default mode, only the guild, friends, the current group or raid and names allowed by hand get through. A recreated account is still a stranger, so it is blocked.

Filtering can then be tuned in detail. A second mode lets everyone through except blocked names. The two approaches can be combined: the block list accepts patterns, and a fragment of a name is enough to block a whole family of characters. A blocked name wins over everything, even the guild or the group. Each kind of interaction has its own setting:

  • Group and guild invitations
  • Whispers from in-game characters
  • Duels and trades
  • Messages in /say and /yell, and emotes
  • Spam in the public channels
  • Mail from filtered people

Blocking before the player sees anything

Hiding a chat line is the easy part. The game lets addons filter messages before they are displayed, and that is where Sanctuary decides whether a line appears.

An invitation is harder. It opens a popup and plays a sound. When the sender is blocked, Sanctuary declines the invitation through the game’s own functions, and the popup never becomes visible.

Sound took the most attempts. The popup plays it the moment it opens, and I did not want to let any of it through, not even half a second. The current version mutes first and decides afterwards. Sanctuary captures the popup’s original sound and mutes the interface sounds it triggers. If the sender is allowed, the addon plays those sounds itself. If not, nothing is heard.

A muted sound survives an interface reload. Sanctuary therefore records in its saved data that a sound is muted, and restores it on the next load if the client was reloaded in between.

Two settings follow the same caution. Deleting mail is the only irreversible thing the addon does, so it only happens if the player turns it on. The Journal, which records what was blocked, hides message text by default. It does not show a harassed player what was sent to them until they ask.

Midnight’s restrictions in instances

The hardest bug to isolate came up during beta testing. In dungeons, group invitations or private messages sometimes got through, while everything worked outside instances.

The cause lies in Midnight. Since patch 12.0.0, the game restricts what addons can read during boss encounters, Mythic+ keystones, PvP matches and on restricted maps such as dungeons and raids. The affected messages arrive as “secret values”: an addon can store them and pass them along, but cannot compare or test them (warcraft.wiki.gg). In the middle of a dungeon, Sanctuary can no longer tell whether a line comes from a player, the game or another addon.

A reliable decision was no longer possible. I added a strict mode, experimental and off by default: while the player is grouped or in an instance, Sanctuary hides any system message it cannot read, erring on the side of caution.

local function shouldSuppressSecretGroupInviteSystemMessage(msg)
    if not isRestrictedValue(msg) then return false end
    return (evaluateStrictSecretSuppression())
end

This mode has a cost: it also hides legitimate system messages. It remains an option, to turn on only if unwanted invitations still get through in dungeons, raids or PvP.

One decision per message

In an earlier version, every chat message was judged twice. The filter decided whether the line was displayed, and the event handler decided what to log and count. Both copies repeated the same sequence of rules.

Within a single release, three fixes landed on the wrong copy. The last one caused a visible bug: a note a player sent to themselves was swallowed by the handler while the filter let it through.

Today, filters and handlers are generated from a single table, and every decision goes through one function per kind of interaction. A new condition is added in the decision, never in the code that consumes it. The interface decides nothing: it only draws. The principle is common in backend work, where a business rule lives in one place. Here it also makes the rules testable without launching the game.

Testing outside the game, then inside it

A World of Warcraft addon is fragile. It depends on an API that keeps changing, on interfaces that redraw themselves and on edge cases nobody documents. Every case found during development had to stay covered.

The repository contains a test harness of about 15,000 lines of Lua. It simulates the game’s API, from frames to friend and guild rosters, the mailbox and secret values. It loads the addon in the order of its TOC file and fires events the way the client would. Like the addon, it depends on no library and no framework.

$ lua5.4 tests/runtime_harness.lua
OK: 5867 assertions

It runs on every push. A second check makes sure the addon still parses under Lua 5.1, the version the client runs.

The harness sees neither real behaviour in instances nor interactions with the game’s interfaces. Two other levels of testing fill that gap.

The first is AI. We agreed on the tests to run, then the AI played them directly in WoW through computer use. It could also run Lua from the game’s chat to inspect the addon’s state, trigger a specific case or spot a bug. It took over the most tedious interaction tests.

The second is beta testing. A debug mode lets a player copy the addon’s log and paste it into a GitHub issue, and nothing is sent automatically. Those logs and that feedback are what let me isolate the instance problem.

Where reliable documentation is missing, tests outside the game are not enough on their own. They keep what has already been learned, and only in-game tests and real feedback teach anything new. I keep both.

AI also helped me iterate quickly, research documentation and edge cases, and work on the interface. With so many options, the hardest part was keeping the addon from becoming overwhelming. Handing off part of the execution mostly leaves me room to think about architecture and the best implementation.

From prototype to 1.2.0

Sanctuary started as a prototype in April 2026. It already had some of these bugs, a completely different interface and fewer features. Version 1.0.0, released on 27 August 2026, is the first real version. Version 1.1.0 followed on 3 September with mail handling, then 1.2.0 on 27 September, in seven languages.

Releasing is automated. A version tag runs the tests, then packages the addon and publishes it on GitHub and CurseForge. Creating that tag remains a manual decision, and release notes are written for players rather than generated from commits.

The trade-offs

Protecting by default sometimes costs a legitimate contact. A stranger who invites the player directly into a group, by right-click or by command, is blocked like everyone else. Groups formed through the game’s group finder are not affected. An option also trusts group members after five minutes spent together, without ever overriding a blocked name.

Strict mode trades a little visibility for peace of mind, knowingly. Sanctuary never touches Battle.net contacts, which are managed in Battle.net, and it complements /ignore rather than replacing it.

Sanctuary is available on CurseForge, and its source code is public on GitHub.