[SEC-004] services/staffThread.js — addRoleMembersToThread previously called
the unscoped guild.members.fetch() on every ticket creation, chunking every
member of the guild. With STAFF_THREAD_AUTO_ADD_ROLE on and a 50-member
staff role, the 300ms-per-add loop also blocked ticket creation for ~15s.
- Read role.members directly (computed from guild.members.cache, kept in
sync via the GuildMembers gateway intent set on the client). Skip the
explicit unscoped fetch in the hot path.
- Cache-cold fallback: one scoped guild.members.fetch({ withPresences:
false }) — irrelevant presence sync stays off the wire.
- createStaffThread no longer awaits the add-loop. Wraps the call in
setImmediate(...) so ticket creation returns immediately while the
rate-limited add-loop runs in the background.
[SEC-005] services/debugLog.js — stacks/messages posted to the debug
channel could leak customer email addresses (interpolated through ticket
errors) and Discord member/channel IDs. Add a redactPII helper applied to
both logError's message + stack and logWarn's body:
- Email regex /[\w.+-]+@[\w.-]+\.\w+/g → [EMAIL_REDACTED]
- Discord snowflake /\b\d{18,20}\b/g → [ID_REDACTED]
interaction.user.tag in the User: line is intentionally not redacted —
it's needed for triage and is not PII (Discord usernames are public).
- Rename CONFIG.TRANSCRIPT_CHAN -> CONFIG.TRANSCRIPT_CHANNEL_ID and
CONFIG.LOG_CHAN -> CONFIG.LOGGING_CHANNEL_ID across 9 callsites so
CONFIG keys match their .env names — no more "grep .env, find nothing"
for new readers
- Replace handlers/commands.js#hasStaffRole with utils.js#isStaff
(was a verbatim copy)
- Delete utils.js#enforceEmbedLimit and its 2 callsites; both inputs are
bounded well under the 6000-char Discord embed cap, so the trim was
defensive code that never fired
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove no-op log stubs (logGmail, logAutomation, logSecurity, logSystem)
and ~17 callsites; dead counters in tickets.js and gmail-poll.js go too
- Dedup three near-identical Gmail send paths into sendThreadedEmail helper
- Drop dead Mongoose fields: broccoliniTicketId, lastSyncedBroccoliniArticleId,
renameCount, renameWindowStart, reminderSent, staffChannelId,
unclaimedRemindersSent, lastMessageAuthorIsStaff
- Drop dead config fields and their .env.example entries
- Inline api/botClient.js (3-line wrapper, 2 callers)
- Trim unused exports across utils.js, tickets.js, configSchema.js, debugLog.js
- Fix handlers/messages.js to use isStaff() — old partial check ignored
ADDITIONAL_STAFF_ROLES, so those members were treated as customers
- Drop unused deps p-queue + dotenv-expand; move mongodb to devDependencies
Net: -583 LOC source + -57 LOC lockfile. All 23 modules load clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>