All skills
wordpress avatar

/wp-abilities-verify

@20324d2 official
by wordpresswordpress/agent-skills2.2k stars
327

Verify a WordPress plugin's Abilities API registrations: enumerate abilities, check that callback behavior matches each annotation's claim (the adversarial readonly-but-writes detection), validate permissions and schemas, and validate audit documents produced by wp-abilities-audit.

Use this Skill: https://skilld.dev/gh/wordpress/agent-skills/wp-abilities-verify

This session only. Nothing lands on disk.

referencesstatic-enumeration.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Static Enumeration

Enumerate a plugin's abilities from source, with no running environment. Static enumeration is necessarily best-effort — PHP's dynamism (variable indirection, runtime-conditional registration) means a complete inventory only comes from a live wp_get_abilities() call. When static and runtime inventories diverge, trust runtime; static drives the diff so the reviewer knows where to look.

Typical registration shape

wp_register_ability(
    '<plugin-slug>/<ability-name>',
    array(
        'label'               => __( '...', '<text-domain>' ),
        'description'         => __( '...', '<text-domain>' ),
        'category'            => '<category-slug>',
        'input_schema'        => array( /* ... */ ),
        'execute_callback'    => array( self::class, 'execute_my_ability' ),
        'permission_callback' => array( self::class, 'check_permission' ),
        'meta'                => array(
            'annotations' => array(
                'readonly'    => true,
                'destructive' => false,
                'idempotent'  => true,
            ),
            'show_in_rest' => true,
        ),
    )
);

Step 1 — find every registration call

grep -rn --include='*.php' 'wp_register_ability\s*(' <plugin-root>/

Zero hits → return a clear "no abilities registered" report per SKILL.md "Failure modes." Don't fabricate an empty inventory.

Step 2 — extract each ability name

The first argument is the ability name — usually a literal string. Real-world formatting splits the call across lines (the example above is itself multi-line), so single-line regexes miss common cases. Use a multi-line tool:

# ripgrep with --multiline + PCRE2 captures the name regardless of line break:
rg --multiline --pcre2 --type=php -n \
   "wp_register_ability\s*\(\s*['\"]([^'\"]+)['\"]" <plugin-root>/

# Fallbacks: pcregrep -M, perl -0777.

If the first argument is a variable or constant (wp_register_ability( $name, ... ), wp_register_ability( MyPlugin\NAME, ... )), trace it: a recent assignment in the same function or a class constant usually resolves; a name computed in a loop won't, in which case flag the limitation and recommend runtime mode for authoritative enumeration.

Step 3 — extract the annotation block

Annotations live at meta.annotations.{readonly,destructive,idempotent}. For each registration, read the array literal forward until the matching close. Common shapes:

  • Multi-line literal (most common).
  • Short-form one-liner.
  • Helper method ('annotations' => self::annotations_for_read()) — resolve the helper if it returns a literal; otherwise mark <unresolved> and run the adversarial check against the callback alone.
  • Class constant ('annotations' => self::READONLY_ANNOTATIONS) — resolve the constant.

Record ability_name → declared_annotations.

Step 4 — follow execute_callback to its body

execute_callback is one of:

'execute_callback' => array( self::class, 'execute_get_things' ),
'execute_callback' => array( My_Class::class, 'execute_get_things' ),
'execute_callback' => array( $this, 'execute_get_things' ),
'execute_callback' => 'my_plugin_execute_get_things',  // top-level function
'execute_callback' => function ( $input ) { /* ... */ },  // closure

Resolve the reference to its source location: file + start line + end line. The annotation-correctness, schema-lint, and permission checks all operate on that byte range.

Limits of static enumeration

Cases where the inventory is incomplete or ambiguous:

  • Variable-indirected names (foreach over a slug list).
  • Variable-indirected annotations (built from config).
  • Conditional registration (if ( feature_enabled() )).
  • Variable-indirected callbacks (array( $this, $callback_name )).

Record each in the report's "Static enumeration limitations" section and recommend a runtime-mode rerun for the authoritative inventory.

Output format

## Static inventory

Found <N> ability registrations across <M> files:

| Ability | Source file | Registration line | Callback file | Callback lines |
|---|---|---|---|---|
| myplugin/get-foo | src/Abilities.php | 42 | src/Abilities.php | 102-134 |
| myplugin/submit-bar | src/Abilities.php | 68 | src/Services/Bar.php | 58-91 |

### Limitations

- <ability>: annotations built dynamically in `annotations_for_read()`;
  recommend runtime mode for annotation cross-check.

Source: SKILL.md on GitHub

1 alert2mo3 checks · Risk HIGH
  • Gen Agent Trust Hub2mo

    This skill facilitates the verification of WordPress plugins but introduces a high-risk security vulnerability by instructing the agent to execute arbitrary shell commands defined within the target plugin's metadata files (AGENTS.md). This behavior allows a malicious repository to achieve remote code execution (RCE) on the agent's host system during the audit process. Additionally, the skill lacks sanitization for data ingested from audit documents used in environment seeding and execution.

  • Socket2mo

    No alerts

  • Snyk2mo

    Risk: LOW · No issues

Signed by skilld at 20324d2. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 3 months ago
Other metadata
compatibility
Targets WordPress 7.0+ plugins (PHP 7.4.0+). Requires a runnable environment (wp-env, docker-based dev stack, or equivalent) for runtime mode; static mode runs entirely from the plugin checkout with no env. Filesystem-based agent with bash + node.
  • Security
  • wordpress
  • php
  • abilities-api
  • verification
  • schema-validation
  • permissions
  • static-analysis

README badge

README badge for wordpress/agent-skills/wp-abilities-verify

Checks WordPress plugin Abilities API registrations for correctness: verifies readonly and destructive claims against callback behavior (catching writes hidden in readonly abilities), validates permission gates and input schemas, and confirms audit documents. Runs in static mode (source inspection only) or runtime mode (live environment execution with permission roundtrip and idempotency checks).

Generated from the current SKILL.md.

Does this skill check if a readonly ability actually writes to the database?
Yes. The adversarial correctness check reads callback bodies and flags readonly abilities that perform writes via $wpdb, update_option, or non-GET delegates—a critical security issue because agents plan actions based on ability annotations.
Can I run this skill without a WordPress environment?
Yes. Static mode runs entirely from the plugin checkout with no environment needed. Runtime mode requires a runnable environment (wp-env, Docker, or equivalent) and catches additional issues like permission roundtrips and idempotency regressions that static mode cannot.
What does this skill require as input?
The plugin checkout path, the mode (static or runtime), and optionally an audit document path and report output path. For runtime mode, you must also provide the env-up command from the plugin's AGENTS.md.
Can this skill validate audit documents?
Yes. If you provide an audit document path, the skill validates it against the canonical schema, checks for missing fields, and cross-references registered abilities against the audit's declared gates.
What output does this skill produce?
A structured markdown report with tables showing each ability's annotation correctness, permission gates, schema lints, and error-code vocabulary, ending with an overall PASS, WARN, or FAIL verdict.

Generated from the current SKILL.md. These answers refresh after source changes.