WP Manifestindependent plugin directory
manifest / developer / wp-steroids

WordPress on Steroids WP Registry grade D. High-severity findings · 7 findingsWP RegistryHigh-severity findings · 7 findingsOpen the reportD

Supercharge Wordpress with YML configuration and more

★ 23stars
9kcomposer installs
1.5.19latest · 3mo ago

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/metabolism/wp-steroids/archive/refs/heads/1.5.x.zip

On Packagist as metabolism/wp-steroids:

composer require metabolism/wp-steroids
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Latest Stable Version Latest Unstable Version License

Supercharge WordPress with:

  • WordPress configuration using yml
  • Permalink configuration for custom post types and taxonomies
  • Maintenance mode
  • Backup download in dev mode
  • Build hook
  • Custom routes action
  • Disabled automatic updates
  • Enhanced security
  • Better GUID using RFC 4122 compliant UUID version 5
  • Multisite images sync (for multisite as multilingual)
  • SVG support
  • Better performance
  • WordPress bug fixes
  • Radio type for taxonomies
  • Multisite post deep copy (with multisite-language-switcher plugin)
  • Custom datatable support with view and delete actions in admin
  • Google Translate or DeepL integration
  • Optimizations

The YML file allows you to configure:

  • Image options
  • Maintenance support
  • Admin pages removal
  • WYSIWYG MCE editor
  • Feature support
  • Multisite configuration
  • ACF configuration
  • Menu
  • Custom post types
  • Custom taxonomies
  • Blocks
  • Page, post, and taxonomy templates
  • Page state
  • Post format
  • External table viewer
  • Advanced roles

Documentation

Documentation is available on Gitbook

Demo

Bedrock install using WP Steroids: https://github.com/metabolism/wp-steroids-demo

YML Sample

##########################################################################
##                                                                      ##
##                    WordPress configuration file                      ##
##                                                                      ##
##########################################################################

wordpress:

  #####################
  ##    Gutenberg    ##
  #####################

  gutenberg:
    replace_reset_styles: true
    remove_core_block: true
    remove_plugin_block:
      - lloc/msls-widget-block
      - yoast/faq-block
      - yoast/how-to-block
      - yoast-seo/breadcrumbs
    remove_core_block_patterns: true
    disable_classic_theme_styles: true
    remove_block_library: true
    block_sorting: true
    load_remote_block_patterns: false
    block_editor_style: '/build/bundle.css'
    block_editor_script: '/blocks.js'
    render_template: 'block/{name}/{name}.twig'
    preview_image: '/app/blocks/{name}.png'
    preview_width: 375


  #########################
  ##    Image options    ##
  #########################

  image:
    compression: 95 #on the fly compression ratio
    max_size: 2 #Max image file size on upload
    svg: true
    resize: #resize image on upload to save server space
      max_width: 1920
      max_height: 2160

  ###########################
  ##    Admin interface    ##
  ###########################

  ## Enable maintenance mode support
  maintenance: true

  ## Hide pages from menu
  remove_menu_page:
    - edit-comments.php #Comments
  #  - index.php #Dashboard
  #  - jetpack #Jetpack
  #  - upload.php #Media
  #  - themes.php #Appearance
  #  - plugins.php #Plugins
  #  - users.php #Users
  #  - tools.php #Tools
  #  - options-general.php #Settings

  ## Hide page from submenu
  remove_submenu_page:
    - themes.php: site-editor.php
    - themes.php: site-editor.php?path=/patterns
  #  - themes.php: nav-menus.php

  ## Handle Customize experience on the server-side.
  ## https://developer.wordpress.org/reference/classes/wp_customize_manager/
  wp_customize:
    remove_section:
      - custom_css

  ## Customize WYSIWYG Editor TinyMCE Buttons
  ##https://www.tiny.cloud/docs/advanced/editor-control-identifiers/
  mce:
    #css: '/static/editor_style.css'
    buttons:
      - formatselect
      - bold
      - italic
      - underline
      - sup
      - strikethrough
      - superscript
      - subscript
      - bullist
      - numlist
      - blockquote
      - hr
      - table
      - alignleft
      - aligncenter
      - alignright
      - alignjustify
      - link
      - unlink
      - wp_more
      - spellchecker
      - wp_adv
      - dfw

  #editor_style: backoffice.css

  ###########################
  ##     User Profile      ##
  ###########################

  user_profile:
    color_scheme: false
    app_passwords: false
    syntax_highlighting: false
    comment_shortcuts: false
    admin_bar_option: false
    keyboard_shortcuts: false
    contact_methods:
      - facebook
      - instagram
      - linkedin
      - twitter
      - url

  ##################
  ##    System    ##
  ##################

  ## Add blog support
  support:
    - page #enable page
    #- post #enable post
    #- tag #enable tag taxonomy for post
    #- category #enable category taxonomy for post

  ## Add post type support
  ## https://developer.wordpress.org/reference/functions/post_type_supports/
  post_type_support:
    page:
      - excerpt #add excerpt to page

  ## Add theme support
  ## https://developer.wordpress.org/reference/functions/add_theme_support/
  theme_support:
    #- core-block-patterns
    - thumbnail #add thumbnails to post
    #- post-formats:
    #  - video
    #  - gallery

  options_page: #add option pages https://www.advancedcustomfields.com/resources/options-page/
    global:
      fields:
        gtm:
          type: text
    translations:
      fields:
        translations:
          type: repeater
          layout: table
          sub_fields:
            key:
              type: text
            translation:
              type: text

  ## Use WordPress as a headless cms
  #headless:
  #  mapping: true

  ## Multisite configuration
  # multisite:
  #  shared_media: true
  #  clone_post: true

  ## Declare constant
  #define:
  #  disallow_file_edit: false

  ## Add custom routes
  #custom_routes:
  #  script: script.js

  #########################
  ##    Carbon fields    ##
  #########################
  carbon_fields:
    shared_fields:
      display:
        type: select
        options:
          default: Default
          theme: Theme
          theme-alt: Theme alt

  ###############
  ##    ACF    ##
  ###############

  acf:
    json_path: '/config/acf'
    settings:
      use_entity: true #add new optimised return format
      autoload: true #autoload options
    user_settings:
      gallery_height: 210
    block:
      api_version: 3
      version: 3
      node:
        breakpoints:
        class:
          - p-vimeo
          - p-youtube
    input:
      lock_max_length: false
    toolbars: #customize wysiwyg toolbar
      Simple:
        1:
          - bullist
          - numlist
          - link
          - bold
          - italic
          - small
          - fullscreen
      Full:
        1:
          - formatselect
          - bold
          - italic
          - small
          - blockquote
          - superscript
          - subscript
          - bullist
          - numlist
          - table
          - alignjustify
          - pastetext
          - removeformat
          - link
          - wp_adv
          - fullscreen
        2:
          - undo
          - redo
          - hr
          - underline
          - strikethrough
          - forecolor
          - removeformat
          - charmap
          - outdent
          - indent
      Basic:
        1:
          - bold
          - italic
          - small
          - blockquote
          - superscript
          - subscript
          - forecolor
          - bullist
          - numlist
          - table
          - link
          - alignjustify
          - pastetext
          - removeformat
          - fullscreen


  ################
  ##    Menu    ##
  ################

  menu:
    depth: 1
    register:
      footer:
        title: Footer
        fields:
          sample:
            type: text
      header: Header


  ###################
  ##    Sidebar    ##
  ###################

  #sidebar: # https://developer.wordpress.org/themes/functionality/sidebars/#registering-a-sidebar
  #  footer:
  #    name : Footer
  #    description : 'Add widgets here to appear in your footer.'
  #    before_widget : '<section id="%1$s" class="widget %2$s">'
  #    after_widget : '</section>'
  #    before_title : '<h2 class="widget-title">'
  #    after_title : '</h2>'


  ##########################
  ##       New block      ##
  ##########################

  #block_categories:
  #  media : Media


  block:
    hero:
      title: Hero
      description: Page Hero
      supports:
        multiple: false
      icon: 'editor-alignleft'
      post_types:
        - page
        - guide
      fields:
        suptitle:
          type: text
        title:
          type: text
        content:
          type: wysiwyg
          toolbar: basic
          media_upload: false
        image:
          type: image
        cta:
          type: link

    video:
      title: Video
      #description: Video
      icon: 'format-video'
      post_types:
        - page
        - guide
      fields:
        video:
          type: url
          placeholder: 'https://www.youtube.com/watch?v=FcTLMTyD2DU'
        cover:
          type: image

    contact:
      title: Contact
      #description: Featured article
      icon: 'email'
      post_types:
        - page

    #hero-home:
    #  title: Homepage hero
    #  description: Display a slider
    #  supports:
    #    align_text: 'left'
    #    #multiple: false
    #  icon: 'editor-alignleft'
    #  post_types:
    #    - page
    #    - guide


  ##########################
  ##    New post types    ##
  ##########################

  post_type: # https://developer.wordpress.org/reference/functions/register_post_type/

    page:
      columns:
        - thumbnail
      template:
        - - acf/hero
          - lock:
              move: true
              remove: true

    guide:
      menu_icon: book #https://developer.wordpress.org/resource/dashicons/
      has_archive: true
      #enable_for_blogs:
      #  - 1
      #disable_for_blogs:
      #  - 2#
      #has_options: true #add acf page option to post type menu
      #capability_type: post
      #publicly_queryable: false
      ## options for archive page
      #posts_per_page: 11
      #orderby: name #https://developer.wordpress.org/reference/classes/wp_query/#order-orderby-parameters or last_word
      #order: ASC
      show_in_rest: true
      template:
        - - acf/hero
          - lock:
              move: true
              remove: true
      supports:
        - title
        - excerpt
        - thumbnail
        - editor
        - revisions
        #- sticky
        #- author
        #- excerpt
        #- trackbacks
        #- custom-fields
        #- comments
        #- page-attributes
        #- post-formats
        #columns: #add new column to post listing, thumbnail|meta
        # - thumbnail
        #taxonomies:
        # - category
      #labels:
      #  name:           'Guides'
      #  singular_name:  'Guide'
      #  all_items:      'Tous les guides'
      #  edit_item:      'Editer le guide'
      #  view_item:      'Voir le guide'
      #  update_item:    'Mettre à jour le guide'
      #  add_new_item:   'Ajouter un guide'
      #  new_item_name:  'Nouveau guide'
      #  search_items:   'Rechercher un guide'
      #  popular_items:  'Guides populaires'
      #  not_found:      'Aucun guide trouvé'
      field_group:
        position: side
      fields:
        sample:
          type: text


  ##########################
  ##    New taxonomies    ##
  ##########################

  taxonomy: # https://developer.wordpress.org/reference/functions/register_taxonomy/
    item:
      ## options for archive page
      #posts_per_page: 11
      #orderby: name #https://developer.wordpress.org/reference/classes/wp_term_query/__construct/
      #order: ASC
      #radio: true #display terms selection as radio instead of checkbox
      #publicly_queryable: false
      #show_in_submenu: false
      #show_in_menu: true
      show_in_rest: true
      #capability_type: true
      object_type:
        - guide
      #capabilities:
      #  manage_terms: 'do_not_allow'
      #  edit_terms: 'do_not_allow'
      #  delete_terms: 'do_not_allow'
      #  assign_terms: 'edit_posts'
      #labels:
      #  name:           'Catégories'
      #  singular_name:  'Catégorie'
      #  all_items:      'Toutes les catégories'
      #  edit_item:      'Editer la catégorie'
      #  view_item:      'Voir la catégorie'
      #  update_item:    'Mettre à jour la catégorie'
      #  add_new_item:   'Ajouter une catégorie'
      #  new_item_name:  'Nouvelle catégorie'
      #  search_items:   'Rechercher une catégorie'
      #  popular_items:  'Catégories populaires'
      #  not_found:      'Aucune catégorie trouvée'
      fields:
        sample:
          type: text

    ####################################
    ##    Templates/States/Formats    ##
    ####################################

    ## Add post and taxonomy templates
    #template:
    #  page:
    #    coming_soon: 'Coming Soon'
    #    not_found: '404'
    #  post:
    #    video: 'Video'
    #  taxonomy:
    #    item:
    #      edito: 'Edito'
    #      podcast: 'Podcast'
    #      reportage: 'Reportage'
    #      video: 'Video'

    ## Add page states like "homepage"
    #page_states:
    #archive_guide: 'Guide archive'
    #404: '404'

  #################################
  ##    Database table viewer    ##
  #################################

  #table:
  #  newsletter:
  #    page_title: Newsletter
  #    menu_title: Newsletter
  #    column_title: id
  #    export: false
  #    columns:
  #      name: Name
  #      email: Email
  #      company: Company
  #      type: Type
  #  contact:
  #    page_title: Contact
  #    menu_title: Contact
  #    column_title: id
  #    columns:
  #      name: Name
  #      email: Email
  #      company: Company
  #      request: Request

  #############################################
  ##               Add Roles                 ##
  ##                                         ##
  ##    To reload role please go to          ##
  ##    /wp-admin/index.php?reload_role=1    ##
  ##                                         ##
  #############################################
  #role:
  #  translator:
  #    display_name: Translator
  #    redirect_to: edit.php?post_type=guide
  #    #inherit: editor
  #    capabilities:
  #      read: true
  #      publish: false
  #      manage_items: true
  #      edit_items: true
  #
  #      edit_blocks: false
  #
  #      manage_categories: true
  #      edit_category: true
  #      delete_category: false
  #      assign_category: false
  #
  #      edit_others_pages: true
  #      read_page: true
  #      edit_pages: true
  #      publish_pages: false
  #      edit_published_pages: true
  #
  #      edit_others_posts: true
  #      read_post: true
  #      edit_posts: true
  #      publish_posts: false
  #      edit_published_posts: true
  #
  #      acf_edit_taxonomy: false
  #      acf_edit_layout: false
  #      acf_edit_relationship: false
  #      acf_edit_repeater: false
  #      acf_edit_true-false: false
  #      acf_edit_radio: false
  #      acf_edit_select: false
  #      acf_edit_image: false
  #      acf_edit_file: false
  #      acf_edit_link: false
  #      acf_edit_post-object: false


  ############################
  ##    Plugins specific    ##
  ############################

  #plugins:
  #  redirection:
  #    redirection_role: edit_others_posts
  #  relevanssi:
  #    options_capability: edit_others_posts

  #########################
  ##    Optimisations    ##
  #########################

  rewrite_rules:
    remove:
      - author
      - attachment
      - embed
      - trackback
      - comment
      - feed

  ####################
  ##    Security    ##
  ####################

  security:
    rest_api: false
    #rest_api:
    #  remove_core: true
    #  ip_whitelist: true #add coma separated ip in $_ENV['REST_API_IP_WHITELIST']
    xmlrpc: false
    pings: false
    disable_update: true
    unfiltered_html: false

  ##################
  ##    Search    ##
  ##################

  #search: # change search settings
  # use_metafields: false
  # posts_per_page: 12

License

The MIT License (MIT). Please see License File for more information.

Releases

Newest 25 of 58 recorded releases.

Tag
Published
1.5.19 latest
Jul 18, 2026 3mo ago
Jun 2, 2026 4mo ago
May 29, 2026 4mo ago
Jan 19, 2026 9mo ago
Dec 31, 2025 9mo ago
Dec 29, 2025 9mo ago
Nov 26, 2025 11mo ago
Nov 25, 2025 11mo ago
Nov 5, 2025 11mo ago
Jun 13, 2025 1 year ago
May 20, 2025 1 year ago
May 16, 2025 1 year ago
Apr 25, 2025 1 year ago
Apr 24, 2025 1 year ago
Apr 22, 2025 1 year ago
Apr 10, 2025 1 year ago
Mar 25, 2025 1 year ago
Feb 18, 2025 1 year ago
Feb 12, 2025 1 year ago
Feb 10, 2025 1 year ago
Dec 6, 2024 1 year ago
Dec 2, 2024 1 year ago
Oct 23, 2024 1 year ago
Oct 7, 2024 2 years ago
Sep 26, 2024 2 years ago
All releases on GitHub →

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.

D grade

Security

WordPress on Steroids 1.5.19 · audited by WP Registry

High-severity findings.

4 high 3 medium
Audited release
1.5.19
Findings
7
Worst severity
high
Content hash
f5403e814c5fd6129e2ff872…

Findings

  • high Unauthenticated REST lockdown bypass via contact-form-7 URI substring

    auth_bypass

    By default security.rest_api is false, so WPS_Security::disableRest() returns WP_Error restricted_rest_api_access for every unauthenticated REST request. WPS_Contact_Form_7 then registers rest_authentication_errors at priority 11 and, if the error code matches and strpos($_SERVER['REQUEST_URI'], 'contact-form-7') !== false, returns false — which WordPress treats as 'no error, continue'. The match is not limited to /wp-json/contact-form-7/; CF7 does not even need to be installed. An unauthenticated visitor can call GET /wp-json/wp/v2/users?x=contact-form-7 (or any other core/plugin route whose permission_callback allows public access) and the plugin's advertised REST lockdown is gone. User listing of authors with published content is the immediate observable; any third-party route that assumed this plugin had closed unauth REST is also reachable.

    includes/plugins/contact-form-7.php:36-48

    Recommendation

    Allowlist the CF7 REST namespace only (e.g. rest_get_url_prefix() plus str_starts_with($GLOBALS['wp']->query_vars['rest_route'] ?? '', '/contact-form-7/')). Do not match an arbitrary substring of REQUEST_URI. Keep returning the WP_Error for every other route.

  • high Any logged-in user can convert/delete arbitrary PNG attachments via post.php?action=convert

    missing_capability

    WPS_Media hooks post_action_convert and post_action_regenerate_metadata. WordPress wp-admin/post.php fires do_action("post_action_{$action}", $post_id) in the default switch branch with no current_user_can and no check_admin_referer (nonce is generated in mediaRowActions via wp_nonce_url but never verified). A Subscriber GET /wp-admin/post.php?action=convert&post={png_id} runs convertToJpg, wp_delete_file on the original PNG and all thumbs, wp_update_post mime to image/jpeg, then $wpdb->query REPLACE of the old/new basename across posts, postmeta, AND options. Observable: the attachment file is gone, every content/option containing that filename is rewritten (serialized options can break), CSRF works because there is no nonce check. regenerate_metadata is the same missing gate and overwrites attachment metadata for any post ID.

    includes/extensions/class-media.php:860-990

    Recommendation

    In both handlers call check_admin_referer('convert-post_'.$post_id) / 'fix_metadata-post_'.$post_id and current_user_can('edit_post', $post_id) before touching files or the database. Do not REPLACE across wp_options with unsandboxed filenames.

  • high Any logged-in user can write permalink options on load-options-permalink.php

    missing_capability

    WPS_Config registers LoadPermalinks on load-options-permalink.php whenever is_admin() (unless WP_HEADLESS without mapping). WordPress fires that load hook from admin.php before options-permalink.php's current_user_can('manage_options') wp_die. LoadPermalinks reads $_POST['page_rewrite_slug'], $_POST['search_rewrite_slug'], and $_POST[{post_type|taxonomy}_rewrite_{slug|archive}] with no capability and no nonce, update_option's them, then flush_rewrite_rules. A Subscriber POST to /wp-admin/options-permalink.php with page_rewrite_slug=evil updates the option; on the next init updatePageStructure/updateSearchStructure consume it and rewrite all page/search URLs. CSRF against any logged-in session also works. Observable: site permalinks change and/or all front-end URLs 404.

    includes/extensions/class-config.php:720-841

    Recommendation

    Do not write options on the load-* hook. Process saves only after current_user_can('manage_options') and check_admin_referer('update-permalink'). Sanitize slugs with sanitize_title().

  • high Unauthenticated-capability clone of any multisite post/term when clone_post is enabled

    missing_capability

    When YAML multisite.clone_post is true and MSLS is present, load-post-new.php and load-edit-tags.php run WPS_Multisite_Language_Switcher::load_post_new / load_edit_tags. Those load hooks fire in admin.php before post-new.php's create_posts check and edit-tags.php's manage_terms check. Any logged-in user GET /wp-admin/post-new.php?clone=1&blog_id={n}&post_id={id} switch_to_blog's the attacker-chosen blog, get_post's that ID (no cap), copies all post_meta (maybe_unserialize), and wp_insert_post's a draft on the current site. Same for terms via tag_ID. Observable: a Subscriber creates a draft copy of a private/draft post from another site in the network, including meta. No nonce. Default is false; the README documents 'Multisite post deep copy'.

    includes/plugins/multisite-language-switcher.php:155-244

    Recommendation

    After the load hook, require current_user_can($post_type_object->cap->create_posts) (and edit_post on the source), check_admin_referer, and validate blog_id is a site the user can access. Do not clone until those gates pass.

  • medium wp_ajax_translate has no capability or nonce check

    missing_capability

    add_action('wp_ajax_translate') is registered for every logged-in user with no check_ajax_referer and no current_user_can. The handler forwards $_POST['q'] and $_POST['format'] to Google Translate or DeepL using WP_GOOGLE_TRANSLATE_KEY / WP_DEEPL_KEY and wp_send_json's the result. Subscriber and CSRF (admin-ajax.php with cookies) can burn paid API quota and drive HTML format through the translation APIs. Keys are not returned to the client. No nopriv, so unauth is blocked by wp_ajax_*.

    includes/extensions/class-translation.php:18-20

    Recommendation

    Require current_user_can('edit_posts') (or the capability of the screen the button is on) and check_ajax_referer. Rate-limit per user. Reject unexpected format values.

  • medium Any logged-in user can set page_on_* options via admin_init

    missing_capability

    WPS_Post_States::setPostStates is hooked to admin_init for every wp-admin request. For each YAML page_states key, if $_POST['page_on_'.$post_state] is set it update_option's that value with no capability and no nonce. auth_redirect runs before admin_init so unauth is out, but a Subscriber POST to /wp-admin/profile.php with page_on_{state}=123 writes the option. Consumers: get_page_by_state() and front-end body_class via a SQL lookup of page_on_%. No-op when page_states is empty (default); the README documents 'Page state'.

    includes/extensions/class-post-states.php:17-31

    Recommendation

    Only save on the Reading settings screen after current_user_can('manage_options') and check_admin_referer. Cast the value with absint(). Prefer register_setting like maintenance_field.

  • medium Custom table CSV export runs in the list-table constructor for any Editor

    missing_capability

    When YAML table.* is configured, addTableViews instantiates WPS_List_Table on init for every Editor/Administrator (not only on the menu page). The constructor immediately calls doActions(), which streams a CSV of SELECT * FROM {$wpdb->prefix}{$table} when action=export&page=table_{name} — no nonce, no menu capability check (menu default is activate_plugins). An Editor who cannot open the menu can still GET any wp-admin URL with those query args and download the whole table (documented for form/PII dumps). Delete is GET ?action=delete&id= without a nonce once the user can load the page.

    includes/lib/list-table.php:46-166

    Recommendation

    Do not export/delete in the constructor. Run those actions only inside the menu callback after current_user_can($args['capability']) and check_admin_referer. Never export on a GET without a nonce.

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.