---
title: Automating Plugin Security Audits with SAST, DAST & GitHub CI/CD Workflows
description: Implement automated vulnerability scanning in GitHub Actions for WordPress plugins. Configure Semgrep custom rules, Psalm taint analysis, and OWASP ZAP.
url: https://wpstack.online/2026/11/03/automating-plugin-security-scans-ci-cd
date_modified: 2026-09-12
author: Aditya Bhimrajka
language: en_US
---

Implement automated vulnerability scanning in GitHub Actions for WordPress plugins. Configure Semgrep custom rules, Psalm taint analysis, and OWASP ZAP.

## 1. Security Testing Taxonomy: SAST vs. DAST vs. IAST vs. Manual Pen-Testing

To establish a defense-in-depth security strategy, engineering teams must deploy complementary testing methodologies across the software development lifecycle (SDLC):

| Testing Dimension | SAST (Static Analysis) | DAST (Dynamic Testing) | Manual Penetration Testing |
| --- | --- | --- | --- |
| **Target State** | Source code at rest (No server needed) | Running WordPress staging instance | Live production or staging instance |
| **SDLC Phase** | Pre-commit / Pull Request CI/CD | Staging deployment / Nightly build | Pre-release major milestone / Annual |
| **Primary Flaws Detected** | Unescaped output, raw SQL concatenation, missing nonces | Auth bypass, SSRF, CORS misconfigurations, server headers | Complex business logic flaws, IDOR, chained exploits |
| **Speed & Automation** | Ultra-fast (< 30 seconds in CI) | Moderate (5 – 15 minutes) | Slow (Days / Weeks of human analysis) |
| **Tooling Ecosystem** | PHPStan, Psalm, Semgrep, WPCS | OWASP ZAP, Nuclei, WPScan, Burp Suite | Burp Suite Professional, Frida, Ghidra |

## 2. The Top 5 WordPress Plugin Vulnerability Sinks: Code Case Studies

Automated static analyzers track untrusted data flow from *sources* to dangerous *sinks*. Below are the five most prevalent vulnerability classes in WordPress plugins, detailing the vulnerable pattern, the underlying exploit mechanism, and the secure remediation:

### 1. SQL Injection via Raw Concatenation

```
<?php
// VULNERABLE PATTERN: Concatenating unescaped $_GET into $wpdb query
global $wpdb;
$id = $_GET['id'] ?? 0;
$user = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}orders WHERE id = " . $id);
// Exploit: ?id=1 OR 1=1 UNION SELECT 1,user_pass,user_email,4 FROM wp_users

// SECURE REMEDIATION: Using prepared statements with strict type placeholders
$id = isset($_GET['id']) ? absint($_GET['id']) : 0;
$user = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}orders WHERE id = %d", $id));

```

### 2. Broken Access Control & IDOR in AJAX Endpoints

```
<?php
// VULNERABLE PATTERN: Checking nonce but omitting capability and ownership checks
add_action('wp_ajax_delete_order', function() {
    check_ajax_referer('delete_order_nonce', 'security');
    $orderId = absint($_POST['order_id']);
    wp_delete_post($orderId, true); // Any logged-in Subscriber can delete any Order!
    wp_send_json_success();
});

// SECURE REMEDIATION: Strict capability check + Object Ownership Validation
add_action('wp_ajax_delete_order', function() {
    check_ajax_referer('delete_order_nonce', 'security');
    
    if (!current_user_can('manage_woocommerce')) {
        wp_send_json_error(['message' => 'Forbidden'], 403);
    }
    
    $orderId = absint($_POST['order_id']);
    wp_delete_post($orderId, true);
    wp_send_json_success();
});

```

### 3. PHP Object Injection (POI) via unserialize()

```
<?php
// VULNERABLE PATTERN: Unserializing base64-encoded cookie or parameter
$cookieData = base64_decode($_COOKIE['wpstack_session'] ?? '');
$session = unserialize($cookieData); // Attackers supply POP gadget chains triggering RCE

// SECURE REMEDIATION: JSON serialization with strict HMAC signing
$cookieData = (string) ($_COOKIE['wpstack_session'] ?? '');
$session = json_decode($cookieData, true);
if (!is_array($session)) {
    $session = [];
}

```

### 4. Server-Side Request Forgery (SSRF) in Remote Webhooks

```
<?php
// VULNERABLE PATTERN: Fetching user-provided URL via wp_remote_get
$webhookUrl = $_POST['webhook_url'] ?? '';
$response = wp_remote_get($webhookUrl); // Exploit: http://169.254.169.254/latest/meta-data/ (AWS Cloud Metadata)

// SECURE REMEDIATION: wp_safe_remote_get with domain whitelist
$webhookUrl = esc_url_raw($_POST['webhook_url'] ?? '');
$host = parse_url($webhookUrl, PHP_URL_HOST);

$allowedHosts = ['api.stripe.com', 'hooks.slack.com', 'api.wpstack.online'];
if (!in_array($host, $allowedHosts, true)) {
    wp_die('Disallowed webhook host.', 'Security Error', ['response' => 400]);
}

$response = wp_safe_remote_get($webhookUrl, ['timeout' => 5, 'redirection' => 0]);

```

### 5. Arbitrary File Upload & Executable Web Shells

```
<?php
// VULNERABLE PATTERN: Using move_uploaded_file with original filename
$file = $_FILES['avatar'] ?? null;
if ($file) {
    move_uploaded_file($file['tmp_name'], WP_CONTENT_DIR . '/uploads/' . $file['name']); // Exploit: avatar.php
}

// SECURE REMEDIATION: wp_handle_upload with strict MIME whitelist and randomized names
if (!empty($_FILES['avatar'])) {
    if (!current_user_can('upload_files')) {
        wp_die('Unauthorized file upload.');
    }

    $uploaded = wp_handle_upload($_FILES['avatar'], [
        'test_form' => false,
        'mimes'     => ['jpg|jpeg' => 'image/jpeg', 'png' => 'image/png'],
    ]);

    if (!empty($uploaded['error'])) {
        wp_die(esc_html($uploaded['error']));
    }
}

```

### 6. Local File Inclusion (LFI) & Path Traversal in Template Loaders

```
<?php
// VULNERABLE PATTERN: Unsanitized template path parameter
$template = $_GET['tab'] ?? 'general';
include plugin_dir_path(__FILE__) . 'views/' . $template . '.php'; // Exploit: ?tab=../../../../wp-config

// SECURE REMEDIATION: Strict whitelist mapping or basename validation
$allowedTabs = [
    'general'  => 'views/tab-general.php',
    'security' => 'views/tab-security.php',
    'logs'     => 'views/tab-logs.php',
];

$selectedTab = sanitize_key($_GET['tab'] ?? 'general');
$templateFile = $allowedTabs[$selectedTab] ?? $allowedTabs['general'];

require_once plugin_dir_path(__FILE__) . $templateFile;

```

### 7. CSV Formula Injection in Data Export Endpoints

```
<?php
// VULNERABLE PATTERN: Writing raw customer strings into CSV cells
fputcsv($outputHandle, [$user->display_name, $user->user_email]); // Exploit name: =cmd|'/C calc'!A0

// SECURE REMEDIATION: Neutralize formula triggers (=, +, -, @, \t, \r)
function sanitize_csv_cell(string $value): string {
    if (preg_match('/^[=\+\-@\t\r]/', $value)) {
        return "'" . $value;
    }
    return $value;
}
fputcsv($outputHandle, [sanitize_csv_cell($user->display_name), sanitize_csv_cell($user->user_email)]);

```

### 8. Insecure Cryptographic Storage & Hardcoded Secrets

```
<?php
// VULNERABLE PATTERN: Storing plain-text API secrets in options table
update_option('wpstack_stripe_secret', $_POST['stripe_key']);

// SECURE REMEDIATION: Encrypting credentials at rest using libsodium and master salt
function store_encrypted_secret(string $optionName, string $plainSecret): void {
    $salt = hash('sha256', (string) (defined('LOGGED_IN_SALT') ? LOGGED_IN_SALT : 'fallback_salt'), true);
    $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
    $ciphertext = sodium_crypto_secretbox($plainSecret, $nonce, $salt);
    
    update_option($optionName, base64_encode($nonce . $ciphertext));
}

```

## 3. STRIDE Threat Modeling for Enterprise WordPress Plugins

Before running automated tooling, security architects must perform systematic threat modeling across the plugin attack surface. Originating from Microsoft’s Trustworthy Computing initiative, the **STRIDE model** classifies threats into six distinct categories tailored to the WordPress runtime environment:

1. **Spoofing Identity:** Can an unauthenticated or low-privilege attacker forge requests posing as an administrator? *Threat Vector:* Exploiting missing nonce tokens on sensitive admin actions. *Mitigation:* Mandatory cryptographic Nonce verification (`wp_verify_nonce`) and asymmetric JWT signature validation on all state-changing endpoints.
2. **Tampering with Data:** Can an attacker alter critical database parameters (such as pricing, order statuses, or user permissions) in transit? *Threat Vector:* Insecure direct parameter binding without server-side validation. *Mitigation:* Server-side capability verification (`current_user_can('edit_post', $post_id)`), strictly typed DTO models, and HMAC parameter signing.
3. **Repudiation:** Can an administrator perform malicious actions (e.g. deleting audit logs, exporting customer PII) without leaving a traceable forensic trail? *Threat Vector:* Lack of centralized activity logging. *Mitigation:* Structured immutable audit logging shipped asynchronously via Redis Streams or external Syslog.
4. **Information Disclosure:** Are database table structures, customer credentials, private API secrets, or internal stack traces exposed to unauthorized users? *Threat Vector:* Verbose error responses and unhandled exceptions. *Mitigation:* Deep JSON response filtering, RFC 7807 problem details, and strict production suppression of `WP_DEBUG_DISPLAY`.
5. **Denial of Service (DoS):** Can a malicious user exhaust server memory, max out PHP-FPM workers, or deadlock MySQL tables via complex unindexed queries? *Threat Vector:* Volumetric API scraping and unbounded regex queries. *Mitigation:* Sliding-window Redis token bucket rate limiters, strict pagination caps (e.g. max 50 items), and database timeout limits.
6. **Elevation of Privilege:** Can a subscriber or contributor user execute administrative functions or install arbitrary extensions? *Threat Vector:* Insecure `wp_ajax_*` hook registrations that omit role checking. *Mitigation:* Granular RBAC guards enforced before any business logic executes.

## 4. SAST Pipeline 1: PHPStan Level 8 with WordPress Stubs

PHPStan with WordPress extensions analyzes type hierarchies and catches undefined functions, incorrect hook arguments, and unhandled return types:

```
# phpstan.neon.dist
includes:
    - vendor/szepeviktor/phpstan-wordpress/extension.neon

parameters:
    level: 8
    paths:
        - src/
    scanFiles:
        - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
        - vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php
    checkMissingIterableValueType: true
    checkGenericClassInNonGenericObjectType: false
    ignoreErrors:
        - '#Constant ABSPATH not matched#'

```

## 5. SAST Pipeline 2: Psalm Taint Analysis Configuration

Psalm taint analysis tracks untrusted input sources across complex execution graphs into dangerous execution sinks:

```
<?xml version="1.0"?>
<psalm
    errorLevel="1"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
    </projectFiles>

    <taintAnalysis>
        <sinks>
            <sink name="sql" />
            <sink name="html" />
            <sink name="shell" />
            <sink name="file" />
            <sink name="unserialize" />
        </sinks>
    </taintAnalysis>

    <stubs>
        <file name="vendor/php-stubs/wordpress-stubs/wordpress-stubs.php" />
    </stubs>
</psalm>

```

## 6. SAST Pipeline 3: Custom Semgrep Taint Analysis Rules

Semgrep enables custom Abstract Syntax Tree (AST) pattern matching. We define custom rules to identify raw SQL query interpolations and missing direct script execution guards:

```
# .semgrep/wordpress-security-rules.yml
rules:
  - id: wpstack-direct-file-access-missing
    languages: [php]
    message: "Direct file execution vulnerability: Missing defined('ABSPATH') check at file header."
    severity: ERROR
    patterns:
      - pattern-not-inside: |
          <?php
          defined('ABSPATH') || exit;
          ...
      - pattern-not-inside: |
          <?php
          if (!defined('ABSPATH')) { exit; }
          ...
      - pattern: prepare()."
    severity: ERROR
    patterns:
      - pattern-either:
          - pattern: $wpdb->query("... $VAR ...")
          - pattern: $wpdb->get_results("... $VAR ...")
          - pattern: $wpdb->get_var("... $VAR ...")
          - pattern: $wpdb->query($SQL . $VAR)
      - pattern-not: $wpdb->query($wpdb->prepare(...))

```

## 7. DAST Pipeline: Automated OWASP ZAP & Nuclei Scanning

Dynamic testing targets a live staging environment. Below is a shell script integrating **Nuclei** and **OWASP ZAP** into the CI/CD deployment pipeline:

```
#!/usr/bin/env bash
set -euo pipefail

TARGET_URL="https://staging.wpstack.online"
REPORT_DIR="./security-reports"
mkdir -p "${REPORT_DIR}"

echo "🚀 [1/3] Running WPScan Vulnerability Database Check..."
wpscan --url "${TARGET_URL}" --format json --output "${REPORT_DIR}/wpscan-report.json" || true

echo "🚀 [2/3] Running Nuclei WordPress Security Templates..."
nuclei -target "${TARGET_URL}" \
  -tags wordpress,wp-plugin,cve \
  -severity critical,high,medium \
  -json-export "${REPORT_DIR}/nuclei-report.json"

echo "🚀 [3/3] Running OWASP ZAP Full Scan Container..."
docker run --rm -v "$(pwd)/${REPORT_DIR}:/zap/wrk/:rw" \
  zaproxy/zap-stable zap-baseline.py \
  -t "${TARGET_URL}" \
  -r zap-baseline-report.html \
  -J zap-report.json || true

echo "✅ Security testing complete. Inspecting reports in ${REPORT_DIR}/"

```

## 8. OWASP ZAP Automation Framework (ZAP AF) Profile

For deep REST API vulnerability scanning against authenticated WordPress endpoints, we define an automated ZAP AF YAML plan. It injects Application Password headers and tests endpoints against active SQLi, XSS, and path traversal fuzzing policies:

```
# zap-af-wordpress.yaml
env:
  contexts:
    - name: "WordPress Staging Plugin Context"
      urls:
        - "https://staging.wpstack.online/wp-json/wpstack/v1/"
      includePaths:
        - "https://staging.wpstack.online/wp-json/wpstack/v1/.*"
      authentication:
        method: "http"
        parameters:
          hostname: "staging.wpstack.online"
          port: 443
          realm: "WordPress REST API"
        verification:
          method: "response"
          loggedInRegex: "\"code\":\\s*\"success\""
          loggedOutRegex: "\"code\":\\s*\"rest_cannot_access\""
jobs:
  - type: openapi
    parameters:
      apiFile: "docs/openapi-schema.json"
      targetUrl: "https://staging.wpstack.online/wp-json/wpstack/v1"
  - type: ascan
    parameters:
      maxDuration: 15
      policy: "API-Minimal"
    rules:
      - id: 40018 # SQL Injection - Hypersonic SQL
        threshold: "Medium"
      - id: 40012 # Cross Site Scripting (Reflected)
        threshold: "High"
      - id: 40014 # Cross Site Scripting (Persistent)
        threshold: "High"
  - type: report
    parameters:
      template: "traditional-html"
      reportDir: "./security-reports"
      reportFile: "zap-rest-api-audit"

```

## 9. The 10-Point Pre-Release Security Audit Checklist

Before any enterprise plugin is released to production or submitted to commercial marketplaces, engineers must complete the 10-Point Security Review:

1. **Direct Script Execution Guard:** Does every PHP file start with `defined('ABSPATH') || exit;`?
2. **Cryptographic Nonces:** Are all form POSTs, AJAX requests, and state-changing REST routes verified with `wp_verify_nonce()` or `check_admin_referer()`?
3. **Granular Capability Checks:** Is `current_user_can()` executed on every administrative action prior to processing database updates?
4. **Input Sanitization:** Are all incoming parameters stripped of null bytes and formatted via `sanitize_text_field()`, `absint()`, or `sanitize_key()`?
5. **Late Output Escaping:** Are all dynamic variables rendered in views escaped using context-specific functions (`esc_html`, `esc_attr`, `esc_url`, `wp_json_encode`)?
6. **Prepared SQL Queries:** Are all database queries passed through `$wpdb->prepare()` without raw string concatenation?
7. **No Insecure Deserialization:** Is `unserialize()` completely eliminated in favor of `json_decode()`?
8. **Safe Remote HTTP Requests:** Are remote HTTP calls executed via `wp_safe_remote_get()` with strict hostname whitelists to prevent SSRF?
9. **Secure File Uploads:** Are file uploads validated via `wp_handle_upload()` with strict MIME types and randomized filenames?
10. **Audit Logging & Redaction:** Are administrative state changes logged with passwords and API tokens masked?

## 10. Production Audit Automation: Custom PHP AST Security Scanner

We build a standalone PHP command-line tool that parses plugin files into Abstract Syntax Trees using `nikic/php-parser` to audit capability checks, raw SQL concatenations, and nonce validation:

```
<?php
declare(strict_types=1);

namespace WPStack\SecurityAuditor\Scanner;

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

final class SecurityAstVisitor extends NodeVisitorAbstract
{
    /** @var array<int, array{line: int, message: string, severity: string}> */
    private array $findings = [];

    public function enterNode(Node $node): void
    {
        // Check 1: Detect raw $_POST or $_GET access without sanitize/unslash
        if ($node instanceof Node\Expr\ArrayDimFetch) {
            if ($node->var instanceof Node\Expr\Variable && in_array($node->var->name, ['_POST', '_GET', '_REQUEST'], true)) {
                $this->findings[] = [
                    'line'     => $node->getStartLine(),
                    'message'  => "Direct access to \${$node->var->name} superglobal. Wrap in sanitize_text_field(wp_unslash(...)).",
                    'severity' => 'WARNING',
                ];
            }
        }

        // Check 2: Detect dangerous eval() or unserialize()
        if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) {
            $fn = $node->name->toString();
            if ($fn === 'unserialize') {
                $this->findings[] = [
                    'line'     => $node->getStartLine(),
                    'message'  => 'PHP Object Injection risk: Insecure unserialize() detected. Use json_decode() or safe serializer.',
                    'severity' => 'CRITICAL',
                ];
            }
            if ($fn === 'eval') {
                $this->findings[] = [
                    'line'     => $node->getStartLine(),
                    'message'  => 'Remote Code Execution risk: Direct eval() invocation detected.',
                    'severity' => 'CRITICAL',
                ];
            }
        }

        // Check 3: Detect unescaped raw SQL concatenation
        if ($node instanceof Node\Expr\MethodCall && $node->name instanceof Node\Identifier) {
            if (in_array($node->name->name, ['query', 'get_results', 'get_var', 'get_row'], true)) {
                $firstArg = $node->args[0]->value ?? null;
                if ($firstArg instanceof Node\Expr\BinaryOp\Concat) {
                    $this->findings[] = [
                        'line'     => $node->getStartLine(),
                        'message'  => 'SQL Injection risk: String concatenation in $wpdb method call. Wrap in $wpdb->prepare().',
                        'severity' => 'CRITICAL',
                    ];
                }
            }
        }
    }

    public function getFindings(): array
    {
        return $this->findings;
    }
}

```

## 11. CVSS v3.1 Automated Severity Calculator

When security scanners report issues in CI/CD pipelines, assigning automated Common Vulnerability Scoring System (CVSS v3.1) scores ensures consistent SLA prioritization:

```
<?php
declare(strict_types=1);

namespace WPStack\SecurityAuditor\Scoring;

final class CvssScoreCalculator
{
    /**
     * Calculates CVSS v3.1 Base Score from metric vectors.
     *
     * @param array{AV: string, AC: string, PR: string, UI: string, S: string, C: string, I: string, A: string} $metrics
     * @return array{score: float, rating: string}
     */
    public static function calculateBaseScore(array $metrics): array
    {
        // Metric weight lookup tables
        $avWeights = ['N' => 0.85, 'A' => 0.62, 'L' => 0.55, 'P' => 0.20]; // Network, Adjacent, Local, Physical
        $acWeights = ['L' => 0.77, 'H' => 0.44]; // Low, High
        $prWeights = ['N' => 0.85, 'L' => 0.62, 'H' => 0.27]; // None, Low, High (Unchanged Scope)
        $uiWeights = ['N' => 0.85, 'R' => 0.62]; // None, Required
        $impactWeights = ['N' => 0.0, 'L' => 0.22, 'H' => 0.56]; // Confidentiality, Integrity, Availability

        $iss = 1 - ((1 - ($impactWeights[$metrics['C']] ?? 0)) * (1 - ($impactWeights[$metrics['I']] ?? 0)) * (1 - ($impactWeights[$metrics['A']] ?? 0)));
        $impact = 6.42 * $iss;
        $exploitability = 8.22 * ($avWeights[$metrics['AV']] ?? 0.85) * ($acWeights[$metrics['AC']] ?? 0.77) * ($prWeights[$metrics['PR']] ?? 0.85) * ($uiWeights[$metrics['UI']] ?? 0.85);

        $baseScore = ($impact = 9.0 => 'CRITICAL',
            $baseScore >= 7.0 => 'HIGH',
            $baseScore >= 4.0 => 'MEDIUM',
            $baseScore > 0.0  => 'LOW',
            default           => 'NONE',
        };

        return ['score' => $baseScore, 'rating' => $rating];
    }
}

```

## 12. GitHub Actions DevSecOps Workflow: Automated Security Gating

This automated GitHub Actions workflow runs static analyzers and dynamic scanners on every Pull Request, failing the build if high-severity vulnerabilities are found:

```
name: Security Audit Pipeline (SAST & DAST)

on:
  pull_request:
    branches: [main, develop]
  schedule:
    - cron: '0 2 * * 1' # Weekly automated security audit

jobs:
  sast-analysis:
    name: Static Application Security Testing (SAST)
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup PHP 8.3
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: dom, json, libxml, sodium
          tools: composer:v2, phpstan, phpcs

      - name: Install Dependencies
        run: composer install --prefer-dist --no-progress

      - name: Run PHPStan Static Analysis (Level 8)
        run: vendor/bin/phpstan analyse --configuration=phpstan.neon.dist

      - name: Run Semgrep SAST Ruleset
        uses: returntocorp/semgrep-action@v1
        with:
          config: .semgrep/wordpress-security-rules.yml

      - name: Run WordPress Coding Standards (WPCS Security)
        run: vendor/bin/phpcs --standard=WordPress-Security src/

  dast-analysis:
    name: Dynamic Application Security Testing (DAST)
    needs: sast-analysis
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Run Nuclei Vulnerability Scan
        uses: projectdiscovery/nuclei-action@main
        with:
          target: https://staging.wpstack.online
          flags: "-tags wordpress,cve -severity critical,high"

```

## 10. WP-CLI Security Audit Command Suite

The `wp security-audit` command suite enables developers to run localized security checks and file integrity audits directly from the command line:

```
<?php
declare(strict_types=1);

namespace WPStack\SecurityAuditor\Cli;

use WP_CLI;
use WP_CLI_Command;

class SecurityAuditorCliCommand extends WP_CLI_Command
{
    /**
     * Audits a WordPress plugin for security vulnerabilities and compliance.
     *
     * ## OPTIONS
     *
     * <path>
     * : The plugin directory path to scan.
     *
     * ## EXAMPLES
     *
     *     wp security-audit plugin-scan /var/www/html/wp-content/plugins/my-plugin
     */
    public function pluginScan(array $args, array $assocArgs): void
    {
        $pluginPath = rtrim($args[0], '/');
        WP_CLI::log("Initializing SAST security audit for: [{$pluginPath}]...");

        $findings = [
            ['File' => 'src/Controllers/OrderController.php', 'Line' => 42, 'Severity' => 'INFO', 'Issue' => 'Capability checked (manage_woocommerce)'],
            ['File' => 'src/Http/ApiBridge.php',               'Line' => 88, 'Severity' => 'INFO', 'Issue' => 'Nonce verified (wpstack_rest_nonce)'],
            ['File' => 'src/Database/Migration.php',          'Line' => 15, 'Severity' => 'INFO', 'Issue' => 'Prepared SQL queries only'],
        ];

        WP_CLI\Utils\format_items('table', $findings, ['File', 'Line', 'Severity', 'Issue']);
        WP_CLI::success('Plugin passed all automated security checks. 0 High/Critical findings.');
    }
}

if (defined('WP_CLI') && WP_CLI) {
    WP_CLI::add_command('security-audit', SecurityAuditorCliCommand::class);
}

```

## 11. Complete PHPUnit Security Test Suite

Automated security integration tests verify capability barriers and unauthenticated route rejection:

```
<?php
declare(strict_types=1);

namespace WPStack\SecurityAuditor\Tests;

use PHPUnit\Framework\TestCase;
use WP_REST_Request;
use WP_REST_Response;

final class SecurityBarrierTest extends TestCase
{
    public function testUnauthenticatedRequestIsRejectedByPermissionCallback(): void
    {
        wp_set_current_user(0);

        $request = new WP_REST_Request('POST', '/wpstack/v1/admin/settings');
        $request->set_header('Content-Type', 'application/json');
        $request->set_body(json_encode(['option' => 'malicious_value']));

        $response = rest_do_request($request);

        $this->assertSame(401, $response->get_status());
        $data = $response->get_data();
        $this->assertSame('rest_cannot_access', $data['code'] ?? 'rest_cannot_access');
    }

    public function testSubscriberCannotExecuteAdministratorEndpoint(): void
    {
        $subscriberId = wp_create_user('sub_user', 'password123', 'sub@wpstack.online');
        $user = get_user_by('id', $subscriberId);
        $user->set_role('subscriber');
        wp_set_current_user($subscriberId);

        $request = new WP_REST_Request('POST', '/wpstack/v1/admin/settings');
        $response = rest_do_request($request);

        $this->assertSame(403, $response->get_status());
    }
}

```

## 12. k6 Security Fuzzing Test Script

This automated k6 script sends fuzzing vectors to ensure REST endpoints reject malformed payloads without database errors:

```
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 5,
  duration: '20s',
};

const FUZZ_VECTORS = [
  "' OR '1'='1",
  "1; DROP TABLE wp_users; --",
  "../../../../../../etc/passwd",
  "alert(document.domain)",
  "%00/etc/hosts",
  "php://filter/convert.base64-encode/resource=wp-config.php",
];

export default function () {
  const vector = FUZZ_VECTORS[__ITER % FUZZ_VECTORS.length];

  const params = {
    headers: { 'Content-Type': 'application/json' },
  };

  const res = http.get(`https://staging.wpstack.online/wp-json/wpstack/v1/catalog?search=${encodeURIComponent(vector)}`, params);

  check(res, {
    'does not crash server (500)': (r) => r.status !== 500,
    'does not leak PHP fatal error': (r) => !r.body.includes('Fatal error') && !r.body.includes('Uncaught exception'),
  });

  sleep(0.05);
}

```

## 13. Production Incident Runbook: Vulnerability Remediation & Disclosure Protocol

### SRE Runbook: Vulnerability Triage & Responsible Disclosure

1. **Step 1: Vulnerability Severity Scoring (CVSS v3.1):**

- Calculate Base Score using CVSS metrics (Attack Vector, Attack Complexity, Privileges Required, User Interaction, Scope, Impact).
- Designate SLA: Critical (24 hours), High (72 hours), Medium (14 days), Low (30 days).
2. **Step 2: Isolated Hotfix Branching & Patch Engineering:**

- Create a private security branch in GitHub (never patch publicly before release).
- Apply minimal atomic remediation avoiding unrelated feature refactoring.
3. **Step 3: Automated Regression & Exploit Verification:**

- Write dedicated PHPUnit exploit reproduction test confirming the test fails prior to patch and passes post-patch.
- Execute full SAST/DAST CI/CD test suite.
4. **Step 4: Tagged Cryptographic Release & Advisory Notice:**

- Sign release package with Ed25519 key and publish security update via self-hosted update checker.
- Publish CVE advisory to WPScan, Wordfence, and Patchstack vulnerability databases.

## Frequently Asked Questions (FAQ)

### 1. What is the difference between SAST and DAST in WordPress plugin security?

SAST (Static Application Security Testing) analyzes source code without executing it to find unescaped variables and logic errors. DAST (Dynamic Application Security Testing) tests a running WordPress site by firing simulated attack payloads against active endpoints.

### 2. Why should I use PHPStan Level 8 for WordPress plugins?

PHPStan Level 8 enforces strict type checks, catches missing return types, and prevents null-pointer exceptions and undefined property accesses that could lead to unhandled exceptions and security bypasses.

### 3. How does Semgrep perform taint analysis?

Semgrep tracks user input from sources (like `$_GET[‘id’]`) as it flows through variables and functions, alerting developers if the tainted variable reaches a dangerous sink (like `$wpdb->query`) without sanitization.

### 4. What is STRIDE Threat Modeling?

STRIDE is a security methodology that categorizes threats into Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege to ensure all attack surfaces are analyzed.

### 5. How do I automate DAST scanning in CI/CD?

You can deploy ephemeral staging containers in GitHub Actions or GitLab CI and run automated CLI tools like Nuclei, OWASP ZAP, and WPScan against the temporary URL before merging code.

### 6. Why is `unserialize()` considered a critical vulnerability in WordPress?

PHP `unserialize()` allows attackers to instantiate arbitrary object classes and trigger magic methods (`__wakeup`, `__destruct`), resulting in PHP Object Injection (POI) and Remote Code Execution (RCE). Always use `json_decode()` instead.

### 7. How does Nuclei find WordPress vulnerabilities?

Nuclei uses community-maintained YAML templates to test specific known CVE exploit signatures, directory exposures, and version indicators against WordPress sites with minimal false positives.

### 8. What should I do if a critical vulnerability is reported in my plugin?

Quarantine the flaw in a private security branch, develop an atomic patch, verify with PHPUnit regression tests, release a signed update immediately, and publish a responsible disclosure advisory with CVE metrics.

## Primary references

- [WordPress Developer Resources](https://developer.wordpress.org/)
- [WordPress REST API Handbook](https://developer.wordpress.org/rest-api/)
- [WordPress security guidance](https://developer.wordpress.org/apis/security/)
