WP Manifestindependent plugin directory
manifest / developer / wp-flags

Flags WP Registry grade B. Medium-severity findings · 1 findingWP RegistryMedium-severity findings · 1 findingOpen the reportB

Flags: WordPress admin-controlled user-based feature flags

★ 36stars
62kcomposer installs
0.1.1latest · 5mo 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/humanmade/wp-flags/archive/refs/heads/master.zip

On Packagist as humanmade/wp-flags:

composer require humanmade/wp-flags
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.
Flags
Lightweight WordPress plugin to enable exposing feature flags to end-users, based on code-based ( or admin UI in the future ) criteria.
A Human Made project.

NOTE: This is a work-in-progress plugin.

Installation

Using Composer

  • Require the package in your project

composer require humanmade/wp-flags

Using submodules

  • Add the plugin as a submodule ( adjust the path as necessary )

git submodule add git@github.com:humanmade/wp-flags.git content/plugins/wp-flags

Usage

use HumanMade\Flags\Flags;

add_action( 'init', function() {
    Flags::add( 'new-flag', 'New Flag', [
        // Is the flag exposed to users ?
        'available' => function() {
            return current_user_can( 'manage_options' );
        },
        // At what level the flag can be set. One of `user` or `site`
        'scope' => 'user',
        // Default flag status
        'active' => true,
        // Is the flag controllable by users ?
        'optin' => true,
        // Custom icon ? ( dashicon-compatible )
        'icon' => 'dashboard',
        // Custom attribute ?
        'some_custom_meta_key' => 'some_value',
    ] );

    // OR just..
    $flag = Flags::add( 'another-flag', 'Another flag' );
    $flag->on( 'active', function( $active, $flag ) {
        // do something based on active status change
    } );

    // Execute logic based on flag status
    if ( Flags::get( 'new-flag' )->active ) {
        show_the_new_sidebar();
    } );
} );

Scope of Setting a Flag

A flag can be set at either the user or site scope, which determines how a flag is controlled. A user-scoped flag can be turned on or off by each user for that user only on a site, whereas a site-scoped flag is turned on or off for every user and is controlled in the site settings.

Credits

Written and maintained by Shady Sharaf. Thanks to all our contributors.

Interested in joining in on the fun? Join us, and become human!

Releases

6 releases.

Tag
Published
0.1.1 latest
May 8, 2026 5mo ago
0.1
Aug 27, 2025 1 year ago
Nov 11, 2021 4 years ago
May 18, 2020 6 years ago
May 15, 2020 6 years ago
May 12, 2020 6 years ago

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

B grade

Security

Flags 0.1 · audited by WP Registry

Medium-severity findings.

1 medium
Audited release
0.1
Findings
1
Worst severity
medium
Content hash
05ee022dadc24daa0dd8f003…

Findings

  • medium Admin-bar AJAX toggles site-scoped flags with nonce but no capability check

    missing_capability

    wp_ajax_wp_flag_ajax_trigger is registered for every logged-in user. handle_change() verifies nonce wp_flags, then Flags::get($id), then $flag->available (class default true) and $flag->optin, then $flag->set('active', (bool) $status) with no current_user_can and no $flag->scope test. AdminBar\add_flag_node prints Ajax\get_toggle_url() (nonce + flag id + status) as the href for every available opt-in flag on the frontend and wp-admin admin bar, which WordPress shows to Subscribers by default. For scope=user the write is the caller's own user meta (intended). For scope=site, Flag::on('active', Site\save) was attached on init, so the same request runs update_option('_wp_flag_{id}', $bool). The README states site-scoped flags are 'controlled in the site settings'; that UI is register_setting on 'general' (manage_options). A Subscriber therefore writes the same option the Settings screen is supposed to protect, and Flag::$active is applied to every user on later requests. Storage mismatch (bool vs 'active'/'inactive') means status=1 persists as '1', which Site\handle() treats as not-active and can wipe an admin-saved 'active' value. Unauthenticated users cannot hit this action (no nopriv). User-scoped self-toggles are out of scope of this finding.

    inc/ajax.php:16-75

    Recommendation

    In handle_change(), if $flag->scope === 'site' require current_user_can('manage_options') (or a dedicated cap) before set('active'). Do not put a GET toggle href on the admin bar for site-scoped flags unless that cap passes. Treat nonce as CSRF protection only, not authorization. Persist site flags as 'active'/'inactive' consistently with SiteMetabox\sanitize_value so a boolean write cannot clobber an administrator's setting.

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.