CHANGELOG.MD

Changelog

All notable changes to GlassFrame Protocol are logged here. This library follows semantic versioning (MAJOR.MINOR.PATCH). Runtime version metadata lives in src/core/VersionInfo.js (VersionInfo.info() / VersionInfo.banner()), so code can read the current version without parsing this file.

[2.1.0] - 2026-08-01

Additive - nothing from 2.0.0's API changes. New detections, a plugin system, per-guild customization, and an owner-only cross-server dashboard.

Added - security detections

  • Dangerous role grants to a member. The existing permission-grant watchdog only caught a role's own permissions changing (RoleUpdate). It missed the other half: someone with ManageRoles handing an already-dangerous role directly to a member (MemberRoleUpdate) without ever touching the role's definition. Now watched, gated the same way - only flagged when the executor isn't recognized trusted staff.
  • Channel permission-overwrite abuse. A nuke doesn't have to delete anything - locking @everyone out of a public channel, or opening a private one up to @everyone, does equivalent damage through permission overwrites. Only @everyone's own overwrite is watched; a role- or member-specific overwrite change is routine administration and ignored.
  • A dedicated @everyone/@here guard in Basic Security, weighted well above regular mention spam (a single mass ping reaches every member at once) and skipped for recognized trusted staff, so a real admin's announcement isn't flagged.
  • Raid-recruitment language is now actually used. raidCallout scoring existed in the NLP engine already but was never wired into BasicSecurityLayer's real decision logic - fixed. Also added a specific invite-link + raid-language combo check (the "join this server to raid with us" pattern), and expanded the raid lexicon to catch common word forms ("raiding", "raids") that the bare-verb-only version missed.

Added - extensibility

  • frame.registerLayer(name, layerInstance) - add a custom security layer to the same shared pipeline the built-in four use. Must extend core/Layer; gets per-guild enable/disable, state persistence, and !gf status/!gf metrics visibility for free, since those now iterate frame.layers dynamically instead of a fixed list. Does not get an automatic button on the 5-button panel (that stays fixed at 5).
  • frame.registerAction(name, handler) - add a punishment action beyond ban/kick/timeout/quarantine, referenceable from config.punishment.ladder. Runs through the same action queue as the built-in ones.
  • Per-guild custom prefix - !gf prefix set <newPrefix> / !gf prefix reset. config.prefix always still works everywhere as a fallback; an overlapping custom/default pair resolves by trying the longer one first.
  • Per-layer log channel routing - getLogChannel(guild, layerName) now receives the reporting layer's name as an optional second argument, so different layers can route to different channels. Fully backward compatible - a getLogChannel that only takes one parameter keeps working unchanged.

Added - the owner-only engine dashboard

  • !gf engine - a bot-wide, cross-server dashboard: layer adoption across every server, the busiest servers by event volume, AI usage (Groq key pool/cooldowns/call volume), queue and cache health, and a live feed of the last several things logged anywhere. See docs/ENGINE.md. Deliberately not listed in !gf help - a per-server admin shouldn't know it exists, let alone see data about other servers.
  • Its own permission model, separate from every other command: options.owners (a list of Discord user IDs) is required regardless, and an optional config.engine.password is a second factor on the initial command only - page-navigation clicks re-check isOwner() but don't re-prompt for the password. Wrong guesses count toward a lockout (config.engine.maxAttempts, config.engine.lockoutMs), and the command message is deleted immediately either way so the password doesn't sit visible in channel history.
  • 5 pages (Overview, Servers, AI, Performance, Activity Log), navigated with a row of tab buttons - this is a second interactive surface, entirely separate from the 5-button panel and the metrics command's own button.
  • frame.getEngineReport() - the raw data behind the dashboard, if you want it for your own tooling instead of the rendered message.
  • PerformanceMonitor.recordEvent(name, guildId) now optionally tracks per-guild activity (topGuildsByActivity()) alongside the existing by-name counters.

[2.0.0] - 2026-08-01

Breaking. Layer activation and the whitelist were accidentally global across every server the bot is in - this release makes them per-guild, the way they were always supposed to work, and adds real state persistence.

Fixed (breaking)

  • Layer on/off state is now per-guild. Previously, Layer.enabled was one shared boolean - arming AntiRaid from one server's control panel silently armed it for every server the bot serves. getStatus(guildId) accepted a guildId but never actually used it. Both are now genuinely per-guild: Layer.enabledGuilds is a Set<guildId>, and frame.enableLayer(name, guildId) / frame.disableLayer(name, guildId) now require a guildId argument. Calling either without one throws, rather than silently doing the wrong thing.
  • The whitelist is now per-guild. frame.whitelist was a single global Set<userId> - whitelisting someone in one server exempted them everywhere. It's now a Map<guildId, Set<userId>>; use the new frame.addToWhitelist(guildId, userId), frame.removeFromWhitelist(guildId, userId), and frame.isWhitelisted(guildId, userId) instead of touching the Map directly. The constructor's whitelist: [...] option still works exactly as before from the outside - it now seeds that list into every guild (present and future) the first time GlassFrame sees it, rather than applying globally.
  • State now actually persists. stateStore was constructed and then never read from or written to anywhere. Every layer toggle and whitelist change is now saved per-guild, and restored at startup (and automatically for any new guild the bot joins) - see docs/STATE.md.
  • Fixed a related bug in BasicSecurityLayer: its spam-rate buffer was keyed by userId alone, so the same user active in two different servers could incorrectly share one rate-limit window. Now keyed by guildId:userId.

Migrating from 1.x

If you only use the built-in control panel and !gf commands, nothing in your bot's code needs to change - update the package and you're done; the panel and commands already operate within a specific server, they just didn't respect that internally before now.

If your own code calls these directly, update the call sites:

1.x 2.0.0
frame.enableLayer(name) frame.enableLayer(name, guildId)
frame.disableLayer(name) frame.disableLayer(name, guildId)
frame.whitelist.add(userId) frame.addToWhitelist(guildId, userId)
frame.whitelist.delete(userId) frame.removeFromWhitelist(guildId, userId)
frame.whitelist.has(userId) frame.isWhitelisted(guildId, userId)
layer.enabled layer.isEnabled(guildId)

options.autoStart in the constructor is unchanged - still a plain array of layer names - but now correctly applies per-guild (to every guild the bot is already in, and automatically to any new one it joins) instead of turning a layer on everywhere at once.

Added

  • frame.ready - a Promise that resolves once every guild the bot was already in at construction time has finished restoring its saved state. State restoration is async (a real stateStore may hit disk/a database), so this exists for anyone who wants an explicit guarantee rather than relying on the fact that, in practice, it resolves faster than any Discord event could arrive.
  • frame.getGuildMetrics(guildId) - one server's layer status, whitelist size, open-case count, and flagged-member count.
  • !gf metrics now shows this server's numbers by default instead of bot-wide totals, with one button on the message to switch to the global (all-servers) view and back. This is a second button, separate from the 5-button panel - the panel is still exactly 5 buttons; the metrics command now has 1 of its own.
  • ProtocolCache.countByPrefix(prefix) - powers the per-guild counts above by counting non-expired guildId:userId-keyed entries for one guild.
  • docs/STATE.md - the per-guild state and persistence model in full.

[1.2.1] - 2026-07-31

Added

  • homepage and discord fields in package.json linking to the docs site and the support server.

[1.2.0] - 2026-07-31

Packaging and licensing changes to make this publishable to npm. No functional/runtime changes to any layer, engine, or command from 1.1.0.

Added

  • GETTING_STARTED.md - onboarding guide for adding GlassFrame to a bot you already have running (this is the file to hand someone alongside the package).
  • scripts/build.js + npm run build - produces dist/, the folder that actually gets published (see docs/PUBLISHING.md in the repo). Uses terser for real minification if it's installed as a devDependency, otherwise copies files through unmodified rather than risking a hand-rolled comment stripper - several files contain string literals with // in them (the Groq endpoint URL, for one), which a naive regex-based stripper would corrupt.
  • docs/PUBLISHING.md - step-by-step npm publishing (same steps on Termux, Windows, and Linux), including what "files" in package.json does and does not upload. Kept in the repo for maintainers; not part of this site.
  • .gitignore for node_modules/ and the generated dist/.

Changed

  • License: replaced the MIT license with a custom Limited Use License (see LICENSE) - grants installing and using the package in your own bot (personal or commercial), and withholds copying/modifying/redistributing the source or reselling the library itself. This is the real mechanism for restricting reuse; a plain npm install-able package cannot be made technically unreadable once it's on someone's disk, since Node has to parse the JS to run it. Not legal advice - see the note at the top of LICENSE.
  • package.json: main now points at dist/index.js; added files (whitelists what npm publish actually uploads - src/ and the root config.js/index.js are deliberately excluded), prepublishOnly (so dist/ can't go stale at publish time), publishConfig.access: "public", and placeholder author/repository/homepage/bugs fields to fill in.

[1.1.0] - 2026-07-31

Adds a batch of security extensions, a performance layer, and faster processing under load - no breaking changes to the 1.0.0 API.

Added

  • PhishingDatabase (src/security/PhishingDatabase.js) - replaces the inline link regex in Basic Security with a dedicated, server-editable module: a live blocklist (!gf phishing add/remove/list) plus general-purpose heuristics (raw-IP links, punycode/homograph domains, digit-for-letter brand look-alikes, known shorteners). Ships with an empty seed list by design - a hardcoded "known bad domains" list goes stale immediately and risks flagging an innocent domain on unverified information.
  • AntiNuke now watches three more attack surfaces:
  • Emoji/sticker bursts - mass emoji or sticker deletion/creation is tracked the same way channel/role bursts already were.
  • Invite abuse - an unrestricted invite (no expiry, no use limit) created by anyone other than recognized trusted staff is flagged; the same pattern from real staff is left alone, so this doesn't turn into noise on a server that already uses a permanent invite link.
  • Webhook message flooding - a webhook created moments ago that then sends a burst of messages is deleted immediately and traced back to whoever created it. This closes a real gap: webhook messages carry no guild member, so they were invisible to every other layer's per-member checks.
  • AntiRaid now also runs creation-time cluster correlation (AntiRaidLayer._clusterScore): a bulk-registered account farm that trickles in slowly enough to dodge the join-rate counter is still caught by noticing that several recent joiners' accounts were all created within the same narrow window.
  • EventQueue (src/core/EventQueue.js) - a bounded-concurrency task queue. Every Discord mutation PunishmentEngine performs and every outbound Groq request now runs through one of two queues (frame.actionQueue, frame.aiQueue) instead of firing unbounded, so a burst (raid, nuke, wave of gray-zone messages) can't trip a rate limit by hammering it all at once. See docs/PERFORMANCE.md.
  • PerformanceMonitor (src/core/PerformanceMonitor.js) and frame.getMetrics() / !gf metrics - lightweight in-process event counts and rolling latency averages, plus every cache's stats, in one snapshot. No external dependency, no network call.
  • Command cooldown (performance.commandCooldownMs, default 3s) - prefix commands are now throttled per user per guild.
  • docs/PERFORMANCE.md.

Changed

  • GroqClient.classify() split into a thin queued/cached front end and an internal _request() - the per-request timeout clock now starts only once a request actually leaves the queue, so a busy moment can no longer cause a false "AI didn't respond in time".
  • PunishmentEngine: an open case can now escalate its action if continued signals push the tier past what it was last acted on at (e.g. log -> timeout -> kick -> ban within the same incident), instead of freezing at whatever action was first decided for the entire debounce window. Never downgrades, and a trusted member's ceiling (quarantine/alert) is still respected on every re-evaluation.

[1.0.0] - 2026-07-29

Initial release of GlassFrame Protocol - a full architectural rebuild of the previous guardian-security-module, renamed and restructured as a standalone library rather than a bundle of bot-side modules.

Added

  • Layered architecture (Layer base class): AntiRaid, AntiNuke, Basic Security, and AI Moderation are now independently toggleable layers that share one pipeline instead of four separate modules that each punished on their own. enable()/disable() fully attach/detach a layer's listeners, so a disabled layer does zero work.
  • ThreatEngine - a shared, decaying per-member risk score that every layer reports to, so scattered small flags across layers combine into one picture instead of triggering separate, redundant reactions. Tiers (none/low/medium/high/critical) drive the punishment ladder.
  • RoleAnalyzer - role hierarchy checks (the protocol never attempts an action it lacks permission for) and role name analysis: flags roles whose name implies authority but hold no real permissions (impersonation bait), and roles that quietly hold dangerous permissions under an innocuous name. Exposed via the new !gf scan command and an automatic audit the moment AntiNuke is armed.
  • PunishmentEngine - the single place that actually bans/kicks/times out/ quarantines a member. Consults role trust level before acting (a real admin is never auto-banned - it's flagged for human review instead), and debounces repeat signals against the same member so one incident produces one action and one log line, not a stream of them.
  • AIModerationLayer + GroqClient - optional Groq-powered second opinion for messages that land in the gray zone between local NLP's clean/dirty thresholds. Supports an API key pool with automatic per-key cooldown on rate-limit responses, so one exhausted key doesn't take moderation down. Verdicts are cached briefly so repeated/near-identical messages don't each cost a fresh call. Disabled by default - supply aiModeration.apiKeys to turn it on.
  • SmartLogger - merges near-duplicate log events inside a short window into a single message with a running count instead of one message per event, and enforces a minimum interval between sends per guild.
  • 5-button control panel (!gf panel) - Activate/Deactivate AntiRaid, AI Moderation, AntiNuke, and Basic Security individually, plus a fifth "Full Protocol" button that arms or disarms all four together. Every layer starts disabled until explicitly armed.
  • Prefix-only command surface (!gf panel|status|scan|whitelist|help) - no slash commands are registered by this library, by design.
  • Dangerous permission-grant watchdog and identity-change watchdog inside AntiNuke - a role quietly gaining Administrator, or the server's name/ icon/vanity URL changing, is flagged the moment it happens rather than waiting for a repeat-offense threshold. A forensic role/channel snapshot is captured the moment any AntiNuke threshold trips.
  • Alt-account composite scoring in AntiRaid (account age + username entropy
  • missing avatar) alongside the existing join-velocity lockdown.
  • Link heuristics in Basic Security: raw-IP links, punycode domains, and Discord look-alike domains.
  • Pluggable state persistence (MemoryStateStore by default, JSONFileStateStore for restart-safe layer state with zero extra dependencies - no native modules, ARM64/Termux-friendly). Update: as originally shipped in 1.0.0 the store was accepted by the constructor but never actually read from or written to - this was fixed for real in 2.0.0, see docs/STATE.md.
  • docs/CACHE_ARCHITECTURE.md and docs/PROTOCOL_LAYERS.md - internal architecture references for anyone extending the library.

Changed

  • Renamed from guardian-security-module to glassframe-protocol. Directory layout moved from a flat modules/ + utils/ split into src/core, src/layers, src/moderation, src/ai, src/logging, src/ui, and src/commands.
  • All punishment decisions moved out of individual layers and into PunishmentEngine; layers now report signals instead of calling .ban() / .kick() / .timeout() directly.
  • Components V2 output kept (no legacy EmbedBuilder anywhere) and extended to support the control panel's button row and event-count badges on aggregated log messages.

Fixed

  • Anti-nuke's self-exemption check previously compared executor.bot === false against the client's own ID, which could never be true for a bot account; it now simply skips audit entries the client itself generated.

[0.x] - guardian-security-module (superseded)

The pre-GlassFrame history shipped as guardian-security-module v1.0.0: local-NLP-only detection, flat antiRaid.js / antiNuke.js / security.js modules, each acting immediately and independently with no shared scoring, no role-name analysis, no AI layer, and no control panel.