nginx-lint-plugin
Plugin SDK for building custom nginx-lint rules as WASM plugins.
Overview
This crate provides everything needed to create lint rules for nginx configuration files. Plugins are compiled to WASM and loaded by the nginx-lint host at runtime.
Quick Start
Create a new plugin project:
Add dependencies to Cargo.toml:
[]
= ["cdylib", "rlib"]
[]
= "0.2"
= { = "1", = ["derive"] }
= "1"
[]
= ["wit-export"]
= ["nginx-lint-plugin/wit-export"]
Implement the plugin in src/lib.rs:
use *;
;
export_component_plugin!;
Build for WASM:
Key Concepts
Plugin Trait
Every plugin must implement [Plugin], which requires two methods:
spec()- Returns metadata about the rule (name, category, description, examples)check()- Inspects the parsed nginx config and returns lint errors
Config Traversal
The SDK provides two ways to iterate over directives:
// Simple iteration over all directives
for directive in config.all_directives
// Context-aware iteration (knows parent blocks)
for ctx in config.all_directives_with_context
Error Reporting
Use ErrorBuilder (created via PluginSpec::error_builder()) to create errors:
let err = self.spec.error_builder;
// Warning at a directive's location
err.warning_at;
// Error at a specific line/column
err.error;
Autofix Support
Attach fixes to errors for automatic correction:
// Replace a directive
err.warning_at
.with_fix;
// Delete a line
err.warning_at
.with_fix;
// Insert after a directive
err.warning_at
.with_fix;
Include Context
When nginx-lint processes include directives, included files receive context about where they were included from. Use ConfigExt methods to check this:
use *;
// Check if file is included from within http context
if config.is_included_from_http
// Check if inside http > server context
if config.is_included_from_http_server
Performance: Reading Only What Your Rule Needs
By default, check() gets the entire parsed config, and the host has to send every directive across the WASM boundary and the SDK has to reconstruct the whole tree — even if your rule only reads one or two directive names. For large config files this dominates the per-check cost.
If your rule only ever inspects a fixed, known set of directive names, declare them with relevant_directives():
The host then sends a config pruned to just those directives (plus the ancestor blocks needed for is_inside() to keep working), instead of the whole file. This is purely additive: omitting it (the default) behaves exactly as before, and it never changes what your rule can see — check() still receives a normal Config with all_directives_with_context() etc. working the same way.
One important exception: if your rule warns when a directive is missing inside some block (e.g. "this http block has no server_tokens"), you must include that enclosing block's own name ("http") in the list, not just the directive you're checking for. Otherwise a block with none of the listed directives inside it has nothing to keep it in the pruned config, and the host drops it entirely — along with the evidence your rule needs to report the block exists but is missing something. If your rule only reports on directives it finds (the common case), you don't need to list any ancestor block names — a matched directive's ancestors are always kept automatically so is_inside() and similar checks keep working.
Don't declare this if check() reads comments or blank lines (ConfigItem::Comment/ConfigItem::BlankLine): the pruned config never includes them, regardless of relevant_directives().
Testing
The SDK provides PluginTestRunner and TestCase for testing plugins:
Fixture Directory Structure
tests/fixtures/
└── 001_basic/
├── error/nginx.conf # Config that should trigger errors
└── expected/nginx.conf # Config after applying fixes
Modules
| Module | Description |
|---|---|
types |
Core types: Plugin, PluginSpec, LintError, Fix, Config extensions |
helpers |
Utility functions: is_domain_name(), extract_host_from_url(), etc. |
testing |
Test utilities: PluginTestRunner, TestCase, fixtures_dir!() |
native |
NativePluginRule adapter for running plugins without WASM overhead |
prelude |
Convenient re-exports for use nginx_lint_plugin::prelude::* |
License
MIT