Skip to content

.htaccess: the AI-agent markdown rule does not cover root-level /AGENTS.md, /CLAUDE.md #2300

Description

@szabeszg

Follow-up to #2288, which added a deny rule for AI-agent instruction files. Thank you for that, this is a request to widen its scope slightly, because the most exposed location is the one it misses.

The gap

The rule added on the dev branch matches only paths under wire/ or site/:

(wire|site|site-[^/]+)/(.+/)?(AGENTS?|CLAUDE|SKILLS?|MEMORY|API)\.md

But these files conventionally live at the project root, that is where the tools that read
them look:

/AGENTS.md
/CLAUDE.md

Neither is matched, so both are served. Verified on a site running @htaccessVersion 301: both returned HTTP 200 with their full contents.

Section 9's stock-documents rule does not help either, as it lists specific filenames:

RewriteCond %{REQUEST_URI} (^|/)(COPYRIGHT|INSTALL|README|CHANGELOG|LICENSE|htaccess)\.(txt|md|textile)$ [NC,OR]

Why a nested .htaccess cannot solve it

The usual answer, drop a guard into the directory, does not apply to a root-level file. A nested .htaccess cannot reach a file in the directory above it, and .htaccess has no Include, so a site cannot ship a tracked snippet that covers the docroot root. That leaves every site
hand-maintaining a rule in its own per-environment root .htaccess, which is exactly the file people avoid touching because it is replaced on upgrade.

This matters most under a docroot-as-repo deployment (cPanel Git Version Control and similar), where the repository root is the web root. There, a tracked CLAUDE.md is published by an ordinary git push. The contents are usually not secret, but they do describe project structure, tooling, paths and conventions, not something to serve by default.

Suggested change

A FilesMatch tests the basename, so one block covers the root and every subdirectory at once, and would supersede the narrower path-based rule:

<FilesMatch "^(AGENTS?|CLAUDE|SKILLS?|MEMORY|API)\.md$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
  </IfModule>
</FilesMatch>

Verified on two sites: /CLAUDE.md, /AGENTS.md and nested files such as /site/modules/<Module>/API.md all return 403, while normal pages and the admin are unaffected.

If matching API.md everywhere is considered too broad, some sites may intend to publish one, the same block without API would still close the root-level gap for the agent files.

Steps to reproduce

  1. Place a CLAUDE.md (or AGENTS.md) in the ProcessWire root.
  2. Request https://example.com/CLAUDE.md.

Expected: 403.
Actual: 200, with the file's contents.

Environment

ProcessWire 3.0.268 and 3.0.269
@htaccessVersion 301 (the #2288 rule is not in 301; checked against the dev-branch pattern)
Server Apache, mod_authz_core available

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions