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 withManageRoleshanding 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.
raidCalloutscoring existed in the NLP engine already but was never wired intoBasicSecurityLayer'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 extendcore/Layer; gets per-guild enable/disable, state persistence, and!gf status/!gf metricsvisibility for free, since those now iterateframe.layersdynamically 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 fromconfig.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.prefixalways 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 - agetLogChannelthat 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. Seedocs/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 optionalconfig.engine.passwordis a second factor on the initial command only - page-navigation clicks re-checkisOwner()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.enabledwas one shared boolean - arming AntiRaid from one server's control panel silently armed it for every server the bot serves.getStatus(guildId)accepted aguildIdbut never actually used it. Both are now genuinely per-guild:Layer.enabledGuildsis aSet<guildId>, andframe.enableLayer(name, guildId)/frame.disableLayer(name, guildId)now require aguildIdargument. Calling either without one throws, rather than silently doing the wrong thing. - The whitelist is now per-guild.
frame.whitelistwas a single globalSet<userId>- whitelisting someone in one server exempted them everywhere. It's now aMap<guildId, Set<userId>>; use the newframe.addToWhitelist(guildId, userId),frame.removeFromWhitelist(guildId, userId), andframe.isWhitelisted(guildId, userId)instead of touching the Map directly. The constructor'swhitelist: [...]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.
stateStorewas 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) - seedocs/STATE.md. - Fixed a related bug in
BasicSecurityLayer: its spam-rate buffer was keyed byuserIdalone, so the same user active in two different servers could incorrectly share one rate-limit window. Now keyed byguildId: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 realstateStoremay 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 metricsnow 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-expiredguildId: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
homepageanddiscordfields inpackage.jsonlinking 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- producesdist/, the folder that actually gets published (seedocs/PUBLISHING.mdin the repo). Usesterserfor 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"inpackage.jsondoes and does not upload. Kept in the repo for maintainers; not part of this site..gitignorefornode_modules/and the generateddist/.
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 plainnpm 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 ofLICENSE. package.json:mainnow points atdist/index.js; addedfiles(whitelists whatnpm publishactually uploads -src/and the rootconfig.js/index.jsare deliberately excluded),prepublishOnly(sodist/can't go stale at publish time),publishConfig.access: "public", and placeholderauthor/repository/homepage/bugsfields 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 mutationPunishmentEngineperforms 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. Seedocs/PERFORMANCE.md.PerformanceMonitor(src/core/PerformanceMonitor.js) andframe.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 (
Layerbase 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 scancommand 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 - supplyaiModeration.apiKeysto 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 (
MemoryStateStoreby default,JSONFileStateStorefor 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, seedocs/STATE.md. docs/CACHE_ARCHITECTURE.mdanddocs/PROTOCOL_LAYERS.md- internal architecture references for anyone extending the library.
Changed
- Renamed from
guardian-security-moduletoglassframe-protocol. Directory layout moved from a flatmodules/+utils/split intosrc/core,src/layers,src/moderation,src/ai,src/logging,src/ui, andsrc/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
EmbedBuilderanywhere) 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 === falseagainst 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.