Integrate all open PRs (#42–#48) into main#49
Merged
Conversation
Implements 11 security hardening plans for the dashboard REST API: - sec-api-01: block self-grant and wildcard permission escalation - sec-api-02: restrict audit log userId filter to self for non-owners - sec-api-03: validate audit log date range query parameters - sec-api-04: add per-user rate limits to music/commands/giveaways creation - sec-api-05: magic-byte validation for welcome background image uploads - sec-api-06: rate limit + 250ms budget on cron preview endpoint - sec-api-07: reject unsafe regex (ReDoS) in custom command creation - sec-api-08: drop permission key from 403 error responses - sec-api-09: exact-match allowlist for audit action filter - sec-api-10: add X-Content-Type-Options nosniff to image preview - sec-api-11: distinguish Prisma P2025 from 500s in actions rule update Adds safe-regex dependency. Includes 49 new tests across 9 files covering each hardening. Typecheck clean; no new regressions.
Implements 9 security hardening plans covering OAuth, sessions, CSRF, and CSP: - sec-auth-01: hard-fail in production when DASHBOARD_SESSION_SECRET is missing or too short - sec-auth-02: use trusted DASHBOARD_PUBLIC_URL for OAuth callback instead of attacker-controllable x-forwarded-host - sec-auth-03: clear oauth_state cookie immediately after validation to prevent replay on failure - sec-auth-04: regenerate sessions on login (drop existing rows for the user) to prevent fixation - sec-auth-05: re-fetch fresh guild membership every 5 minutes in requireGuildAdmin to close stale-cache access window - sec-auth-06: add CSRF double-submit token enforcement on mutating /api routes, issued via GET /auth/csrf - sec-auth-07: replace style-src 'unsafe-inline' with per-request CSP nonces (helmet enableCSPNonces) - sec-auth-08: tighten oauth_state cookie to SameSite=Strict - sec-auth-09: reduce session TTL from 7 days to 24 hours Adds DASHBOARD_PUBLIC_URL config field and new csrf.ts shared module. Typecheck clean. Dashboard test suite: 14 fail / 298 pass -- net improvement over baseline (15 failures); no new regressions. Note: SPA client will need a follow-up change to fetch /auth/csrf and include X-CSRF-Token on mutating requests.
Implements 9 security hardening plans covering the bot process:
- sec-bot-01: regression tests pinning $queryRaw parameterized safety
- sec-bot-02: webhook headers -> strict allowlist (was denylist)
- sec-bot-03: sanitize usernames/guild name before welcome canvas render
(strip bidi overrides, zero-width chars, control chars)
- sec-bot-04: restrict action rule names to [a-zA-Z0-9 _-]{1,50}
- sec-bot-05: templateEngine - fix escape lookbehind so escaped user
content can no longer smuggle core variables into the second pass,
plus adversarial regression tests
- sec-bot-06: validate emoji against Unicode/custom-emoji form before
message.react()
- sec-bot-07: classify auto-role assignment failures by Discord API
error code (50013/10011 warn, others error)
- sec-bot-08: level-up announcements restrict mentions to the leveling
user only via allowedMentions config
- sec-bot-09: add checkBotPermissions for ManageMessages to /warn
Adds sanitizeDisplayName helper in packages/systems/welcome.
Bot test suite: 349 pass / 16 pre-existing baseline failures (no new
regressions). Systems test suite: 234/234 green.
Implements 8 security hardening plans covering encryption, cache correctness, logging, and schema integrity: - sec-data-01: encrypt DashboardSession.accessToken with AES-256-GCM (session-scoped key via sha256(dashboardSessionSecret)); adds isEncrypted/encryptAccessToken/decryptAccessToken helpers and a backfill script for legacy plaintext rows - sec-data-02: atomic swap of actions rule cache - build the new per-event Map locally then swap it in to eliminate the empty-cache window during reloadGuild - sec-data-03: constant-time session id comparison via timingSafeEqual with equalized not-found-path work (dummy decrypt) - sec-data-04: DashboardRoleAssignment FK onDelete -> Restrict plus new deleteDashboardRoleWithAudit helper that enumerates, audit-logs and unassigns inside a single transaction before deleting the role - sec-data-05: redactSensitive() in @fluxcore/utils logger strips Bearer/Basic auth, Discord webhooks, bot tokens, and sensitive query params from all log output - sec-data-06: migrate DashboardAuditLog.details String -> Json (jsonb with ALTER TABLE USING migration); routes no longer stringify - sec-data-07: safeJsonParse warn-on-failure with rule context so corrupted rows surface instead of silently degrading - sec-data-08: action rate limiter keyed per (guildId, eventType) so noisy events can't starve quieter ones Prisma migrations: 3 new (encryption annotation, FK restrict, jsonb). Typecheck clean; dashboard 287 pass / 15 baseline failures (no new regressions); systems 228/228 unit tests green (integration tests for sec-data-04/06 require real test DB).
Implements 12 infrastructure hardening plans covering secrets,
containers, CI scanning, and dev environment defaults:
- sec-infra-01: remove hardcoded Lavalink 'youshallnotpass' default;
fail-fast in production if LAVALINK_PASSWORD unset
- sec-infra-02: migrate production secrets from env_file to Docker
secrets with *_FILE pattern; adds secret-files.ts resolver
- sec-infra-03: backup service uses .pgpass from Docker secret
instead of PGPASSWORD env var; backup.sh is fail-closed
- sec-infra-04: Dockerfile dev/test stages now run as node user
- sec-infra-05: parameterize dev postgres password with ${...:-fluxcore}
- sec-infra-06: BOT_SYNC_SECRET production fail-fast (unset or <32)
- sec-infra-07: Vite dev server defaults to 127.0.0.1; opt-in 0.0.0.0
via VITE_HOST env var
- sec-infra-08: add .github/workflows/security.yml (pnpm audit +
gitleaks + Trivy for bot/dashboard/filesystem) and .gitleaks.toml
- sec-infra-09: blank LAVALINK_PASSWORD in .env.example + CI guard
against reintroducing the hardcoded default
- sec-infra-10: parameterize pgAdmin credentials with fail-fast guard
- sec-infra-11: add docker-compose.no-db-port.yml override + CI guard
against public postgres exposure
- sec-infra-12: broaden .dockerignore and .gitignore to .env.* glob;
CI guard blocking tracked env files
New files: security.yml workflow, .gitleaks.toml, secret-files.ts,
docker-compose.no-db-port.yml, vite-config.test.ts, 3 config tests,
secrets/.gitkeep.
Typecheck clean. Dashboard 281 pass / 15 baseline failures (no new
regressions). Bot 325 pass / 16 baseline failures (no new regressions).
Rewrite shared Icon.tsx as a Material-Symbol-name to Lucide component map (same public API, strokeWidth 1.5, size default 20, filled fill). Remove the Material Symbols font link from index.html (eliminates ligature-word FOUT) and add font preconnects; delete the now-dead .material-symbols-outlined CSS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anding) Heading hierarchy: PageHeader title is now h1, sidebar brand demoted to a non-heading. Remove dead controls: topbar notifications button, sidebar docs/support and footer placeholder links (no real URLs); wire the topbar settings gear to the settings route. Demote the hero's tertiary CTA to a text link. Give the mobile sidebar close button a 44x44 target. Hero min-height vh -> dvh. Add an accessible refetch spinner to the overview day toggle. Add a persistent landing header (logo + login). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop header.notifications and sidebar.docs/support (no longer referenced after the dead-control cleanup) from common.json across all 48 languages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lazy-load the 19 guild feature pages via React.lazy, each wrapped in its own Suspense/PageSkeleton inside the layout Outlet so the sidebar stays mounted. Add Vite manualChunks for recharts/react-dom/tanstack/radix/i18n/lucide. Initial entry chunk drops from 2,225 kB to 181 kB (gzip 552 -> 43 kB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
discord-select/discord-multi-select: raise option contrast (text/80 -> full text) and fix RTL chevron (ml-2 -> ms-2). Add Volume2 to the Icon map (volume_up) for voice-channel indicators. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nodes get role/aria-label, larger handle hit targets, and Yes/No branch titles on the condition node; canvas region is labeled. ActionFields/NodeDetailPanel: raw <input> -> shadcn Input, label/id association, aria-required + non-color-only required markers, emoji channel labels -> Icon, reduced-motion guard on the detail panel. Remove dead files: RuleForm, ActionRow, VariableHelper, AddActionNode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icon-only buttons get aria-labels; destructive actions gain ConfirmDialogs; bare empty states use the shared EmptyState; section headings fixed (no h1->h3 skips); form labels associated (htmlFor/id); inline validation near fields; color-not-only cues added. Page-specific: security raw <select> -> shadcn Select + load skeleton; commands -> DiscordMultiSelect + Switch toggle + unsaved-changes guard + fixed mis-namespaced common: i18n keys; settings non-functional notification/danger controls removed (unsupported by API); permissions matrix labeled + audit log -> Table; scheduled browser-timezone default + cron validation; logs category icons + filter aria-labels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…obile inputs) Address three findings from the Obsidian Engine design-system audit: - Discord blue (#5865F2) is 4.19:1 as text on #0e0e10 (fails WCAG AA). Document --color-discord as fill-only and add --color-discord-text (#8b94ff, 7.14:1 AAA) for on-dark text/icons. The verbatim Discord button palette in the reaction-roles preview is intentional brand fidelity (style name shown alongside, so not color-only) — annotated. - Add a Title-LG (2rem) tier between Headline-SM and Display-LG so hierarchy holds on dense pages. - Render form inputs at 16px below the md breakpoint (14px from md up) to prevent iOS Safari focus auto-zoom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postgres 18 initializes its data directory at /var/lib/postgresql (not the legacy /var/lib/postgresql/data), so mount the named volume there to persist data across `pnpm dev` restarts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…irection Regenerate pnpm-lock.yaml to record already-declared dependencies: @playwright/test ^1.49.0 and @radix-ui/react-direction ^1.1.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract remaining hardcoded UI strings (automation/rules, welcome, overview, permissions, tempvoice, tickets, music, logs) into i18n, and fix pre-existing i18n gaps found during the sweep: - missing/broken keys that rendered raw or showed English in every locale (confirmDelete dialogs, starboard/suggestions/moderation/ warnings, common time.weeks/limit/options) - an incorrect i18next plural (editor.issues.one/.other -> _one/_other) Repair 53 broken locale JSON files (41 empty, 12 with unescaped quotes) and bring all 48 languages to full key parity (1477 keys, zero [NEEDS TRANSLATION] markers). Add real translations for the major languages (es, fr, de, pt, ru, ja, zh-CN, ar, ur); remaining locales use English placeholders pending native review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Taller, reliably scrollable list so all 48 languages are reachable, with an on-brand scrollbar using design tokens - Render the language list LTR with <bdi>-isolated native names so RTL locales no longer mirror or reorder the parentheses - Add translate="no" so browser auto-translation stops rewriting the native names (e.g. Japanese, Chinese) into English - Fix the hover state: hover:bg-surface-container-high referenced a non-existent token; use surface-hover with a transition and keyboard-focus parity Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ings From an adversarially-verified RTL/icon/focus audit of the dashboard: - Icon.tsx: stop applying fill="currentColor" to active icons. Lucide glyphs are stroke-based outlines; filling them collapsed detailed icons (Hand/Shield/Zap) into solid blobs — the "Welcome icon corrupted when active" and "icons all filled" reports. Active state now uses a heavier stroke (2.25 vs 1.5), per design.md's "outlined, not filled" rule. - Sidebar.tsx: nav had pe-2 only, so Link items sat flush against the ScrollArea's overflow-hidden edge and the :focus-visible ring was clipped on the inline-start side. px-2 gives the ring clearance. - RuleList.tsx: flow-strip arrowheads used physical border-r, so they pointed backwards against the auto-reversed chip order in RTL. border-e. - NodeDetailPanel.tsx: slide-in-from-end-4 is not a real utility and never animated; replaced with slide-in-from-right-4 + rtl:slide-in-from-left-4 so the panel enters from its end-0 dock edge in both directions. - dialog.tsx: centered modal carries no data-side, so its physical slide-in-from-left never flipped; added rtl: right counterparts. - HeroSection.tsx: decorative glow used physical right-1/4 with no translate, so it didn't mirror; end-1/4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dashboard used animate-in / slide-in-from-* / fade-in / zoom-in classes (Radix dialogs, dropdowns, selects, tooltips, popovers, and the automation detail panel) but no plugin provided them — Tailwind 4 core ships none of these, so every enter/exit animation was a silent no-op app-wide. Add tw-animate-css (the Tailwind-4-native successor to tailwindcss-animate) and import it in styles.css. Verified in-browser: animate-in now resolves to the "enter" keyframes, and physical slide utilities stay physical (no RTL auto-flip), so the explicit rtl: slide variants added in the prior commit resolve correctly in both directions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail panel overlays the React Flow canvas, which does not mirror (nodes stay in LTR pixel space). Using logical end-0 correctly mirrored the panel to the LEFT edge in RTL — but that dropped it on top of the trigger/start node and read as broken. Pin it to the physical right in both directions (border-l, slide-in-from-right, no rtl variant) so it stays consistent with the unmirrored canvas; the panel's content stays RTL via inherited dir. Also switch the React Flow control panel's inset from logical me-2 to physical mr-2 to match its bottom-right position. Verified in-browser: right-0 keeps the panel on the right edge in both LTR and RTL, where end-0 flipped it to the left in RTL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the "empty automation canvas in RTL" report: React Flow does not support RTL — under dir="rtl" its transform/coordinate math breaks and nodes render off-screen, leaving a blank canvas. Force the canvas container (relative flex-1, holding ReactFlow) to dir="ltr" so the graph renders normally, and set the NodeDetailPanel root back to dir="rtl" so its content stays Arabic/RTL. The panel is already pinned to the physical right (prior commit), which is correct for an overlay on the unmirrored canvas. Verified live in the authenticated dashboard (Arabic/RTL): the trigger + action nodes now render, a Member Join trigger sets correctly, and the detail panel docks right with RTL content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Radix injects a `display:table; min-width:100%` viewport wrapper for horizontal-scroll measurement. We only scroll vertically, and that table wrapper sized 16px wider than its viewport and overflowed, pushing its inline-start padding outside the clip box — the node detail panel showed 0 padding on one side and 16px on the other (most visible in RTL). Force the wrapper to display:block so it fills the viewport width exactly; padding is now symmetric. No component uses horizontal ScrollArea. Verified live: panel content insets are now 16px on both sides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Autocomplete, inline highlighting, searchable variable browser, and a
Discord-style live preview (hybrid data) for all {variable} template fields.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
requireGuildAdmin authorized off the cached OAuth session snapshot, so admin access revoked on Discord kept working (up to 30 min) — a privilege-escalation hole. Authorization now derives from a live bot-token check (guildAuthz.isUserGuildAdmin: owner / Administrator / Manage Server computed from current guild roles), surfaced through resolveUserPermissions. Revocation is honored within the ~60s cache. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a refresh control on the servers page backed by POST /api/guilds/refresh, which force-refreshes the user's guilds from Discord and invalidates stale server-side guild/permission caches. The manageable-guild filter now also includes guilds the user owns (OAuth `owner` flag), not just Manage Server / Administrator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the plain Textarea + static hint in the level-up announcement field with VariableEditor, VariableBrowser, and DiscordMessagePreview, giving users inline token insertion and a live Discord-style preview. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Threads workflow eventType down through NodeDetailPanel → ActionFields and swaps variable-bearing text/textarea inputs for VariableEditor with per-event autocomplete. StepPanel action fields get an empty variable list (not event-scoped in the current model). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation Thread eventType into StepPanelProps and compute buildAutomationVariables in StepPanel so step action fields receive the real token set instead of []. Fix VariableEditor id prop (text + textarea branches) to use sanitised fieldId so <Label htmlFor> correctly associates with the input. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds DiscordMessagePreview below ActionFields in ActionPanel for sendMessage, sendDM (content preview) and sendEmbed (embed preview), using already-computed variables and usePreviewContext for live guild/user resolution. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…highlight The highlight backdrop and the text field each carried the full field style (border + bg-surface-lowest + rounded), stacking two bordered boxes into a doubled/corrupted border. The field also carried both text-text and text-transparent, so twMerge could not dedupe the custom color and the field's own text painted over the colored backdrop, hiding variable highlighting. Move border/background/rounding/focus-ring to the container (focus-within), and make both inner layers borderless + transparent-bg with identical padding: the field text is transparent, the backdrop shows the accent/danger-colored tokens. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add variables.* and variables.entries.* to common.json so VariableBrowser and VariableEditor render human-readable descriptions for all registry tokens (welcome/leveling/tempvoice top-level tokens + automation/command entries). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 1 added @testing-library/react, @testing-library/user-event, and jsdom to apps/dashboard devDependencies, but the docker workflow (repo root not bind-mounted) meant the root lockfile was never regenerated, so `pnpm install --frozen-lockfile` (CI/clean clone) would fail. Regenerated from the current manifests. Diff is additive (peer-dep hash re-resolution only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… forwarding
- vitest.config.ts: widen coverage include to *.{ts,tsx} so .tsx components are not silently excluded
- commands.tsx: gate DiscordMessagePreview content/embed by formResponseType to prevent both rendering at once
- VariableEditor: add aria-required prop and forward it to textarea and input elements
- ActionFields: pass aria-required={field.required} to both VariableEditor branches (text + textarea)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # packages/config/tests/index.test.ts
# Conflicts: # apps/dashboard/src/server/shared/middleware.ts # apps/dashboard/tests/server/shared/middleware.test.ts
…#43 sec-auth-06) #43 enforces CSRF double-submit on mutating /api routes but no PR updated the SPA client, which would 403 every dashboard mutation. apiFetch now fetches/reads the csrf_token cookie and sends it as x-csrf-token on non-safe methods; the one raw-fetch mutation (welcome image preview) does the same via getCsrfToken(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update tests to match the merged PRs' intentional behavior changes (none were caused by conflict resolution). Fixes across 3 packages: - config/logger: set LAVALINK_PASSWORD (now unconditionally required, #46) - session: 24h TTL + guildsRefreshedAt mock field (#43); auth: pass callbackUrl to getAuthorizationUrl/exchangeCode + mock buildCallbackUrl (#43) - permissions: add isGuildAdmin to resolveUserPermissions mocks (#43 live-authz); actions: extend persistence mock (getLastFiredByGuild etc); anti-raid/starboard: additionalProperties strips -> assert 200 + field not forwarded - pre-existing (identical to main, unrelated to merge): starboard vi.mock hoisting + Collection.find mock; Discord.js Collection .map mocks (messageDelete, transcript, voiceStateUpdate member.user.bot); lockdown test isolation; kick/timeout moderation-persistence mocks Result: typecheck 14/14, tests 1053/1053 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates all 7 open PRs (#42–#48) into
mainin one verified integration branch — zero dropped changes, no removed features. All branches forked from the samemaintip (209e839f), so they were individually mergeable but conflicted pairwise once one landed. This branch resolves everything once and lands green.Merging this PR delivers the intent of #42, #43, #44, #45, #46, #47, #48 — please close those as merged.
What's included
{variable}fields (stacked on #47)Merge & conflict resolution
Merged in dependency order (security PRs → #47 → #48). The only 3 real conflicts, all resolved preserving both sides' intent:
shared/middleware.ts+ its test (fix(dashboard): UI/UX review remediation — Lucide icons, a11y, code-splitting #47 ↔ fix(security): harden auth, session, csrf, csp (sec-auth-01..09) #43): took fix(dashboard): UI/UX review remediation — Lucide icons, a11y, code-splitting #47's live per-request authorization (resolveUserPermissions), which supersedes fix(security): harden auth, session, csrf, csp (sec-auth-01..09) #43's OAuth-snapshot refresh and closes the stale-authz hole. Dropped the redundantMANAGE_GUILDsnapshot path.config/tests/index.test.ts(fix(security): harden auth, session, csrf, csp (sec-auth-01..09) #43 ↔ fix(security): harden infrastructure and CI (sec-infra-01..12) #46, add/add): unioned both — session-secret/public-URL (fix(security): harden auth, session, csrf, csp (sec-auth-01..09) #43) + lavalink-password (fix(security): harden infrastructure and CI (sec-infra-01..12) #46) blocks.Four files auto-merged but were semantically verified to compose both PRs' logic:
session.ts(encryption + 24h TTL),permissions/routes.ts,actions/persistence.ts,config/src/index.ts.Companion fixes (required for a working, green result)
client/shared/lib/client.ts): fix(security): harden auth, session, csrf, csp (sec-auth-01..09) #43 makes mutating/apiroutes requireX-CSRF-Token, but no PR updated the client —apiFetchnow attaches the token so dashboard save/edit/delete keep working.safe-regextopnpm-lock.yaml(the fix(dashboard): UI/UX review remediation — Lucide icons, a11y, code-splitting #47/feat(dashboard): variable autocomplete, highlighting & live preview for {variable} fields #48 lockfile never saw fix(security): harden dashboard api surface (sec-api-01..11) #42's manifest);--frozen-lockfilepasses.isGuildAdmin, CSRF headers, etc.) + fixed pre-existing mock bugs. None were caused by the conflict resolutions.Verification
pnpm typecheck— 14/14 taskspnpm test— 1053/1053 tests green (systems 270, dashboard 416, bot 367)pnpm install --frozen-lockfilepassespnpm db:migrate+pnpm db:generate; on deploy run fix(security): harden data layer and persistence (sec-data-01..08) #45's encryption backfill (scripts/migrate-encrypt-session-tokens.ts) or token encryption is inert.Follow-up (tracked, not blocking this merge)
#46 adds
.github/workflows/security.yml, currently red on pre-existing issues — remediate in a fast follow-up:lavalink/application.yml:17youshallnotpassliterals indocker-compose.yml(L32/125) withLAVALINK_SERVER_PASSWORD; allowlist commented.env.exampleplaceholders in.gitleaks.toml; setGITLEAKS_LICENSEpnpm audithigh sev: bump/pinesbuild/undici/semverviapnpm.overridestrivy: bumpnode:22-alpinedigest + scoped.trivyignore🤖 Generated with Claude Code