From 25def86bc4d18c1cd062c670acc7f7473e67fe74 Mon Sep 17 00:00:00 2001 From: Felipe Zipitria Date: Mon, 13 Apr 2026 09:29:40 -0300 Subject: [PATCH 1/3] feat: add plugin schema Signed-off-by: Felipe Zipitria --- docs/plugin-descriptor-schema.md | 183 +++++++++++ examples/body-decompress-plugin.yaml | 56 ++++ examples/fake-bot-plugin.yaml | 57 ++++ .../wordpress-rule-exclusions-plugin.yaml | 50 +++ plugin-schema.json | 284 ++++++++++++++++++ 5 files changed, 630 insertions(+) create mode 100644 docs/plugin-descriptor-schema.md create mode 100644 examples/body-decompress-plugin.yaml create mode 100644 examples/fake-bot-plugin.yaml create mode 100644 examples/wordpress-rule-exclusions-plugin.yaml create mode 100644 plugin-schema.json diff --git a/docs/plugin-descriptor-schema.md b/docs/plugin-descriptor-schema.md new file mode 100644 index 0000000..fe7fede --- /dev/null +++ b/docs/plugin-descriptor-schema.md @@ -0,0 +1,183 @@ +# CRS Plugin Descriptor Schema + +## Overview + +The plugin descriptor schema defines a `plugin.yaml` file that lives in the root of each CRS plugin repository. It provides machine-readable metadata about the plugin, its configuration variables, and compatibility requirements. + +The plugin registry aggregates these descriptors to generate the registry table, and downstream tooling (such as a CRS configurator) can parse them to build preconfigured CRS deployments based on plugin selection. + +## Goals + +- Allow each plugin repository to be the single source of truth for its own metadata. +- Enable automated tooling to discover, validate, and configure plugins. +- Replace manual registry table maintenance with generated output. +- Provide enough information for a configurator to present a UI for plugin selection and variable tuning. + +## Schema Structure + +The schema is defined as JSON Schema (2020-12) in [`plugin-schema.json`](../plugin-schema.json). A `plugin.yaml` file has the following top-level sections: + +### `schema_version` + +**Required.** Integer, currently fixed at `1`. + +Allows future evolution of the schema without breaking existing parsers. Tooling should check this field and handle unknown versions gracefully. + +### `plugin` + +**Required.** Plugin identity and metadata. + +| Field | Required | Description | +|--------------------|----------|-------------| +| `name` | yes | Plugin name following the CRS convention: `-plugin`. Validated by regex. | +| `description` | yes | One-line summary of the plugin's purpose. | +| `long_description` | no | Multi-line extended description for documentation and UI display. | +| `version` | yes | Semantic version (`MAJOR.MINOR.PATCH`). | +| `type` | yes | `official` (coreruleset-maintained) or `3rd-party`. | +| `category` | no | Functional category: `rule-exclusion`, `detection`, `protection`, `utility`, `logging`, or `performance`. | +| `status` | yes | Maturity level: `tested`, `being-tested`, `untested`, or `draft`. | +| `license` | yes | SPDX license identifier (e.g., `Apache-2.0`, `GPL-2.0-only`). | +| `authors` | yes | List of author objects with `name` (required), `email` and `url` (optional). | +| `repository` | yes | URL of the plugin source repository. | +| `homepage` | no | URL to documentation or project site. | +| `keywords` | no | Tags for discovery and categorization. | + +### `rule_id_range` + +**Required.** The registered rule ID block from the plugin registry. + +| Field | Description | +|---------|-------------| +| `start` | First rule ID in the allocated range (integer, 9500000-9999999). | +| `end` | Last rule ID in the allocated range (integer, 9500000-9999999). | + +Plugins typically receive a 1,000-ID range. The schema validates that values fall within the CRS plugin namespace (9,500,000 - 9,999,999). + +### `compatibility` + +**Optional.** WAF engine and CRS version requirements. + +| Field | Description | +|---------------|-------------| +| `crs_version` | Version constraint string (e.g., `>=4.0.0`). | +| `engines` | List of compatible WAF engines: `modsecurity2`, `modsecurity3`, `coraza`. | + +When omitted, no compatibility constraints are assumed. Tooling should treat missing engines as "compatible with all". + +### `dependencies` + +**Optional.** List of other CRS plugins this plugin depends on. + +Each entry has: +| Field | Required | Description | +|-----------|----------|-------------| +| `name` | yes | Name of the required plugin. | +| `version` | no | Version constraint for the dependency. | + +Most plugins have no dependencies. This field exists for plugins that build on top of other plugins. + +### `configuration` + +**Required.** The key section for automated tooling. Describes the config file and all user-tunable variables. + +| Field | Description | +|-------------|-------------| +| `file` | Path to the `-config.conf` file relative to the plugin root. | +| `variables` | List of transaction variable definitions (see below). | + +#### Variable Definition + +Each entry in `variables` describes a single `tx.*` variable from the config file: + +| Field | Required | Description | +|------------------|----------|-------------| +| `name` | yes | Full ModSecurity variable name (e.g., `tx.myplugin_enabled`). | +| `type` | yes | Data type: `boolean`, `integer`, `string`, or `enum`. | +| `default` | no | Default value if not set by the user. | +| `description` | yes | Human-readable explanation of the variable. | +| `required` | no | Whether the user must explicitly set this variable (default: `false`). | +| `allowed_values` | no | List of valid values when type is `enum`. | +| `example` | no | Example value for documentation. | +| `min` | no | Minimum value when type is `integer`. | +| `max` | no | Maximum value when type is `integer`. | + +#### Variable Types + +The four types cover all patterns found across existing CRS plugins: + +- **`boolean`** — Enable/disable flags. Every plugin has at least `tx._enabled`. Values: `0` (disabled) or `1` (enabled). +- **`integer`** — Numeric thresholds and limits (e.g., `tx.body-decompress-plugin_max_data_size_bytes`). Supports `min`/`max` constraints. +- **`string`** — Freeform text values (e.g., `tx.google-oauth2-plugin_whitelisted_parameters`). The `example` field helps users understand the expected format. +- **`enum`** — Constrained choices (e.g., `tx.phpmyadmin-rule-exclusions-plugin_url_format` with values `v51`, `v52`). Must include `allowed_values`. + +### `files` + +**Optional.** Maps the standard plugin files for tooling convenience. + +| Field | Description | +|----------|-------------| +| `config` | Path to the configuration file. | +| `before` | Path to the before-CRS rules file. | +| `after` | Path to the after-CRS rules file. | + +These follow the CRS naming convention: `plugins/-config.conf`, `plugins/-before.conf`, `plugins/-after.conf`. Some plugins use a variant like `-config-before.conf`. + +## Design Decisions + +### Why YAML over JSON or TOML? + +YAML supports comments, multi-line strings, and is widely used in the CRS ecosystem (test files use FTW YAML format). Plugin authors already work with YAML for regression tests, so it is familiar. + +### Why `schema_version` as a top-level field? + +Embedding versioning in the schema itself allows parsers to detect incompatible changes before attempting to parse. This is simpler than relying on file naming or external version tracking. + +### Why typed variables with constraints? + +A configurator project needs to know more than just variable names and defaults. By declaring types, allowed values, and numeric bounds, tooling can: + +1. Render appropriate input widgets (toggle for boolean, slider for bounded integer, dropdown for enum). +2. Validate user input before generating configuration. +3. Generate documentation automatically. + +### Why is `configuration` required? + +Every CRS plugin has at least one configuration variable (`tx._enabled`). Making this section required ensures consistency and guarantees that tooling always has something to work with, even for minimal rule-exclusion plugins. + +### Why separate `rule_id_range` from `plugin`? + +The rule ID range is a registry-level concern (namespace coordination), not a plugin identity attribute. Keeping it separate makes it clear that this range is allocated by the registry and should not be changed unilaterally. + +### Category taxonomy + +The six categories cover the existing plugin landscape: + +| Category | Examples | +|------------------|----------| +| `rule-exclusion` | wordpress, drupal, nextcloud, phpbb, cpanel | +| `detection` | fake-bot | +| `protection` | dos-protection | +| `utility` | template, body-decompress, auto-decoding | +| `logging` | database-logging, false-positive-report | +| `performance` | performance-plugin | + +New categories can be added to the schema's enum as the ecosystem grows. + +## Configurator Integration + +The primary consumer of `plugin.yaml` files is a configurator tool that: + +1. **Fetches** `plugin.yaml` from each registered plugin repository. +2. **Presents** available plugins grouped by category, with descriptions and compatibility info. +3. **Collects** user choices: which plugins to enable and how to tune their variables. +4. **Validates** input against variable constraints (type, min/max, allowed_values). +5. **Resolves** dependencies between plugins. +6. **Generates** a complete CRS configuration with all selected plugins and their tuned `tx.*` variables. + +## Rollout Plan + +1. Add `plugin.yaml` to `coreruleset/template-plugin` as the reference implementation. +2. Get feedback from the CRS team and iterate on the schema. +3. Once the schema is accepted, create PRs adding `plugin.yaml` to all registered plugins. +4. Update the registry to validate incoming plugin registrations against the schema. +5. Build the configurator project. diff --git a/examples/body-decompress-plugin.yaml b/examples/body-decompress-plugin.yaml new file mode 100644 index 0000000..2d7aea0 --- /dev/null +++ b/examples/body-decompress-plugin.yaml @@ -0,0 +1,56 @@ +# OWASP CRS Plugin Descriptor +# https://github.com/coreruleset/plugin-registry + +schema_version: 1 + +plugin: + name: "body-decompress-plugin" + description: "On-the-fly decompression of response bodies for CRS inspection" + long_description: | + Decompresses gzip/deflate compressed HTTP response bodies so that CRS + response rules can inspect the actual content. Without this plugin, + compressed responses bypass response body inspection rules. + version: "1.0.0" + type: "official" + category: "utility" + status: "being-tested" + license: "Apache-2.0" + authors: + - name: "OWASP CRS Team" + url: "https://coreruleset.org" + repository: "https://github.com/coreruleset/body-decompress-plugin" + keywords: + - "decompression" + - "response-body" + - "gzip" + +rule_id_range: + start: 9503000 + end: 9503999 + +compatibility: + crs_version: ">=4.0.0" + engines: + - "modsecurity2" + - "modsecurity3" + +configuration: + file: "plugins/body-decompress-config-before.conf" + variables: + - name: "tx.body-decompress-plugin_enable" + type: "boolean" + default: 1 + description: "Enable or disable response body decompression" + + - name: "tx.body-decompress-plugin_max_data_size_bytes" + type: "integer" + default: 102400 + description: "Maximum decompressed data size in bytes" + min: 1024 + max: 10485760 + example: 102400 + +files: + config: "plugins/body-decompress-config-before.conf" + before: "plugins/body-decompress-before.conf" + after: "plugins/body-decompress-after.conf" diff --git a/examples/fake-bot-plugin.yaml b/examples/fake-bot-plugin.yaml new file mode 100644 index 0000000..c3b3cd8 --- /dev/null +++ b/examples/fake-bot-plugin.yaml @@ -0,0 +1,57 @@ +# OWASP CRS Plugin Descriptor +# https://github.com/coreruleset/plugin-registry + +schema_version: 1 + +plugin: + name: "fake-bot-plugin" + description: "Detects fake search engine bots by verifying crawler IP addresses" + long_description: | + Identifies HTTP requests that claim to be from known search engine crawlers + (Googlebot, Bingbot, etc.) but originate from IP addresses not belonging to + those search engines. Helps protect against scraping and abuse from clients + impersonating legitimate bots. + version: "1.0.0" + type: "official" + category: "detection" + status: "tested" + license: "Apache-2.0" + authors: + - name: "OWASP CRS Team" + url: "https://coreruleset.org" + repository: "https://github.com/coreruleset/fake-bot-plugin" + homepage: "https://coreruleset.org/docs/plugins/" + keywords: + - "bot-detection" + - "fake-bot" + - "crawler" + - "security" + +rule_id_range: + start: 9504000 + end: 9504999 + +compatibility: + crs_version: ">=4.0.0" + engines: + - "modsecurity2" + - "modsecurity3" + - "coraza" + +configuration: + file: "plugins/fake-bot-config.conf" + variables: + - name: "tx.fake-bot-plugin_enabled" + type: "boolean" + default: 1 + description: "Enable or disable the fake bot detection plugin (0 to disable)" + + - name: "tx.fake-bot-plugin_whitelist_broken_apple_devices" + type: "boolean" + default: 0 + description: "Whitelist broken Apple devices that incorrectly identify as Googlebot" + +files: + config: "plugins/fake-bot-config.conf" + before: "plugins/fake-bot-before.conf" + after: "plugins/fake-bot-after.conf" diff --git a/examples/wordpress-rule-exclusions-plugin.yaml b/examples/wordpress-rule-exclusions-plugin.yaml new file mode 100644 index 0000000..887b845 --- /dev/null +++ b/examples/wordpress-rule-exclusions-plugin.yaml @@ -0,0 +1,50 @@ +# OWASP CRS Plugin Descriptor +# https://github.com/coreruleset/plugin-registry + +schema_version: 1 + +plugin: + name: "wordpress-rule-exclusions-plugin" + description: "CRS rule exclusions for WordPress applications" + long_description: | + Provides targeted rule exclusions to eliminate false positives when + running OWASP CRS in front of WordPress sites. Covers the WordPress + admin panel, REST API, and common plugin/theme patterns. + version: "1.1.0" + type: "official" + category: "rule-exclusion" + status: "tested" + license: "Apache-2.0" + authors: + - name: "OWASP CRS Team" + url: "https://coreruleset.org" + repository: "https://github.com/coreruleset/wordpress-rule-exclusions-plugin" + keywords: + - "wordpress" + - "cms" + - "rule-exclusion" + - "false-positive" + +rule_id_range: + start: 9507000 + end: 9507999 + +compatibility: + crs_version: ">=4.0.0" + engines: + - "modsecurity2" + - "modsecurity3" + - "coraza" + +configuration: + file: "plugins/wordpress-rule-exclusions-config.conf" + variables: + - name: "tx.wordpress-rule-exclusions-plugin_enabled" + type: "boolean" + default: 1 + description: "Enable or disable WordPress rule exclusions (0 to disable)" + +files: + config: "plugins/wordpress-rule-exclusions-config.conf" + before: "plugins/wordpress-rule-exclusions-before.conf" + after: "plugins/wordpress-rule-exclusions-after.conf" diff --git a/plugin-schema.json b/plugin-schema.json new file mode 100644 index 0000000..60147e4 --- /dev/null +++ b/plugin-schema.json @@ -0,0 +1,284 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/coreruleset/plugin-registry/main/plugin-schema.json", + "title": "CRS Plugin Descriptor", + "description": "Schema for OWASP CRS plugin metadata files (plugin.yaml)", + "type": "object", + "required": [ + "schema_version", + "plugin", + "rule_id_range", + "configuration" + ], + "additionalProperties": false, + "properties": { + "schema_version": { + "type": "integer", + "const": 1, + "description": "Version of the plugin descriptor schema" + }, + "plugin": { + "type": "object", + "required": [ + "name", + "description", + "version", + "type", + "status", + "license", + "authors", + "repository" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z0-9]+(-[a-z0-9]+)*-plugin$", + "description": "Plugin name using the CRS naming convention: -plugin" + }, + "description": { + "type": "string", + "description": "Short human-readable description of what the plugin does" + }, + "long_description": { + "type": "string", + "description": "Extended description with details about functionality and use cases" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Semantic version of the plugin" + }, + "type": { + "type": "string", + "enum": [ + "official", + "3rd-party" + ], + "description": "Whether the plugin is maintained by the CRS team or a third party" + }, + "category": { + "type": "string", + "enum": [ + "rule-exclusion", + "detection", + "protection", + "utility", + "logging", + "performance" + ], + "description": "Functional category of the plugin" + }, + "status": { + "type": "string", + "enum": [ + "tested", + "being-tested", + "untested", + "draft" + ], + "description": "Current testing/maturity status" + }, + "license": { + "type": "string", + "description": "SPDX license identifier (e.g., Apache-2.0, GPL-2.0-only)" + }, + "authors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "repository": { + "type": "string", + "format": "uri", + "description": "URL of the plugin source repository" + }, + "homepage": { + "type": "string", + "format": "uri", + "description": "URL of the plugin homepage or documentation site" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tags for discovery and categorization" + } + } + }, + "rule_id_range": { + "type": "object", + "required": [ + "start", + "end" + ], + "additionalProperties": false, + "properties": { + "start": { + "type": "integer", + "minimum": 9500000, + "maximum": 9999999, + "description": "First rule ID in the allocated range" + }, + "end": { + "type": "integer", + "minimum": 9500000, + "maximum": 9999999, + "description": "Last rule ID in the allocated range" + } + } + }, + "compatibility": { + "type": "object", + "additionalProperties": false, + "properties": { + "crs_version": { + "type": "string", + "description": "CRS version constraint (e.g., '>=4.0.0')" + }, + "engines": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "modsecurity2", + "modsecurity3", + "coraza" + ] + }, + "description": "Compatible WAF engines" + } + } + }, + "dependencies": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the required plugin" + }, + "version": { + "type": "string", + "description": "Version constraint for the dependency" + } + } + }, + "description": "Other CRS plugins this plugin depends on" + }, + "configuration": { + "type": "object", + "required": [ + "file", + "variables" + ], + "additionalProperties": false, + "properties": { + "file": { + "type": "string", + "pattern": "^plugins/.+-config(-before)?\\.conf$", + "description": "Path to the configuration file relative to the plugin root" + }, + "variables": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "type", + "description" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "pattern": "^tx\\..+", + "description": "Full ModSecurity transaction variable name (e.g., tx.myplugin_enabled)" + }, + "type": { + "type": "string", + "enum": [ + "boolean", + "integer", + "string", + "enum" + ], + "description": "Data type of the variable" + }, + "default": { + "description": "Default value if not explicitly set by the user" + }, + "description": { + "type": "string", + "description": "Human-readable explanation of the variable's purpose" + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether this variable must be set for the plugin to function" + }, + "allowed_values": { + "type": "array", + "description": "List of allowed values when type is 'enum'" + }, + "example": { + "description": "Example value for documentation purposes" + }, + "min": { + "type": "number", + "description": "Minimum value when type is 'integer'" + }, + "max": { + "type": "number", + "description": "Maximum value when type is 'integer'" + } + } + } + } + } + }, + "files": { + "type": "object", + "additionalProperties": false, + "properties": { + "config": { + "type": "string", + "description": "Configuration file path (relative to plugin root)" + }, + "before": { + "type": "string", + "description": "Before-CRS rules file path" + }, + "after": { + "type": "string", + "description": "After-CRS rules file path" + } + } + } + } +} From bc8278197efe1b48de038fe41798bbc2215248f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:51:18 +0000 Subject: [PATCH 2/3] fix: address schema review comments - constraints, types, and naming Agent-Logs-Url: https://github.com/coreruleset/plugin-registry/sessions/e5bb0215-3e42-4fc3-9912-84b7af7f99c7 Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --- docs/plugin-descriptor-schema.md | 2 +- examples/body-decompress-plugin.yaml | 2 +- plugin-schema.json | 90 +++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/docs/plugin-descriptor-schema.md b/docs/plugin-descriptor-schema.md index fe7fede..b5cabee 100644 --- a/docs/plugin-descriptor-schema.md +++ b/docs/plugin-descriptor-schema.md @@ -105,7 +105,7 @@ Each entry in `variables` describes a single `tx.*` variable from the config fil The four types cover all patterns found across existing CRS plugins: -- **`boolean`** — Enable/disable flags. Every plugin has at least `tx._enabled`. Values: `0` (disabled) or `1` (enabled). +- **`boolean`** — Enable/disable flags. Every plugin has at least `tx._enabled`. Values are integers: `0` (disabled) or `1` (enabled), following the ModSecurity convention for boolean flags. - **`integer`** — Numeric thresholds and limits (e.g., `tx.body-decompress-plugin_max_data_size_bytes`). Supports `min`/`max` constraints. - **`string`** — Freeform text values (e.g., `tx.google-oauth2-plugin_whitelisted_parameters`). The `example` field helps users understand the expected format. - **`enum`** — Constrained choices (e.g., `tx.phpmyadmin-rule-exclusions-plugin_url_format` with values `v51`, `v52`). Must include `allowed_values`. diff --git a/examples/body-decompress-plugin.yaml b/examples/body-decompress-plugin.yaml index 2d7aea0..b697856 100644 --- a/examples/body-decompress-plugin.yaml +++ b/examples/body-decompress-plugin.yaml @@ -37,7 +37,7 @@ compatibility: configuration: file: "plugins/body-decompress-config-before.conf" variables: - - name: "tx.body-decompress-plugin_enable" + - name: "tx.body-decompress-plugin_enabled" type: "boolean" default: 1 description: "Enable or disable response body decompression" diff --git a/plugin-schema.json b/plugin-schema.json index 60147e4..b590adc 100644 --- a/plugin-schema.json +++ b/plugin-schema.json @@ -206,6 +206,7 @@ }, "variables": { "type": "array", + "minItems": 1, "items": { "type": "object", "required": [ @@ -244,20 +245,103 @@ }, "allowed_values": { "type": "array", + "items": { + "type": "string" + }, "description": "List of allowed values when type is 'enum'" }, "example": { "description": "Example value for documentation purposes" }, "min": { - "type": "number", + "type": "integer", "description": "Minimum value when type is 'integer'" }, "max": { - "type": "number", + "type": "integer", "description": "Maximum value when type is 'integer'" } - } + }, + "allOf": [ + { + "if": { + "properties": { "type": { "const": "boolean" } }, + "required": ["type"] + }, + "then": { + "properties": { + "default": { "type": "integer", "enum": [0, 1] }, + "example": { "type": "integer", "enum": [0, 1] } + } + } + }, + { + "if": { + "properties": { "type": { "const": "integer" } }, + "required": ["type"] + }, + "then": { + "properties": { + "default": { "type": "integer" }, + "example": { "type": "integer" } + } + } + }, + { + "if": { + "properties": { "type": { "const": "string" } }, + "required": ["type"] + }, + "then": { + "properties": { + "default": { "type": "string" }, + "example": { "type": "string" } + } + } + }, + { + "if": { + "properties": { "type": { "const": "enum" } }, + "required": ["type"] + }, + "then": { + "required": ["allowed_values"], + "properties": { + "default": { "type": "string" }, + "example": { "type": "string" } + } + } + }, + { + "if": { + "not": { + "properties": { "type": { "const": "integer" } }, + "required": ["type"] + } + }, + "then": { + "not": { + "anyOf": [ + { "required": ["min"] }, + { "required": ["max"] } + ] + } + } + }, + { + "if": { + "not": { + "properties": { "type": { "const": "enum" } }, + "required": ["type"] + } + }, + "then": { + "not": { + "required": ["allowed_values"] + } + } + } + ] } } } From e9ba83f1a550a3d12a45207cfe11fbe6db460499 Mon Sep 17 00:00:00 2001 From: Felipe Zipitria Date: Tue, 14 Apr 2026 18:21:40 -0300 Subject: [PATCH 3/3] feat: remove version field from plugin schema Version is derived from GitHub release tags at query time. Embedding it in plugin.yaml would inevitably drift as developers forget to update it. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/plugin-descriptor-schema.md | 5 +- examples/body-decompress-plugin.yaml | 1 - examples/fake-bot-plugin.yaml | 1 - .../wordpress-rule-exclusions-plugin.yaml | 1 - plugin-schema.json | 6 -- plugin.yaml.template | 86 +++++++++++++++++++ 6 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 plugin.yaml.template diff --git a/docs/plugin-descriptor-schema.md b/docs/plugin-descriptor-schema.md index b5cabee..38f9dae 100644 --- a/docs/plugin-descriptor-schema.md +++ b/docs/plugin-descriptor-schema.md @@ -32,7 +32,6 @@ Allows future evolution of the schema without breaking existing parsers. Tooling | `name` | yes | Plugin name following the CRS convention: `-plugin`. Validated by regex. | | `description` | yes | One-line summary of the plugin's purpose. | | `long_description` | no | Multi-line extended description for documentation and UI display. | -| `version` | yes | Semantic version (`MAJOR.MINOR.PATCH`). | | `type` | yes | `official` (coreruleset-maintained) or `3rd-party`. | | `category` | no | Functional category: `rule-exclusion`, `detection`, `protection`, `utility`, `logging`, or `performance`. | | `status` | yes | Maturity level: `tested`, `being-tested`, `untested`, or `draft`. | @@ -144,6 +143,10 @@ A configurator project needs to know more than just variable names and defaults. Every CRS plugin has at least one configuration variable (`tx._enabled`). Making this section required ensures consistency and guarantees that tooling always has something to work with, even for minimal rule-exclusion plugins. +### Why no `version` field? + +The plugin version is derived from GitHub release tags at query time. Embedding a version in `plugin.yaml` would inevitably drift because developers forget to bump it. Tooling should fetch the latest release tag from the repository URL instead. This keeps `plugin.yaml` as a static descriptor that rarely needs updating. + ### Why separate `rule_id_range` from `plugin`? The rule ID range is a registry-level concern (namespace coordination), not a plugin identity attribute. Keeping it separate makes it clear that this range is allocated by the registry and should not be changed unilaterally. diff --git a/examples/body-decompress-plugin.yaml b/examples/body-decompress-plugin.yaml index b697856..dc924a3 100644 --- a/examples/body-decompress-plugin.yaml +++ b/examples/body-decompress-plugin.yaml @@ -10,7 +10,6 @@ plugin: Decompresses gzip/deflate compressed HTTP response bodies so that CRS response rules can inspect the actual content. Without this plugin, compressed responses bypass response body inspection rules. - version: "1.0.0" type: "official" category: "utility" status: "being-tested" diff --git a/examples/fake-bot-plugin.yaml b/examples/fake-bot-plugin.yaml index c3b3cd8..61efae4 100644 --- a/examples/fake-bot-plugin.yaml +++ b/examples/fake-bot-plugin.yaml @@ -11,7 +11,6 @@ plugin: (Googlebot, Bingbot, etc.) but originate from IP addresses not belonging to those search engines. Helps protect against scraping and abuse from clients impersonating legitimate bots. - version: "1.0.0" type: "official" category: "detection" status: "tested" diff --git a/examples/wordpress-rule-exclusions-plugin.yaml b/examples/wordpress-rule-exclusions-plugin.yaml index 887b845..5494cf1 100644 --- a/examples/wordpress-rule-exclusions-plugin.yaml +++ b/examples/wordpress-rule-exclusions-plugin.yaml @@ -10,7 +10,6 @@ plugin: Provides targeted rule exclusions to eliminate false positives when running OWASP CRS in front of WordPress sites. Covers the WordPress admin panel, REST API, and common plugin/theme patterns. - version: "1.1.0" type: "official" category: "rule-exclusion" status: "tested" diff --git a/plugin-schema.json b/plugin-schema.json index b590adc..dc8c5ae 100644 --- a/plugin-schema.json +++ b/plugin-schema.json @@ -22,7 +22,6 @@ "required": [ "name", "description", - "version", "type", "status", "license", @@ -44,11 +43,6 @@ "type": "string", "description": "Extended description with details about functionality and use cases" }, - "version": { - "type": "string", - "pattern": "^\\d+\\.\\d+\\.\\d+$", - "description": "Semantic version of the plugin" - }, "type": { "type": "string", "enum": [ diff --git a/plugin.yaml.template b/plugin.yaml.template new file mode 100644 index 0000000..1031ea4 --- /dev/null +++ b/plugin.yaml.template @@ -0,0 +1,86 @@ +# OWASP CRS Plugin Descriptor +# Copy this file as 'plugin.yaml' in your plugin repository root. +# See plugin-schema.json for the full JSON Schema definition. +# Docs: https://github.com/coreruleset/plugin-registry + +schema_version: 1 + +plugin: + # Plugin name must follow the pattern: -plugin + name: "template-plugin" + description: "Short description of what the plugin does" + long_description: | + Extended description explaining the plugin's purpose, how it works, + and when users should consider enabling it. This field supports + multi-line text. + # "official" for coreruleset-maintained, "3rd-party" otherwise + type: "official" + # Category: rule-exclusion | detection | protection | utility | logging | performance + category: "utility" + # Status: tested | being-tested | untested | draft + status: "untested" + license: "Apache-2.0" + authors: + - name: "Your Name" + # email: "you@example.com" # optional + # url: "https://example.com" # optional + repository: "https://github.com/coreruleset/template-plugin" + # homepage: "https://example.com/docs" # optional + keywords: + - "example" + - "template" + +# Registered rule ID range from the plugin registry +rule_id_range: + start: 9500000 + end: 9500999 + +# WAF engine and CRS version compatibility (optional) +compatibility: + crs_version: ">=4.0.0" + engines: + - "modsecurity2" + - "modsecurity3" + - "coraza" + +# Plugin dependencies (optional, omit if none) +# dependencies: +# - name: "other-plugin" +# version: ">=1.0.0" + +# Configuration variables exposed by the plugin +configuration: + file: "plugins/template-config.conf" + variables: + - name: "tx.template-plugin_enabled" + type: "boolean" + default: 1 + description: "Enable or disable the plugin (0 to disable)" + required: false + + # Add your plugin-specific variables below. Examples: + # + # - name: "tx.template-plugin_threshold" + # type: "integer" + # default: 100 + # description: "Detection threshold" + # min: 1 + # max: 1000 + # + # - name: "tx.template-plugin_mode" + # type: "enum" + # default: "block" + # description: "Operating mode" + # allowed_values: ["detect", "block"] + # + # - name: "tx.template-plugin_whitelist" + # type: "string" + # default: "" + # description: "Space-separated list of whitelisted values" + # example: "/value1/ /value2/" + +# Plugin rule files (optional, helps tooling locate files) +files: + config: "plugins/template-config.conf" + before: "plugins/template-before.conf" + after: "plugins/template-after.conf"