CONFIG.JS
Configuration
Every threshold in GlassFrame lives in this one file. Start with the defaults, watch the security log channel for a week, and adjust - a very active server needs higher spam/mention thresholds than a quiet one. One caveat: this whole file is one merged config object for the entire bot process, not settable per guild - see Protocol Layers - Scope if this bot serves more than one server.
"use strict";
module.exports = {
// Prefix for all commands. This library never registers slash commands.
prefix: "!gf ",
// Documentation only - the actual channel resolution always goes through
// the required options.getLogChannel(guild) function you provide.
logChannelName: "security-logs",
roleAnalysis: {
// Role-name fragments (lowercased, substring match) that imply real authority.
protectedNames: [
"owner", "admin", "administrator", "moderator", "mod",
"staff", "discord staff", "security"
],
// Permission names (PermissionsBitField.Flags keys) treated as "dangerous"
// when granted to a role - watched by AntiNuke's grant watchdog.
dangerousPermissions: [
"Administrator", "ManageGuild", "ManageRoles", "ManageWebhooks",
"BanMembers", "ManageChannels"
],
// Permission names that mark a member as genuinely trusted staff.
trustPermissions: ["Administrator", "ManageGuild", "ManageRoles", "BanMembers", "KickMembers"],
// A role younger than this that already holds a dangerous permission is extra suspicious.
newRoleGraceMs: 1000 * 60 * 10
},
threatEngine: {
// Score halves every this-many ms of inactivity - stale flags fade instead of lingering forever.
decayHalfLifeMs: 1000 * 60 * 30,
tiers: { low: 20, medium: 45, high: 70, critical: 90 }
},
punishment: {
// While a case is open for a member, new signals merge into it instead of re-punishing/re-logging.
caseCooldownMs: 1000 * 30,
ladder: { low: "log", medium: "timeout", high: "kick", critical: "ban" },
timeoutDurationMs: 1000 * 60 * 10,
// Action used instead of kick/ban when the target holds a real trusted/protected role.
protectedTrustAction: "quarantine"
},
basicSecurity: {
spam: { windowMs: 6000, messageThreshold: 6, duplicateThreshold: 4 },
mentionSpam: { maxMentionsPerMessage: 8 },
nlp: { scamScoreThreshold: 0.62, phishingScoreThreshold: 0.55, raidCalloutThreshold: 0.3 },
// Seed domains for PhishingDatabase (src/security/PhishingDatabase.js).
// Empty by default - populate with what you actually see via
// `!gf phishing add <domain>` rather than shipping unverified blocklists.
linkGuard: { seedDomains: [] }
},
antiRaid: {
joinWindowMs: 10000,
joinThreshold: 6,
minAccountAgeMs: 1000 * 60 * 60 * 24 * 3,
usernameEntropyFloor: 2.6,
usernameEntropyCeiling: 4.6,
lockdownDurationMs: 1000 * 60 * 10,
altScoreThreshold: 55,
// Behavioral correlation: catches a raid that trickles in slowly enough
// to dodge the join-rate counter above, by noticing that several
// "unrelated" new joiners were actually all created within the same
// narrow window - a classic sign of a bulk-registered account farm.
cluster: {
windowMs: 1000 * 60 * 5,
minClusterSize: 4,
creationToleranceMs: 1000 * 60 * 2
}
},
antiNuke: {
windowMs: 15000,
thresholds: {
channelDelete: 3, channelCreate: 5, roleDelete: 3, roleCreate: 5,
memberBanAdd: 4, memberKick: 5, webhookCreate: 3, botAdd: 1, guildUpdate: 3,
emojiDelete: 5, emojiCreate: 8, stickerDelete: 3, stickerCreate: 5
},
watchDangerousGrants: true,
autoRevertDangerousGrants: false,
watchVanityChanges: true,
// A brand-new invite with no expiry and no use limit, created by anyone
// other than the owner, is worth a look the moment it's created.
watchInviteAbuse: true,
// Watches @everyone's channel permission overwrites specifically - a
// channel getting locked out for everyone, or a private channel getting
// opened to everyone, is a nuke technique that doesn't delete anything.
watchOverwriteAbuse: true,
// A webhook that starts pushing a burst of messages within minutes of
// being created is a classic "nuke via webhook" pattern - this catches
// it even though the messages themselves look like normal messageCreate
// events. The offending webhook is deleted as immediate containment.
webhookAbuse: {
enabled: true,
messageThreshold: 8,
windowMs: 8000,
// How long after creation a webhook is still considered "recent" enough to watch.
watchPeriodMs: 1000 * 60 * 5
},
// Forensic role/channel snapshot retention, taken the moment a threshold trips.
snapshotTtlMs: 1000 * 60 * 60
},
aiModeration: {
// Empty by default - the layer stays idle until you supply at least one key.
apiKeys: [],
model: "llama-3.3-70b-versatile",
endpoint: "https://api.groq.com/openai/v1/chat/completions",
timeoutMs: 6000,
cacheTtlMs: 1000 * 60 * 5,
// A conservative self-imposed budget shared across the whole key pool, not Groq's own rate limit.
maxCallsPerMinute: 20,
// Messages whose local NLP score falls in this band get a second, AI-backed opinion.
grayZone: { min: 0.30, max: 0.62 }
},
logging: {
// Near-duplicate log events inside this window merge into one message with a running count.
aggregationWindowMs: 15000,
// Minimum time between sends to the same guild's log channel.
minSendIntervalMs: 1200
},
// Bounded-concurrency controls (src/core/EventQueue.js) so a burst - a raid,
// a nuke attempt, a wave of gray-zone messages - queues smoothly instead of
// firing every Discord/Groq call at once and tripping a rate limit right
// when speed matters most. See docs/PERFORMANCE.md.
performance: {
actionConcurrency: 4, // simultaneous ban/kick/timeout/quarantine calls
aiConcurrency: 3, // simultaneous outbound Groq requests
// Per-user, per-guild floor between prefix commands, so a doubled
// keypress can't spawn two control panels or send a command twice.
commandCooldownMs: 3000
},
cache: {
// Default for GlassFrame's debug flag - logs every cache SET/HIT/MISS/EXPIRE to the console.
// See docs/CACHE_ARCHITECTURE.md. Overridden by the `debug` constructor option if provided.
debug: false
},
// The owner-only `!gf engine` dashboard - see docs/ENGINE.md. Being an
// owner (options.owners) is required no matter what; a password is an
// optional second factor on top of that for the initial command only -
// page-navigation button clicks are still gated by isOwner() but don't
// re-prompt for the password.
engine: {
// Set via an environment variable in your own bot, e.g.
// process.env.GLASSFRAME_ENGINE_PASSWORD - never hardcode a real
// password into a committed config.js. Leave null to skip the second
// factor and rely on the owners list alone.
password: null,
maxAttempts: 3,
lockoutMs: 1000 * 60 * 10
}
};