WordPress Plugin Security Audit: Static Analysis with RIPS and Semgrep

Running automated static analysis on your WordPress plugin before release catches entire classes of vulnerabilities — SQL injection, reflected XSS, open redirects, and insecure unserialize() calls — that manual code review often misses. Two tools stand out for PHP/WordPress work: the open-source Semgrep with its WordPress rule-set, and the community-maintained PHPCS WordPress-Security sniff.

Problem: A WordPress plugin codebase has never been audited for security — SQL injections, nonce mismatches, missing capability checks, and file inclusion vulnerabilities accumulate silently without automated detection.

Solution: Run phpcs --standard=WordPress-Security using the WordPress Coding Standards ruleset to catch common patterns like missing $wpdb->prepare(), output unescaped with echo $var, and missing nonce verification. Supplement with Psalm or PHPStan using the WordPress stubs package for type-level analysis.


The commands below show how to install both tools, run them against a plugin directory, and interpret the most important findings. A sample vulnerable snippet and its fixed version are included.


# 1. Install Semgrep
pip install semgrep

# 2. Run WordPress-specific rules from the semgrep-rules community registry
semgrep --config "p/wordpress" /path/to/my-plugin/

# 3. Install PHP_CodeSniffer + WordPress Security sniffs
composer require --dev squizlabs/php_codesniffer wp-coding-standards/wpcs
./vendor/bin/phpcs --config-set installed_paths vendor/wp-coding-standards/wpcs

# 4. Run PHPCS with WordPress-Security ruleset
./vendor/bin/phpcs --standard=WordPress-Security /path/to/my-plugin/


<?php
// ❌ VULNERABLE: direct $_GET use in SQL, unsanitised output
add_action( 'wp_ajax_my_action', function () {
    global $wpdb;
    $id  = $_GET['id'];                         // no sanitisation
    $row = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = $id" );
    echo $row->post_title;                      // no escaping
    wp_die();
} );

// ✅ FIXED: nonce check, absint sanitisation, prepared statement, escaped output
add_action( 'wp_ajax_my_action', function () {
    check_ajax_referer( 'my_action_nonce', 'nonce' );
    global $wpdb;
    $id  = absint( $_GET['id'] ?? 0 );
    $row = $wpdb->get_row(
        $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE ID = %d", $id )
    );
    if ( $row ) {
        echo esc_html( $row->post_title );
    }
    wp_die();
} );


NOTE: Semgrep's p/wordpress pack flags unsafe $_GET/$_POST in SQL and echo statements; PHPCS WordPress-Security additionally checks for missing nonce verification and direct database access — run both tools together for maximum coverage.