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
  }
};