WP Manifestindependent plugin directory
manifest / admin / minn-admin

Minn Admin WP Registry grade C+. Medium-severity findings · 13 findingsWP RegistryMedium-severity findings · 13 findingsOpen the reportC+ #1 in Admin & Dashboard

A reimagined WordPress admin experience. Fast, focused and beautiful, served at /minn-admin/

★ 143stars
9.8krelease downloads
≈480active sites
v0.46.0latest · 4d ago

Install

Download minn-admin.zip From the v0.46.0 release on GitHub, Oct 18, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/austinginder/minn-admin/releases/download/v0.46.0/minn-admin.zip
Updates itself. It ships its own updater (manifest.json updater), so new versions appear under Dashboard → Updates.

A reimagined WordPress admin experience. Fast, focused and beautiful.

Minn Admin serves a modern, minimal dashboard at /minn-admin/ on your WordPress site. It's a single-page app built on the WordPress REST API with no React, no build step, and one vanilla-JS file. It lives alongside the classic wp-admin, which stays fully available.

Website: minnadmin.com · User guide · FAQ · Security · Roadmap

Minn Admin — Overview

Install

  1. Download or clone into wp-content/plugins/minn-admin.
  2. Activate through the Plugins screen.
  3. Visit /minn-admin/. It is also linked from the admin bar and the wp-admin menu.

Pretty permalinks are recommended for clean routes such as /minn-admin/content. Without them, Minn falls back to /?minn_admin=1 with hash routing. Updates arrive through the normal WordPress updates screen from GitHub Releases.

What you can do

  • Run the site: Manage content, media, comments, users, settings, and WooCommerce from one focused interface.
  • Write with confidence: Keep native Gutenberg content while gaining autosave, revision diffs, post locking, recovery, and distraction-free writing tools.
  • Work with your plugins: Bring forms, backups, email logs, redirects, snippets, analytics, licenses, and other daily workflows into consistent Minn surfaces.
  • Care for the site: Handle updates, diagnostics, logs, visibility warnings, backups, security posture, and cache controls without hunting through plugin menus.
  • Stay in control: Classic wp-admin remains available, builder-owned content stays protected, and integrations never inject third-party interfaces into Minn.
  • Use it your way: Minn works on phones, supports multisite and twenty-four languages, and includes dark, light, System, and custom per-user color schemes.

For a task-based walkthrough, read Using Minn Admin. For exact plugin coverage, see the plugin compatibility map.

Full feature reference

Expand only the part of Minn you want to explore.

Daily site work
  • Overview — stat cards, a real Traffic chart with hover details when an analytics plugin is installed (Koko Analytics, WP Statistics, Burst, Independent Analytics, AnalyticsWP, Plausible Analytics, Matomo, Google Analytics through Site Kit, or Jetpack Stats), click a bar for that day's top pages and referrers (Koko, WP Statistics, Burst, Independent Analytics, Plausible Analytics, Matomo and Jetpack Stats today; others join via minn_admin_traffic_day) and step through days with the arrow keys, plus a recent-activity feed. Open stats (also a palette command) opens a dedicated page for longer history: ranges up to twelve months, a custom window inside the last year, and range-wide breakdowns (top pages and referrers everywhere; countries, devices and search terms where the provider tracks them)
  • Content — posts, pages and custom post types sorted by publish date (scheduled posts lead with their go-out dates), with search, category/tag filters, status pills (live posts carrying unsaved edits wear a Modified chip, with a matching filter; a post someone else has open says who is editing), bulk actions (set status or trash, with shift-click range select), and row actions: right-click or hover for quick publish/draft/trash, view, and a block-editor escape
  • Media — grid/list library, uploads, drag-and-drop, a preview overlay with arrow-key navigation and in-place title, alt text, caption & description editing, bulk select and delete (shift-range, on the grid and the list), a right-click menu, and a built-in image editor (rotate and crop, saved as a new copy). Folders arrive from your folder plugin (FileBird, Real Media Library, Folders by Premio or HappyFiles Pro) with a Move to folder bulk action through each plugin's own machinery; an Unattached filter and a month picker cover the daily cleanup questions; every file's detail names the post it is attached to, one click from that post's editor; Replace file works in place through Enable Media Replace; the SVG filter tab appears with Safe SVG or SVG Support active, and ↻ Thumbnails works through Regenerate Thumbnails or Force Regenerate Thumbnails
  • Comments — full moderation (pending / approved / spam / trash) with bulk moderation (each tab offers its own verbs), inline replies, in-place comment editing, Block commenter (core's disallowed list, with Undo) and a right-click menu for the same verbs
  • Ecommerce — full WooCommerce day-to-day in Minn: Orders open as full pages (linkable, refreshable, with a Quick view modal from the list) with search, status, notes, a Payment card that records hand-received payments through WooCommerce's own machinery, itemized refunds with quantity steppers and restocking, resend/custom email, pay URL, New order, Analytics with long-range revenue and top products, and a Store strip on the Overview naming the orders that need work today. Products open as full pages as well, in the order page's shape: a main column for what the product is (pricing with its sale schedule and tax class, inventory down to GTIN and backorders, shipping, the image and gallery as tiles you drag to reorder, attributes, and variations a variable product can generate from its own attributes, each one a row that opens its own editor) and a sidebar for what it is filed under (status, type, categories, tags and brands as checkable lists you can add to, upsells and cross-sells), with a save bar that offers Discard beside Save and the long description in Minn's own editor. The products list carries stock filters, bulk actions and Add product. The orders list filters the way a storefront back office does: a status view, search and Add filter in one row, active filters as removable chips, multi-status, date window, customer and product all narrowing on the server, and the whole set living in the URL so a filtered list can be pasted to someone else. Orders can apply and remove coupons, and an order that belongs to a subscription carries a badge that opens its summary. Coupons, Customers, and Subscriptions when WooCommerce Subscriptions is active: a subscription opens on its own page in the order page's shape, with editable items and schedule, coupons, a notes timeline, a quick view of related orders, and the same filter bar with its own status vocabulary. Products and coupons wear that same orders filter bar (status as the dropdown; stock, category, tag, type, featured and on sale on products; a date window on coupons). Gift cards when YITH, official WooCommerce Gift Cards, or PW Gift Cards is active: one Commerce item with a provider switcher, the outstanding balance the store still owes, enable/disable, change the balance, resend, and Add gift card. Orders, products and customers carry right-click menus for the common moves (status changes, stock and publish toggles, email, jump to a customer's orders). Invoice / packing-slip downloads when PDF Invoices & Packing Slips is active. Product, coupon and subscription CPTs are fenced out of Content.
  • Users — directory with search, a role filter, create/edit users, roles, passwords, bulk role change, per-user login sessions with one-click sign-out, Switch to this user when the User Switching plugin is active (a switched session shows a Switch back bar in Minn), and Copy one-time login link when One Time Login is active. Editing a user is a full page at /minn-admin/users/{id}: identity, public profile, language, sessions, plus the user's Minn appearance set by an admin (color scheme including custom palettes, default admin, toolbar) so a client's Minn looks right before their first sign-in, and a Hidden for them card restoring anything they hid. Administrators also get Role defaults: per role, choose what happens after sign-in and which toolbar that role gets on the public site, as an overlay that never erases anyone's saved preference
  • Multisite — fuzzy-search the sites you can use from a compact sidebar switcher or the command palette; subsite administrators can add existing network accounts, change roles, and remove site memberships. Network administrators get first-class Sites, Network users, and Network settings surfaces, plus network-wide plugin activation, theme availability, and post-core-update database migrations. With WP Multi Network active, a Networks surface lists every network, creates networks with root sites, moves sites between them, and safely deletes non-primary networks. Main/current-site, self-lockout, and last-admin guards protect destructive actions; account creation/deletion, network domain and path changes, the long settings tail, and very large network upgrades link to WordPress Network Admin
  • In your language — the whole interface follows the language you pick, per user, in twenty-four languages covering about half of WordPress installs. Right-to-left languages (Arabic, Hebrew, Persian and friends) get a genuinely mirrored layout, counts follow each language's own plural rules, and translations arrive as WordPress language packs through the same Updates screen as everything else. Extensions → Translations lists every language whose files are on the site, not only the ones waiting to update, and can remove a language nothing uses so WordPress stops keeping it current forever
  • Your profile — a full page at /minn-admin/profile: account, public profile (first/last name, website, bio, Gravatar), per-user language with automatic pack installs, the front-end toolbar preference, appearance, hidden integrations, and login sessions
  • AI Access — generate revocable application passwords for AI agents plus a site-tailored agent guide (markdown REST reference) to hand to a coding agent; configuration work stays out of Minn by design
Extensions, structure, and settings
  • Extensions — install plugins and themes from WordPress.org or zip upload (Add plugin opens a catalog by category with install tips, not only free-text search), activate, deactivate, delete, per-item and bulk updates with Queued… / Updating… feedback that survives worker recycles, a Themes tab with screenshots, cards wearing real wp.org icons (linked to the directory) with linked author lines, per-item auto-update toggles on every plugin and theme card (core's own auto_update_* lists, so Minn and wp-admin always agree), Live preview on inactive themes (Customizer for classic themes, Site Editor preview for block themes), right-click menus on plugin and theme cards (Activate, Update, Delete, Open on WordPress.org or GitHub, Copy file), a Translations tab that lists every installed language and can remove one nothing uses, and a Licenses tab (below)
  • Structure — post types, taxonomies and terms on one page. See every registered post type and taxonomy and manage definitions through whoever owns them (ACF, Custom Post Type UI, or Minn's own store when neither is active; code-registered ones shown read-only), and a full terms manager: rename, re-parent, merge (posts move to the survivor through core's own reassignment) and delete across every taxonomy, with an indented tree for hierarchical ones. Post type and taxonomy rows answer a right-click (open the definition, jump to that type's content, start a new item, manage terms, or remove an editable definition)
  • Settings — reorganized by intent: Site (identity, logo, site language, locale, admin, with timezone picker), Visibility (search engines, maintenance mode, membership), Homepage, Content (new-content defaults + permalinks with automatic rewrite flushing), Comments (discussion + spam), Design (the Customizer's Additional CSS, validated before saving) and Connectors (WP 7.0's registry of AI providers and external services: connection state, where each key comes from (saved, wp-config constant or environment variable), install the companion plugin in place, keys saved through core's own masked route), under a sticky section nav. The Spam page shows who filters comment spam (Akismet, Antispam Bee, CleanTalk, WP Armour) with safe toggles and blocked counts. Site-visibility warnings: an Overview banner and a persistent topbar chip appear whenever a maintenance plugin, password gate or "discourage search engines" is hiding the site, with inline fix controls where Minn can safely flip the setting, including turn-off switches with Undo for detected coming-soon and maintenance plugins that write through each plugin's own storage (third parties register via minn_admin_visibility_providers and minn_admin_visibility_toggles)
Writing and page building
  • Editor — a calm, block-aware writing surface that stores native Gutenberg markup (zero lock-in: open any post in the block editor, any time). Markdown typing conventions (**bold**, `code`, ## headings, lists, quotes, fences, dividers…), with wraps that stay on the undo stack (including inline code). Every core layout container is a writing surface: Group, Columns, Cover and Media & Text open for typing, nested to any depth, with markdown, the slash menu, the block picker and rich paste all working inside and each column its own side-by-side surface; container framing and styling re-save byte-for-byte, so the block editor always reopens the layout cleanly. Complex blocks (a spacer, a buttons row, a plugin's block) stay preserved byte-for-byte as configurable islands with real front-end styles, right where they live, with duplicate and move arrows in the ⚙ popover (left/right hops between columns) and in-place text editing. Pasting raw block markup (from an AI tool or a pattern file) converts to real blocks instantly. WordPress's own dynamic blocks insert from the slash menu too: the Query Loop (post type, ordering, count and more editable from the ⚙ gear), the widget blocks (Latest Posts, Archives, Calendar, Search…), and Tabs and Accordion with starter sections, all saved as exactly the markup the block editor produces. Link popover on ⌘K, text alignment, table and image controls in island-style cutouts, at the top level and inside containers. Slash commands stay curated and type-to-filter; Browse all or ⌘/ opens the block picker, grouped by source (basics, plugin blocks, design libraries, patterns). Plugins can register free-form slash items through minn_admin_editor_commands (boilerplate HTML, island templates, or an async REST route). Dynamic third-party blocks that render standalone auto-appear in search (no adapter); Stackable, Kadence, and GenerateBlocks free design/pattern libraries insert as valid Gutenberg markup with images sideloaded; block patterns from core, the theme, and plugins join the same search, and so do your own saved patterns: synced ones insert as live wp:block references that follow later edits, unsynced ones as detached copies, with a Patterns entry in Content to manage them. Island content is editable: text runs and an Images section rewrite only what you change; block settings scale (used fields first, the rest behind More settings); every island links out to the block editor for layout controls. Gallery-shaped blocks (core galleries, sliders and carousels, fixed layouts, Jetpack's tiled gallery) open an images editor: reorder, replace, add, remove, duplicate and caption photos, each moved as an exact unit, with dropped files uploading into the block and gallery columns, crop and random order editable from the ⚙ popover. Multi-block containers open an Edit content window: one card per block, text first, other settings tucked behind a toggle. Previews pick up lazy CSS, layout-support CSS (gallery columns, group gaps) and auto-warm browser-compiled styles when needed. Syntax-highlighted code blocks; writing stats on the sticky pill (words, reading time, session delta, optional word goal); scheduling and one-click publish. Paste cleanup turns Word / Google Docs / web HTML into the safe subset; paste or drag an image uploads at the caret with an inline caption. The publish sidebar edits the slug, visibility (a themed Public / Password / Private combobox), per-post discussion, sticky and post format (when the theme supports formats). Deleting an embed offers an Undo toast; table add/delete row and column undo with ⌘Z. Revision diffs open a side-by-side, word-marked diff against the current content. An outline panel lists headings as a live table of contents; focus mode (⌘⇧D) fades all but the current paragraph; outline mode (⌘⇧O) leaves just the writing and the outline. The internal link picker searches your own posts from the link popover, and a themed date-time picker handles scheduling. Find & replace (⌘⇧F) matches across inline formatting, never touches protected islands, and every replace is a native undo step. Built for real writing sessions: IME-safe composition (CJK and dead keys), mobile Safari keyboard and hit-target polish, and a first-cut accessible toolbar, slash menu, and block popovers. ⌘⏎ publishes; the help dialog documents every shortcut. Where this is heading: the editor roadmap
  • Never lose work — post locking on WordPress's own _edit_lock (Minn, the classic editor and Gutenberg all honor each other, with takeover), plus a localStorage crash net that snapshots every edit within ~1.2s (before the first autosave) and offers recovery on the next open. Status-aware autosave: drafts save in place, published posts back up to revisions (only Update goes live), with a backup-restore banner.
  • Page builders — build a page with Divi, Elementor, Brizy, Beaver Builder, Etch, Bricks, Breakdance or WPBakery and keep managing it from Minn: builder-owned pages are marked, edited through the builder's own chrome-free surface via Edit in ⟨builder⟩ (no wp-admin screen), and fenced so a stray Minn edit can't break the builder's canvas. + New can start a page in any active builder. Bricks and Elementor templates get a Templates surface: list by type, rename, duplicate, export, tag, create, and jump into the builder, all through each builder's own permissions. Third parties register via the minn_admin_page_builders filter
Site care and plugin workflows
  • System — a developer diagnostics page with a sticky section jump bar: a health strip over WordPress / PHP / database / server facts, plus loopback and REST self-checks, site visibility, Wordfence firewall & scan posture, SSL enforcement (Really Simple SSL), backups and licenses; the autoloaded-options breakdown and cron health expand into full-detail modals (every option by size, every scheduled event with its next run); the real login URL (honoring login-hiders), an installed extensions manifest, a Tools card linking wp-admin's one-shot jobs (Site Health, export/import, GDPR tools), live debug toggles that safely rewrite wp-config.php, a multi-source log viewer (debug log, PHP error log and every WooCommerce log channel, with a collapse-repeats mode), and one-click Copy report as markdown
  • Licenses — a license manager on Extensions → Licenses, beside the plugins and themes it describes: every paid product classified valid / expired / invalid / missing from the vendor's own locally stored state (read-only: no network calls, no seat burn), grouped by state with inactive components collapsed, with paste-to-activate, deactivate and re-verify wired through each vendor's own code for more than twenty vendors (Elementor Pro, ACF PRO, WP Rocket, Gravity Forms & SMTP, WPForms Pro, Breakdance Pro, Divi, Beaver Builder, Brizy, Etch, Bricks, The Events Calendar family, Kadence Blocks Pro, WPMU DEV, SearchWP, Gravity Perks, GP Premium, Perfmatters, WP All Import/Export, Slider Revolution, LayerSlider), plus generic Freemius / EDD / SureCart / StellarWP detection. Service keys and account connections ride the same card with a chip saying which they are: Akismet's key (also editable in place on the spam card), the WooCommerce.com, Site Kit and Jetpack connections as read-only rows with a Connect link when absent, and the AI connector keys WordPress core manages, one doorway from Settings → Connectors. A pasted key rides one request and is never stored or logged; failures never auto-retry; inactive components can be turned back on in place. Third parties register via minn_admin_license_providers
  • Editor field panels — ACF fields edit in full: every applying field group renders with no "Show in REST API" setting required, covering text, choices, switches, checkboxes, color, image, gallery, file, date and time, rich text, the relational pickers (Post Object, Relationship, Page Link, Taxonomy, User), repeaters as row cards and flexible content as sections, with ACF's conditional logic honored live and unrendered values preserved through every edit; Meta Box and Pods simple fields ride the same panel (their advanced types count as locked with a wp-admin link); SEO panel at full per-post depth for Yoast SEO, Rank Math, All in One SEO, SEOPress, SiteSEO, SureRank or Squirrly (first active SEO plugin wins): a live search-result preview with length counters, title, meta description and focus keyword, robots directives, canonical URL, the Facebook and X cards with images, and each plugin's extras such as cornerstone or pillar content, every write through the plugin's own storage and every field behind the plugin's own permissions; Event details for The Events Calendar (dates, all-day, venue and organizer as live search pickers, cost, website); Job listing for WP Job Manager, drawn live from its own field schema; Podcast episode for Seriously Simple Podcasting and PowerPress (media file, duration, the Apple Podcasts fields). Every write goes through the owning plugin's own save machinery
  • ACF schema and options — a Field Groups manager plus a full builder page per group: stack fields, configure inline, drag to reorder, duplicate, a repeater sub-field builder, and location rules edited as readable sentences over the site's real post types, templates and roles; import and export in ACF's own Tools JSON with imports that update an existing group in place; options pages merged under one Site Options sidebar entry with their tabs intact; and ACF blocks exposing their real fields in the block inspector (the dataForm descriptor, open to any plugin with the same shape)
  • Menus & Widgets — classic nav menus with drag-to-reorder (children travel with their parent) and right-click menus on every item; classic sidebars with drag grips to reorder widgets in an area, plus move between areas and in-place edit for block/text/HTML widgets
  • Design (block themes) — the three things the Site Editor keeps behind its own canvas, on one screen. Templates sorts the ones this site changed to the top and says which is which (from the theme, customized here, added here, plugin-registered), counts what actually uses each one, resets a customized template to the theme's file, and opens any of them in Minn's own editor with the byte-for-byte guarantee the rest of the editor makes. Template parts get their own tab with the count of templates that include them. Navigation lists a block theme's menus with where each one renders and arranges the items in place: rename, re-point, drag to reorder, indent to build a dropdown, add a page or custom link, remove with Undo. Styles shows the theme's style variations as swatch cards built from each one's own palette, applies one with a real Undo, and says plainly when a site cannot store them
  • Crocoblock — the Jet pack's daily work, each surface over the plugin's own tables and code: JetEngine meta boxes as an editor Custom fields panel, its options pages as Site options tabs, and its post types and taxonomies on Structure; JetBooking and JetAppointments in the Bookings inbox; Custom Content Types as a Content types surface with a view per type; JetReviews as a Reviews inbox with per-field ratings and bulk approve; JetThemeCore parts on Templates beside Elementor and Bricks; JetSearch suggestions and JetSmartFilters filters sharing one Search item, its indexer reindexing as a background job. One Crocoblock membership key on Licenses covers every installed Jet plugin
  • Surfaces — Minn's answer to plugin sprawl: one sidebar item per job, not per plugin, with every capable plugin layered in behind it and a provider switcher when more than one is active. Forms (Gravity Forms, WPForms, Ninja Forms, Fluent Forms, Forminator, Formidable, Everest Forms, SureForms, Elementor Pro, Contact Form 7 via Flamingo or CFDB7) shows entries as contact cards with real field labels and ←/→ stepping, with the full Gravity Forms workflow inside Minn: star, spam, trash, restore, bulk actions, notes and resent notifications across Received / Spam / Trash views, plus a Feeds view listing every add-on integration across your forms (Everest Forms carries the same three status views through its own entry helpers); Bookings (Amelia, LatePoint, Bookly, JetBooking, JetAppointments) lists upcoming appointments with pending / today / canceled filters, a contact card, a Next 14 days chart, and approve / cancel / no-show through the plugin so its notifications still fire; Email (Gravity SMTP, FluentSMTP, WP Mail SMTP, Post SMTP, WP Mail Logging, SureMails, Site Mailer) shows sent mail with the real HTML body in a fully sandboxed preview, resend, and search plus delete where the logger supports it, plus Gravity SMTP's full settings (all 21 connectors, drawn at runtime from its own schema), suppressions, send a test email, and a FluentSMTP Settings view for the day-to-day choices (on FluentSMTP 2.3+ resends ride its own machinery with a logged trail, Resend to… retargets a message, and its daily connection check surfaces on the status card and System health); Activity Log (Simple History, WP Activity Log, Aryo, Stream, All-In-One Security, Wordfence login security, plus Limit Login Attempts Reloaded and Solid Security lockouts with unlock/release actions) reads like an audit feed; Redirects (Redirection, Safe Redirect Manager, Simple 301 Redirects, 301 Redirects) lists, searches, creates and edits, with sortable columns on Redirection; Snippets (Code Snippets, WPCode, FluentSnippets, Simple Custom CSS and JS, Header Footer Code Manager) lists, toggles, creates and bulk-edits; Performance (Perfmatters, Autoptimize, Asset CleanUp, Performance Lab) shares one Tools item with a provider switcher; Backups (UpdraftPlus, WPvivid, BackWPup, All-in-One WP Migration, Duplicator, Disembark) below. Status cards now open the whole mail, redirects and snippets families, and detail modals render typed rows: status pills, code blocks, key-value tables and sandboxed HTML previews. Surface lists open a ⋯ / right-click menu of that collection's actions. Plugins that need their own first-run install get a setup card that runs their installer in place. The sidebar organizes into Workspace / Tools / Manage groups so daily inboxes stay separate from site plumbing, and admin menus a developer removed with remove_menu_page stay hidden in Minn too (cosmetic like the original; opt out with minn_admin_respect_removed_menus)
  • Backups — with UpdraftPlus or WPvivid: sets listed, status cards, a System health check answering "is my site backed up?", and Back up site now from ⌘K. With BackWPup: local folder archives and run-now. With All-in-One WP Migration: local .wpress exports with delete. With Duplicator: packages with archive sizes read from disk and delete through its own cleanup. With Disembark: a status card (last scan, database size, working files), the exact disembark connect command click-to-copy, scan sessions with cleanup, and token regeneration. Backups and exports run as background jobs on UpdraftPlus, WPvivid, BackWPup, Duplicator and All-in-One WP Migration: a progress pill in the topbar carries the live step and percent on every route, a modal offers Stop through the plugin's own abort, and the job survives a reload. Every finished row carries Download, streamed from the plugin's own backup folder through a nonce-checked admin-post door, so nothing has to be web-readable
Control and extensibility
  • Notifications that respect you — comments, plugin/theme/core updates and new users in one panel, plus an admin-notice digest: the notices other plugins print in wp-admin are extracted as structured data (never their HTML or JavaScript) into a Notices tab; Allow / No Thanks and ThemeIsle-style dismiss links run in the background (not a new wp-admin tab), and any notice can be hidden with Undo. Each update offer has its own Update → version button; the Updates tab also pins Update everything (plugins, themes and core in one click, poll-verified core). A pending WordPress update also shows as an amber topbar chip and Overview banner
  • Command palette — ⌘K / Ctrl-K everywhere, and it finds your content: type anything to see your posts, pages and CPTs (drafts and scheduled included) under the command matches, and Enter opens the Minn editor. Site-care actions built in: Clear site cache purges every layer the site runs (Kinsta, LiteSpeed, WP Super Cache, W3TC, WP Rocket, WP Fastest Cache, SiteGround, Autoptimize, WP-Optimize, Cache Enabler, Hummingbird, Elementor CSS, SpeedyCache, Redis Object Cache, Breeze, Nginx Helper, Cloudflare), each in its own isolated request
  • Extending — one-filter APIs for any plugin to register views (with status cards and optional charts, extra list views, tabs, status filters, sortable columns, detail layouts with typed rows including sandboxed HTML previews, actions with inline fields, bulk actions, schema-driven settings views including item-scoped settings, and setup gates), editor panels (including async search-picker fields), editor slash commands, traffic data (including per-day drill-down), media folder providers, cache purgers, spam providers, license providers, visibility providers, design libraries, page builders or block-inspector forms; the System page's Integrations card shows everything registered and flags descriptor problems instead of failing silently. The full coverage map lives in docs/plugin-support.md
  • Quiet by architecture — integrations are data, never third-party HTML or scripts, and attention is budgeted: one plugin holds at most three nav slots and three default slash entries (overflow stays one search away), Workspace placement requires an inbox-shaped view, off-site links always carry the ↗ mark, and every surface, editor panel, design library, slash namespace and core menu item can be hidden per user from Minn's own UI (restore from Your profile, or an admin restores for you from the Users page), with no API for a plugin to detect or resist it
  • Dark, light and System themes (follows your OS until you choose; right-click for an explicit menu) plus per-user color schemes: named light/dark presets or a fully custom scheme with per-slot color pickers, set on Your profile. Bundled fonts, zero external requests from the app, responsive down to phones

Extending

Any plugin can add a view to Minn with one filter: a declarative descriptor, no JavaScript required. Start with docs/for-plugin-authors.md; it opens with a quick start and carries a screenshot of every primitive. If your data lives in a custom table, docs/shim-tutorial.md walks the whole build with a copyable example plugin, and the WordPress Playground demo boots that example preactivated so a working instance of the API is one click away.

Documentation

Development

Edit and go. There's no build step. Lint with node --check assets/js/app.js and php -l minn-admin.php. Commit messages follow Emoji-Log.

License

MIT © Austin Ginder

Bundled Hanken Grotesk and JetBrains Mono are SIL OFL 1.1. See assets/fonts/.

Releases

Newest 25 of 48 recorded releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v0.46.0 latest
Oct 18, 2026 4d ago
minn-admin.zip +9 language packs
332
minn-admin.zip280
minn-admin-de_DE.zip31
minn-admin-cs_CZ.zip6
minn-admin-ar.zip4
minn-admin-en_AU.zip4
minn-admin-da_DK.zip3
minn-admin-de_CH.zip3
minn-admin-de_AT.zip1
minn-admin-de_CH_informal.zip0
minn-admin-de_DE_formal.zip0
Oct 14, 2026 8d ago
minn-admin.zip +9 language packs
342
minn-admin.zip290
minn-admin-de_DE.zip28
minn-admin-cs_CZ.zip8
minn-admin-ar.zip5
minn-admin-en_AU.zip4
minn-admin-da_DK.zip3
minn-admin-de_CH.zip3
minn-admin-de_AT.zip1
minn-admin-de_DE_formal.zip0
minn-admin-de_CH_informal.zip0
Oct 11, 2026 11d ago
minn-admin.zip +9 language packs
288
minn-admin.zip240
minn-admin-de_DE.zip26
minn-admin-cs_CZ.zip6
minn-admin-ar.zip5
minn-admin-de_CH.zip4
minn-admin-da_DK.zip3
minn-admin-en_AU.zip3
minn-admin-de_AT.zip1
minn-admin-de_CH_informal.zip0
minn-admin-de_DE_formal.zip0
Oct 5, 2026 17d ago
minn-admin.zip +9 language packs
445
minn-admin.zip329
minn-admin-en_GB.zip46
minn-admin-de_DE.zip31
minn-admin-cs_CZ.zip8
minn-admin-en_CA.zip6
minn-admin-en_NZ.zip6
minn-admin-en_AU.zip6
minn-admin-ar.zip6
minn-admin-da_DK.zip5
minn-admin-de_DE_formal.zip2
Sep 27, 2026 25d ago
minn-admin.zip +9 language packs cohort
481
minn-admin.zip361
minn-admin-en_GB.zip48
minn-admin-de_DE.zip34
minn-admin-cs_CZ.zip11
minn-admin-ar.zip6
minn-admin-en_NZ.zip6
minn-admin-da_DK.zip5
minn-admin-en_AU.zip5
minn-admin-en_CA.zip4
minn-admin-de_DE_formal.zip1
Sep 17, 2026 1mo ago
minn-admin.zip +9 language packs
606
minn-admin.zip474
minn-admin-en_GB.zip50
minn-admin-de_DE.zip46
minn-admin-cs_CZ.zip11
minn-admin-da_DK.zip6
minn-admin-en_CA.zip5
minn-admin-en_NZ.zip5
minn-admin-ar.zip4
minn-admin-en_AU.zip4
minn-admin-de_DE_formal.zip1
Sep 15, 2026 1mo ago
minn-admin.zip +9 language packs
369
minn-admin.zip266
minn-admin-en_GB.zip38
minn-admin-de_DE.zip33
minn-admin-cs_CZ.zip9
minn-admin-da_DK.zip5
minn-admin-en_CA.zip5
minn-admin-en_NZ.zip5
minn-admin-en_AU.zip4
minn-admin-ar.zip3
minn-admin-de_DE_formal.zip1
Sep 11, 2026 1mo ago
minn-admin.zip +9 language packs
403
minn-admin.zip293
minn-admin-en_GB.zip35
minn-admin-de_DE.zip34
minn-admin-cs_CZ.zip12
minn-admin-da_DK.zip7
minn-admin-en_CA.zip6
minn-admin-en_NZ.zip6
minn-admin-en_AU.zip5
minn-admin-ar.zip3
minn-admin-de_DE_formal.zip2
Sep 3, 2026 1mo ago
minn-admin.zip +9 language packs
637
minn-admin.zip526
minn-admin-en_GB.zip37
minn-admin-de_DE.zip30
minn-admin-cs_CZ.zip11
minn-admin-da_DK.zip8
minn-admin-en_NZ.zip7
minn-admin-ar.zip6
minn-admin-en_AU.zip6
minn-admin-en_CA.zip5
minn-admin-de_DE_formal.zip1
Sep 1, 2026 1mo ago
minn-admin.zip +9 language packs
406
minn-admin.zip294
minn-admin-de_DE.zip35
minn-admin-en_GB.zip33
minn-admin-cs_CZ.zip12
minn-admin-da_DK.zip7
minn-admin-en_AU.zip7
minn-admin-en_CA.zip6
minn-admin-en_NZ.zip6
minn-admin-ar.zip4
minn-admin-de_DE_formal.zip2
Aug 30, 2026 1mo ago
minn-admin.zip +9 language packs
401
minn-admin.zip288
minn-admin-en_GB.zip34
minn-admin-de_DE.zip31
minn-admin-cs_CZ.zip10
minn-admin-en_ZA.zip9
minn-admin-en_CA.zip7
minn-admin-da_DK.zip6
minn-admin-en_NZ.zip6
minn-admin-en_AU.zip6
minn-admin-ar.zip4
Aug 25, 2026 1mo ago
minn-admin.zip +9 language packs
581
minn-admin.zip450
minn-admin-en_GB.zip40
minn-admin-de_DE.zip38
minn-admin-cs_CZ.zip11
minn-admin-en_ZA.zip10
minn-admin-da_DK.zip9
minn-admin-en_AU.zip6
minn-admin-en_CA.zip6
minn-admin-en_NZ.zip6
minn-admin-ar.zip5
Aug 21, 2026 2mo ago
minn-admin.zip +9 language packs
495
minn-admin.zip384
minn-admin-en_GB.zip34
minn-admin-de_DE.zip33
minn-admin-cs_CZ.zip10
minn-admin-en_ZA.zip9
minn-admin-da_DK.zip7
minn-admin-en_AU.zip5
minn-admin-en_CA.zip5
minn-admin-en_NZ.zip5
minn-admin-ar.zip3
Aug 19, 2026 2mo ago
minn-admin.zip +9 language packs
541
minn-admin.zip442
minn-admin-en_GB.zip35
minn-admin-de_DE.zip25
minn-admin-cs_CZ.zip8
minn-admin-en_ZA.zip8
minn-admin-ar.zip5
minn-admin-da_DK.zip5
minn-admin-en_NZ.zip5
minn-admin-en_AU.zip4
minn-admin-en_CA.zip4
Aug 18, 2026 2mo ago
minn-admin.zip +9 language packs
374
minn-admin.zip292
minn-admin-en_GB.zip26
minn-admin-de_DE.zip17
minn-admin-en_ZA.zip8
minn-admin-en_AU.zip7
minn-admin-cs_CZ.zip6
minn-admin-da_DK.zip5
minn-admin-en_NZ.zip5
minn-admin-ar.zip4
minn-admin-en_CA.zip4
Aug 16, 2026 2mo ago
minn-admin.zip +9 language packs
413
minn-admin.zip341
minn-admin-en_GB.zip22
minn-admin-de_DE.zip17
minn-admin-cs_CZ.zip7
minn-admin-en_ZA.zip6
minn-admin-en_AU.zip5
minn-admin-ar.zip4
minn-admin-da_DK.zip4
minn-admin-en_NZ.zip4
minn-admin-en_CA.zip3
Aug 15, 2026 2mo ago
minn-admin.zip +9 language packs
418
minn-admin.zip372
minn-admin-en_GB.zip15
minn-admin-de_DE.zip11
minn-admin-ar.zip4
minn-admin-en_AU.zip4
minn-admin-cs_CZ.zip3
minn-admin-en_ZA.zip3
minn-admin-en_NZ.zip3
minn-admin-da_DK.zip2
minn-admin-en_CA.zip1
Aug 13, 2026 2mo ago
minn-admin.zip
401
Aug 12, 2026 2mo ago
minn-admin.zip
513
Aug 11, 2026 2mo ago
minn-admin.zip
608
Aug 9, 2026 2mo ago
minn-admin.zip
40
Aug 8, 2026 2mo ago
minn-admin.zip
17
Aug 6, 2026 2mo ago
minn-admin.zip
25
Aug 4, 2026 2mo ago
minn-admin.zip
69
Jul 30, 2026 2mo ago
minn-admin.zip
53
All releases on GitHub →

Active-site estimate ≈480 comes from the v0.42.0 cohort. Method.

C+ grade

Security

Minn Admin 0.46.0 · audited by WP Registry

Medium-severity findings.

1 medium 12 low
Audited release
0.46.0
Findings
13
Worst severity
medium
Content hash
ae97688ed0843f548de84ee7…

Findings

  • medium SEO panel offered to writers the SEO plugin refuses; the minn_seo write then answers 403 after the post is stored, so every save/autosave of a new post creates another draft

    unsafe_input

    /minn-admin/v1/seo/fields returns the active SEO provider's groups to any edit_posts holder without asking the provider's can_edit predicate, and for a new post (post=0) without the per-post-type metabox switch. The minn_seo REST field update_callback asks both and returns WP_Error 403 (also for refused images). WordPress runs REST field updates after wp_insert_post() and returns their error as the response (class-wp-rest-posts-controller.php 845-850), so the post exists but the client never learns its id (app.js sets ed.id only on success) and keeps the panel dirty; every manual save and every 15-second autosave creates another draft. Stock reach: Contributors on Rank Math (no rank_math_onpage_* caps by default; Rank Math shows them no metabox), Editors/Authors/Contributors on SureRank (manage_options-only), roles blocked by SEOPress/SiteSEO/AIOSEO, and any author on a post type whose SEO metabox is switched off. On existing posts the body saves but the editor reports failure and REST fields registered after minn_seo are skipped. Same class as RC 09-1 (Pods), which Minn fixed with minn_admin_panel_held(); seo.php was not reached.

    includes/adapters/seo.php:2014-2043, 2087-2105, 2160-2172, 516-518, 930-932

    Recommendation

    In /seo/fields, return no groups when the provider's can_edit refuses the user, and resolve the post type for a new post (pass {type} in fieldsRoute) so minn_admin_seo_shows_for_type() is asked there too. In the update_callback, hold every refusal with minn_admin_panel_held( $request, $error, 'minn_seo' ) instead of returning it, so the post keeps its 201/200 and reports minn_field_errors; run image checks before writing any field.

  • low Fluent Forms emails save still half-applies when Fluent's own validator refuses a later notification or the confirmation (R2-2 fix pre-checks masks only)

    unsafe_input

    b7fc895c moved refuse_masks() and Minn's own refusals ahead of every write in POST /minn-admin/v1/fluent-forms/forms/{id}/emails and documents that 'a refusal leaves nothing saved'. Fluent's own validator still runs inside the write loop: SettingsService::store() validates each notification (subject, message and the Send-to address are required; fluentform app/Services/Settings/Validator/Notifications.php:20-24) as it is written, and saveGeneral() validates the confirmation (customUrl required for a redirect; Confirmations.php:20) after every notification is stored. A save that changes notification A and leaves notification B's message empty, or switches the confirmation to a redirect with no URL, stores A and answers 400 on B or on the confirmation. The client keeps A marked unsaved, but the live notification now sends the edited subject, body or recipient. Fluent's own screen saves one notification per request, so it never half-applies. Reachable by any user Fluent grants fluentform_forms_manager for the form (Acl::hasPermission, Acl.php:157). Integrity only; nothing is disclosed.

    includes/adapters/fluent-forms-emails.php:256-257, 366-388

    Recommendation

    In the first pass, call \FluentForm\App\Services\Settings\Validator::validate( 'notifications', $n ) next to refuse_masks() at :306 and Validator::validate( 'confirmations', $c ) next to :358, and map a ValidationException through minn_admin_fluent_validation_error() as the write loop does. These are the same static calls store() and saveGeneral() make, so every refusal then precedes the first write. Otherwise, narrow the comment at :256-257 to mask refusals.

  • low wp-config switch is offered, written and reported as applying when a PHP auto_prepend_file already defines the constant (unfixed sibling of RC 02-1)

    insecure_config

    config_define_live() treats a literal wp-config define as the running one when its value equals the constant's running value and no file loaded by wp-config.php before WordPress may define it. config_loaded_files() lists only get_included_files() strictly between wp-config.php and wp-settings.php, so a file PHP runs before index.php through auto_prepend_file (php.ini or .user.ini) is never scanned. When such a prepend defines WP_DEBUG_DISPLAY, WP_DEBUG or SCRIPT_DEBUG to the same value as wp-config's literal, the System page shows the switch as live and unlocked. POST /minn-admin/v1/system/config passes its not_live re-check, rewrites the wp-config line and answers 200 ('applies on the next page load'), but PHP keeps the prepend's define: the site keeps the old value (errors stay on screen after 'Show errors' is turned off), every request warns that the constant is already defined, and the switch reads as locked on the next load. This is the RC 02-1 consequence through the one load path its fix does not read; fix-delta review 1 listed it as an open sibling, and it was neither fixed nor recorded as a non-fix. Reachable by an Administrator with manage_options, view_site_health_checks and edit_files on a site that allows file edits (the install owner on multisite). (Found independently by two audit slices.)

    includes/class-minn-admin-rest.php:14407, 14433-14441, 14452-14463, 14576-14577; 14246-14256, 14400-14417, 14433-14463, 14574-14582

    Recommendation

    Lock the switch (as 'elsewhere') when ini_get('auto_prepend_file') names a file that config_defined_by_loaded() says may define the constant, or include every get_included_files() entry before wp-config.php other than core's index.php, wp-blog-header.php and wp-load.php in the scanned list; treat an unreadable prepend as one that may define it.

  • low WP Crontrol event list and detail clip cron arguments to 200 bytes before the egress fence, so a credential copy straddling the cut prints in part (missed sibling of RC 11-2)

    information_disclosure

    minn_admin_crontrol_row() takes WP Crontrol's get_args_display() and cuts it with substr($args_out, 0, 200) . '…'; the same clipped string is the detail view's Arguments row, so GET /minn-admin/v1/crontrol/events and /crontrol/events/{id} never hand the full arguments to Minn_Admin_Egress. The fence masks only whole needle forms, so a credential the database browser counts (a licence key, gateway secret or connector token of 16+ characters) that begins before byte 200 and ends after it is printed up to the cut instead of as [redacted:...]. Typical carriers are URL cron events whose query string holds a service token and scheduled events whose args hold an API key or a tokenised webhook URL. RC 11-2 fixed this shape for the transients list by deciding redaction on the whole stored value (Minn_Admin_DB::withholds_cell()) before clipping; the Crontrol view was not given the same rule. WP Crontrol shows its capability holder the full arguments, so nothing is disclosed beyond the vendor; the defect is that a control Minn claims (no credential the browser would redact leaves a minn-admin/v1 response) does not hold here. Administrator (manage_options) only. The admin-notice text clip (399/159) and WP Mail SMTP event content clip (119) have the same shape with lower likelihood.

    includes/adapters/wp-crontrol.php:139-148, 258

    Recommendation

    Before clipping, ask the browser's rule of the whole display string (e.g. Minn_Admin_DB::withholds_cell( $wpdb->options, 'option_value', array( 'option_name' => 'cron', 'option_value' => $args_out ) )) and serve the arguments withheld when it says so; apply the same decide-then-clip rule to the notice and mail-event clip sites.

  • low Gravity Forms form builder writes the egress mask into untouched field settings (HTML content, default value, description, label, choices): missed sibling of the RC 12-1 fix

    unsafe_input

    GET /minn-admin/v1/gf/forms/{id}/builder is a minn-admin/v1 response, so a credential held in a field's content, defaultValue, description, label, placeholder or choices is served as [redacted:<fp>]. The builder page sends every field row with every setting key as loaded on every save (assets/js/app.js gfbRowOut 56177-56190, save 56303-56316). minn_admin_gfb_save() decides what changed with minn_admin_gfb_changed() (664-701), which compares each sent value with the raw minn_admin_gfb_field_row() value through minn_admin_gfb_same() (636-655), a plain string compare: the masked copy differs from the stored credential, so it is treated as an edit, minn_admin_gfb_overlay() writes it as sent (737-745) and Gravity Forms' editor save stores it (1103-1104). The file has no Minn_Admin::unchanged()/Minn_Admin_Egress::served_as() compare and no refuse_masks(); form meta has no option-level restore. ad66eefa fixed this shape for the ACF and ACPT builders, GF notifications/confirmations and the form email pages, not for the GF form builder. Reachable by a gravityforms_edit_forms holder (gate 1169-1171). A form whose HTML field embeds a key or whose hidden field default carries a token also stored in a credential row loses it to the placeholder on any unrelated builder save, silently breaking the embed or integration. Reachable by anyone holding gravityforms_edit_forms, including every multisite subsite admin; forms live in Gravity Forms' own table, where the option-level egress restore never runs. (Found independently by two audit slices.)

    includes/adapters/gravity-forms-builder.php:636-655, 664-701, 737-745, 1040-1043, 1103-1104; 664-691, 723-745, 893-923, 1016-1018, 1031-1047, 1110

    Recommendation

    Treat a value as unchanged when Minn_Admin::unchanged( $v, $base[ $k ] ) holds (served_as for arrays such as choices), and run Minn_Admin_Egress::refuse_masks() over each row's changed text values against the stored field (and over sent title/description) before $handler->save(). Add a mask case to the forms suite.

  • low WP Job Manager panel offers Featured and Expiry to a listing editor WPJM's own box hides them from, drops the edit silently, and the save answers success

    unsafe_input

    minn_admin_wpjm_mapped_fields() (wp-job-manager.php 70-107) filters fields by auth_view_callback only, so _featured and _job_expires, whose auth_edit_callback is auth_check_can_manage_job_listings (wp-job-manager class-wp-job-manager-post-types.php 2104-2123, 2362-2370), are drawn as ordinary controls for a user holding edit_job_listings without manage_job_listings. WPJM's own metabox omits them (admin/class-wp-job-manager-writepanels.php 71-90). minn_admin_wpjm_write_values() (134-204) refuses them with a silent continue (181-191) and the update callback (292-300) reports nothing, so the post save answers 200 with no minn_field_errors; the client adopts the sent value as saved unless it came back blank (assets/js/app.js 36775-36781). A new expiry date or an unticked Featured reads as stored while nothing changed. Requires a role-manager grant (WPJM gives both caps only to administrators by default). No privilege is gained; the refusal is reported as success, against security.md's panel rule.

    includes/adapters/wp-job-manager.php:70-107, 134-204, 292-300

    Recommendation

    Leave out of the field list and values any field whose auth_edit_callback refuses this user for this post, as WPJM's metabox does, and report a refused write through minn_admin_panel_held() so it returns in minn_field_errors instead of being dropped silently.

  • low Perfmatters settings drop raw-output fields from a caller without unfiltered_html or code edits and answer "Settings saved"

    unsafe_input

    minn_admin_perfmatters_save() (perfmatters.php 303-310) skips, with a bare continue, any raw-output field (header/body/footer code, DNS prefetch, tracking id, CDN hosts, lazyload threshold, login URL message; 245-261) when the caller lacks unfiltered_html or Minn_Admin::code_edits_allowed() is false. The GET tab shape (136-180) serves those fields as ordinary editable fields and the POST (403-411) returns 200 with the stored values; the client toasts 'Settings saved' and re-renders (assets/js/app.js 21117-21122). A multisite subsite administrator (manage_options, Perfmatters' own gate 206-211, no unfiltered_html) who sets a CDN URL or tracking id is told it saved while nothing was stored; a mixed save half-applies under a success message. Minn's stricter rule is correct; the success answer is not (the Bricks settings save refuses such cases with 400).

    includes/adapters/perfmatters.php:136-180, 303-310, 403-411

    Recommendation

    Before any write, if any sent key is a raw-output field and the caller fails the rule, return 403 naming those fields and write nothing.

  • low Gift card 'Adjust balance' sets an absolute balance computed at request time, crediting back a redemption made after the person read the balance (PW Gift Cards' own adjustment is a delta)

    unsafe_input

    The kit's Adjust balance action sends one absolute 'New balance' (gift-cards-kit.php 205-208). PW Gift Cards applies adjust_balance( $balance - get_balance() ) at request time (pw-gift-cards.php 413-436) and WooCommerce Gift Cards calls set_balance( $balance ) (woocommerce-gift-cards.php 492-520), with nothing tying the request to the balance the person saw. PW Gift Cards Pro's own screen adjusts by a typed amount (pw-gift-cards/admin/pw-gift-cards-admin.php 1720-1745), so a concurrent redemption is never undone; WooCommerce Gift Cards offers no balance edit of its own (YITH's own screen is absolute: parity). A Shop Manager who reads 50, types 40 to deduct 10 while a checkout redeems 20, credits the card +10 instead: the redeemed amount is returned to the card and the ledger shows a staff credit nobody intended. Same race class as the 0.44.0 stock-quantity finding fixed with inventory_delta.

    includes/adapters/pw-gift-cards.php:413-436 (also woocommerce-gift-cards.php 492-520, gift-cards-kit.php 205-208)

    Recommendation

    Send the balance the person saw (balance_seen from the row) and answer 409 when the stored balance differs, as JetSearch's weight_seen does, or accept a signed adjustment as PW's own screen does.

  • low Database browser prints, and the egress fence never masks, credentials Contact Form 7 and Yoast SEO mask on their own screens (CF7 Stripe secret key, captcha secrets, Brevo key, Constant Contact OAuth tokens; Yoast SEMrush/Wincher OAuth tokens and per-user AI JWTs)

    information_disclosure

    Minn's database browser (GET /minn-admin/v1/db/rows and /db/row, gated by can_read_system: manage_options + view_site_health_checks + install_owner) redacts option and user-meta rows only by its hand lists (SECRET_OPTION_KEYS, licence names and shapes, SECRET_USERMETA_KEYS), and the egress fence masks only credentials found in those listed rows. Neither list contains Contact Form 7's serialized 'wpcf7' option, which holds stripe.api_keys.secret, the reCAPTCHA and Turnstile secrets, the Brevo api_key and the Constant Contact client_secret plus OAuth access/refresh tokens; CF7's Integration screen (wpcf7_manage_integration => manage_options) prints each only through wpcf7_mask_password() (contact-form-7 modules/stripe/service.php:272, recaptcha/service.php:371, turnstile/service.php:354, sendinblue/service.php:229, constant-contact.php:199), and core options.php shows the row as SERIALIZED DATA (wp-admin/options.php:416-422). Nor do they contain Yoast SEO's 'wpseo' option, which stores semrush_tokens, wincher_tokens and myyoast-oauth access/refresh tokens in plain text (src/config/oauth-client.php:195-196) that Yoast's settings UI explicitly withholds (src/integrations/settings-integration.php:63-72), or Yoast's per-user _yoast_wpseo_ai_generator_access_jwt / _refresh_jwt user meta, which no screen shows. A site Administrator, the hardened-site viewer the browser's redaction exists for, therefore reads a live Stripe secret key, mailing-list and captcha secrets, SEO-tool OAuth tokens and every other user's AI refresh token in full. Same class as the fixed RC 08-2 (fluentmail-settings).

    includes/class-minn-admin-db.php:1179, 1221, 1376-1380, 1007-1016, 757-808, 1694-1706

    Recommendation

    Add 'wpcf7' and 'wpseo' to SECRET_OPTION_KEYS (their secrets sit at credential-named leaves, so they also become egress needles and value-search exclusions), add '_yoast_wpseo_ai_generator_access_jwt' and '_yoast_wpseo_ai_generator_refresh_jwt' to SECRET_USERMETA_KEYS and to the usermeta needle keys in credential_needles(), and extend the security.md list.

  • low Picking the Custom palette resends the whole palette as loaded, putting back colors changed meanwhile (incomplete sibling of the 0.45.0 appearance-resend fix)

    unsafe_input

    Since 0.46.0 every appearance control sends only its own keys (appearancePick) and slot edits send deltas, and the route keeps every key a body leaves out and merges only the colors sent (update_my_appearance with Minn_Admin::merge_custom_tokens). A pick of the Custom swatch still sends the whole custom map: on the user edit page save(['scheme','custom']) copies ue.appearance.custom, the copy loaded when the page opened, and on Your profile the swatch handler sends prev.custom from the boot copy when the previous scheme was a preset. appearanceOf() normalizes it to every slot of both modes, so merge_custom_tokens() overwrites every stored color. An administrator's Custom pick on a user's page therefore puts back colors the user changed in their own session since the page opened, and a user's own Custom pick undoes colors an administrator pre-set meanwhile. The route already keeps the stored palette when custom is absent. Impact is cosmetic: Minn palette colors only.

    assets/js/app.js:54045-54056, 5614-5620 (server merge rule: includes/class-minn-admin-rest.php 7648-7650; includes/class-minn-admin.php 922-940)

    Recommendation

    Send { scheme: 'custom' } alone for a Custom pick whose previous scheme was a preset or custom (seed the palette only for the site -> custom case); the route already merges absent keys from the stored record.

  • low Run on Minn installs any release Minn ever signed: the Ed25519 signature covers bytes, not version, and the engine's own no-downgrade and version-named rules are not applied

    insecure_config

    Minn_Admin_Switch::release() takes version, package, sha256 and signature from the update service's answer (class-minn-admin-switch.php:111-123), and stage() accepts the archive once its sha256 matches and an Ed25519 signature over the zip bytes verifies against the pinned KEYS (:177-185, :257-276). Nothing binds the signature to a release: the version is read from the staged bootstrap.php and never compared with the version the service named (:209-210), and no floor is applied beyond the presence of Minn\Cli\Installer::run (:233). The engine's own updater, for the same signed archives, refuses a version not newer than the running one ('an older release signed in its day is still signed, and putting it back would bring back what it was replaced for', minn/src/Minn/Ops/EngineUpdate.php:65-77) and an archive whose version differs from its release's (:108-111). So whoever controls the answer of https://updates.minn.run/v1/minn/releases/latest (a compromised service or CDN, a TLS-intercepting proxy the host trusts) can name an older signed minn.zip with its genuine sha256 and signature, and an administrator's Check + Switch (POST minn-admin/v1/minn-switch/check, then /minn-switch/run) or `wp minn install` replaces WordPress with that superseded engine and every hole it carried. The signature exists precisely so the service need not be trusted; the precondition is control of the release channel, so this is low.

    includes/class-minn-admin-switch.php:111-123, 177-185, 209-210, 233

    Recommendation

    Apply the engine's own rules: refuse when the archive's MINN_ENGINE_VERSION differs from the release's named version (EngineUpdate.php:108-111), and refuse a release older than a floor Minn Admin carries (a MIN_ENGINE constant bumped each release, or the highest version this site has been offered, stored in an option), mirroring EngineUpdate.php:65-77 for a site with no running engine. Longer term, sign a (version, sha256) statement rather than bare bytes; optionally reuse Archive::inspect()'s symlink and size refusals before extractTo().

  • low WP Activity Log event context values are clipped to 300 bytes before the egress fence sees them, so a credential copy straddling the cut prints in part

    information_disclosure

    minn_admin_resource('wsal/events') item view (includes/adapters/wp-activity-log.php:195-234) prints every wsal_metadata value for the event, clipped with substr( $val, 0, 300 ) . '…' at :221-222. Minn_Admin_Egress masks only whole credential forms, so a key the database browser redacts that starts before byte 300 and ends after it is no longer recognised and its leading characters are served. WSAL logs raw values into this table (post-meta events 2053-2055 MetaValue, user-meta 4016 new_value; wp-security-audit-log/classes/WPSensors/class-wp-metadata-sensor.php:335-347, 410-415). WSAL's own viewer shows the full value to the same user, so this is not past the vendor, but it defeats Minn's claim that no redacted credential leaves a minn-admin/v1 response. Same shape as RC 11-2 (fixed for transients by deciding on the whole value) and 00-2 (WP Crontrol); WSAL was not among 00-2's listed peers. Reachable by whoever WSAL's 'can view' setting admits (administrators by default).

    includes/adapters/wp-activity-log.php:217-223

    Recommendation

    Decide redaction on the whole stored value before clipping (as transients-manager.php does via Minn_Admin_DB::withholds_cell()) and show the value as withheld when the browser would redact it; put the clip-after-check rule in one helper used by every clip site (WSAL, WP Crontrol, notices, WP Mail SMTP).

  • low escCssUrl output placed in HTML style attributes without HTML-escaping (image field thumbnails and siblings)

    missing_escaping

    escCssUrl (assets/js/app.js:297-308) is designed for a CSS url() string but its output is interpolated into HTML style attributes without esc() (imageControlChrome 3177-3181, plus 4859, 7794, 26776-26777 and out-of-range 39279, 50218, 50375, 50423). A stored image URL a Contributor can set on their own draft through the SEO vendor's own REST route (Rank Math updateMeta, sanitized with CMB2::sanitize_textfield) is served unchanged by the Minn SEO panel (includes/adapters/seo.php:483-490) and can alter the presentation of a Minn control when an Editor or Administrator opens that post's SEO panel. No script execution or data access. The same unescaped pattern also feeds the featured thumbnail, site icon, theme screenshot and media-picker tiles (app.js 39279, 50218, 50375, 50423), found independently by a second slice.

    assets/js/app.js:297-308, 1962-1966, 3177-3181, 3247, 4859, 7794, 26776-26777, 39279, 50218, 50375, 50423

    Recommendation

    HTML-escape escCssUrl output at every call site (esc( escCssUrl( u ) )) or have escCssUrl return HTML-escaped text; alternatively set style.backgroundImage via the DOM after render. Apply to the out-of-range siblings as well.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.