Skip to main content
AmitVishwakarma
Community Advisor
Community Advisor
October 6, 2026

AEM Dispatcher Filter Audit: Catch Leaks Before Go-Live

  • October 6, 2026
  • 0 replies
  • 10 views

[OVERVIEW]
Offline Python tool that replays 23 known attack requests against your Dispatcher /filter rules and shows which ones get through, and which rule to fix.

It reads dispatcher.any, a farm file or filters.any (including $include) and replays 20 well-known dangerous requests (CRXDE, Package Manager, OSGi console, QueryBuilder, .infinity.json, .tidy.infinity.json, numeric selectors, .query.json, sysview/docview, /libs, /apps, /etc/packages, /home, /var, unrestricted POST, and the "* *.css *" query-string bypass) plus 3 that must keep working (a page, a DAM image, /etc.clientlibs) so over-blocking is noticed.

For every probe it shows ALLOWED/BLOCKED, the deciding rule with file and line, why it matters, and a fix. Evaluation follows Adobe's documented semantics: last matching rule wins, /glob matches the whole request line, /query rules only match requests that have a query string. Most Dispatcher leaks are rule-ORDER problems (a broad allow placed after a deny backstop) or selector gaps, which reading the file by eye tends to miss. Works for AEMaaCS, AMS and on-prem configs, and can gate CI (--fail-on high returns exit code 1).

It is an offline approximation, not the real Dispatcher module. Use it as a fast pre-check next to Adobe's Dispatcher SDK validator and a test on a real Dispatcher.

[IMPACT]
Goals: catch Dispatcher filter mistakes before deployment, in seconds, without a running AEM or Dispatcher, and give reviewers a repeatable check.

Results (measured on the synthetic sample configs shipped with the tool):

- 37 automated unit tests, all passing (parser, $include, last-match-wins, query-rule semantics, selector splitting, Adobe-style constructs, CLI exit codes, JSON/Markdown output).
- "Typical mistakes" sample (9 rules): 16 of 23 probes failed (4 critical, 8 high, 3 medium, 1 info). Root causes: broad allows for /etc, /libs, /bin, /crx, /system, unrestricted POST, and a deny backstop placed above the allows.
- After applying only the suggested fixes: 0 of 23 failed.
- Hardened sample using $include: 0 of 23 failed. Legacy /glob sample: the query-string bypass (GET /crx/de/index.jsp?foo=.css) was caught.
- A full run takes a fraction of a second.

Real-world check on public files: Adobe's WKND project Dispatcher config (default_filters.any + filters.any via $include, 44 rules) gives 0 of 23 probes failed, i.e. no false alarms on a real hardened config, including the 3 "must keep working" requests. A custom probe (--probe "GET /home/users/a/admin.infinity.json") showed that WKND's rule /0201 lets that URL through the Dispatcher. That is a review item, not proof of a leak: repository ACLs are a separate layer, and I did not test it against a running server. Reproduce: clone https://github.com/adobe/aem-guides-wknd and run the tool on dispatcher/src/conf.dispatcher.d/filters/filters.any.

[LESSONS]
Order beats content: in the sample the deny backstop was correct but sat above the allows, so it did nothing. Keep backstops LAST.

Replaying requests finds leaks that reading misses, e.g. tidy.infinity.json when only "infinity" was denied.

Allow rules should set /path AND /extension (and /method); "/url /content/*" alone lets every selector and extension through.

Avoid /glob in filters (Adobe marks it deprecated); "* *.css *" style allows can be bypassed through the query string.

Limits: approximation of Dispatcher; URL parts are split with a simple heuristic; /suffix is not probed; element values are matched as glob OR regex; only /filter is audited (not cache, rewrites, vhosts, headers).

Keep your own must-be-blocked URLs in CI with --probe.

[TRYABLE]
1. Save dispatcher_filter_audit.py (Python 3.8+, nothing to install).
2. Run it on your config, e.g. dispatcher/src/conf.dispatcher.d/filters/filters.any or your farm file: python dispatcher_filter_audit.py path/to/filters.any
3. Read the [FAIL] lines: request, ALLOW/DENY, deciding rule (file:line), why it matters, fix.
4. Fix the rules (remove broad allows, make allows specific with /path + /extension, move deny backstops to the end) and re-run until 0 failures.
5. Add your own must-be-blocked URLs: python dispatcher_filter_audit.py filters.any --probe "GET /content/site/private.json"
6. Optional: --format markdown for a PR comment, --fail-on high for a CI gate.
7. Validate with Adobe's Dispatcher SDK validator and test on a real Dispatcher. This tool does not replace that.
8. Try the samples first: samples/bad (16 failures), samples/bad-fixed (0), samples/good (0), samples/legacy-glob (query-string bypass), samples/adobe-style (0).

[SETUP]
Python 3.8+.  Read access to your Dispatcher config files. The tool only reads local files.

[SAMPLE_OUTPUT]
Repo URL - https://github.com/amitpersonal28-star/dispatcher-filter-audit

Result:

ALLOWED; ALLOW by rule /0020 (dispatcher.any:18); expected blocked [CRITICAL] CRXDE Lite reachable fix: Deny /crx/* on publish. Never allow it on public hostnames.

SUMMARY: 16/23 probes failed | critical=4 high=8 medium=3 low=0 info=1