v0.20.0 — Agent-Ready discovery & Markdown for Agents
oCMS is now discoverable by AI agents. A new Link header on the homepage, three /.well-known/* surfaces, and Accept: text/markdown content negotiation on article routes let scanners like isitagentready.com find the REST API and consume page content without HTML theme overhead. This release also closes 10 of 11 findings from the latest security audit, each with a drift test that fails on the bug state.
Added
Agent-Ready Discovery
- Publish RFC 8288
Linkheader on/pointing to API Catalog,
OpenAPI service-desc, and Swagger UI service-doc relations so agent
scanners can find the REST API from a single homepage hit GET /.well-known/api-catalog(RFC 9727 linkset) declares stable
API surfaces with media types and doc linksGET /.well-known/agent-skills/index.json(v0.2.0) advertises the
REST API capability with a real OpenAPI SHA-256 digest for client
validationGET /.well-known/mcp/server-card.json(draft SEP-1649) exposes MCP
server metadata;transportisnullpending a future MCP transport
implementation, with REST fallback viacapabilities.rest.openapiContent-Signal:directive in/robots.txtdeclares training-data
preferences (defaultsearch=yes, ai-train=no, ai-input=yes,
configurable viarobots_content_signalconfig key)- New config keys:
robots_content_signalandmcp_server_version
Markdown for Agents
- Content negotiation on
/and/{slug}: callers sending
Accept: text/markdownreceive a plain-text Markdown representation
instead of HTML theme output - Response headers include
Content-Type: text/markdown; charset=utf-8,
Vary: Accept(for CDN cache separation), andX-Markdown-Tokens
for agent context-budget estimation - Conversion from stored TinyMCE HTML via
JohannesKaufmann/html-to-markdown/v2;<script>and<iframe>
stripped by construction - 2 MB input cap to prevent CPU DoS on oversized pages; falls back to
HTML on overflow - Auth parity with the HTML path: draft pages remain admin/editor-only
in both representations
Configuration
OCMS_HSTS_PRELOADenvironment variable (defaultfalse) enables
the; preloadHSTS directive in production without recompiling;
opt-in for operators submitting domains to hstspreload.org
Changed
Deployment & Operations
ocmsctl start|stop|restartauto-delegates tosystemctlwhen a
site is enabled asocms@<site-id>.service, eliminating manual
sudo systemctlinvocations. PID-file sites continue to work
unchangedocmsctl start --foregroundstill errors on systemd-enabled sites
(unsupported)- Delegation respects management intent:
startdefers to systemd
when the unit is enabled;stop/restartonly act when the unit
is active, avoiding races with a PID-file process
Documentation
scripts/deploy/README.md— new Troubleshooting sections for
missing systemd drop-in files and "sites don't come back after
reboot", plus clarification that the health-check cron only
restartsactivesitesdocs/agent-ready.md(new) describes discovery surfaces, Markdown
negotiation, config keys, and local verificationdocs/login-security.md— Production Deployment Checklist calls
out rotating the seededadmin@example.com / changeme1234
credentials on first logindocs/reverse-proxy.md— HSTS Preload section with the
OCMS_HSTS_PRELOADenv var and hstspreload.org submission process
Dependencies
- Update htmx.org to 2.0.10
- Update Tailwind CSS to 4.2.4
Fixed
Agent-Ready Content Negotiation
Accept: text/markdown;q=0now correctly yields HTML- Wildcard ranges (
*/*,text/*) contribute to HTML quality without
forcing markdown when HTML is listed with higher specificity - Specificity tiers (explicit media type > wildcard) defer to explicit
entries, soAccept: text/html;q=0.2, text/markdown;q=0.8, */*
correctly prefers markdown
Login Protection
- Serialize
RecordFailedAttemptandRecordSuccessfulLoginunder a
per-process mutex so concurrent requests cannot drop attempts or let
an upsert re-lock a just-cleared account
Security
Markdown Rendering (FIND-001, FIND-002)
- Markdown canonical URL requires configured
site_url; nor.Host
fallback, blocking attacker-supplied Host headers from landing in
[Source]links (FIND-001) sanitizeHeadingcollapses newlines in H1 titles before writing
markdown, blocking heading break-out (FIND-002)
Authentication (FIND-003, FIND-005)
auth.VerifyDummyPasswordruns constant-cost Argon2id verification
on the unknown-email path, eliminating timing-based email
enumeration (FIND-003)- New
login_protectionSQLite table replaces the in-memory lockout
map, so brute-force windows survive deploys, crashes, and OOM kills
(FIND-005)
Session & CSP (FIND-006, FIND-007, FIND-008)
- Session cookie upgraded to
SameSite=Strict(FIND-006) - Remove unused
unpkg.comandesm.shfromscript-srcand
connect-srcin both dev and prod CSPs (FIND-007) - Add
upgrade-insecure-requeststo the production CSP so
admin-authored HTML withhttp://subresources auto-upgrades
(FIND-008)
Transport & Secrets (FIND-009, FIND-010, FIND-011)
OCMS_HSTS_PRELOADenv var enables HSTS preload without
recompiling; docs cover the hstspreload.org submission process
(FIND-009)- Broaden
.gitignoreto.env*and*.api_keysso secret-carrying
files cannot be committed by accident (FIND-010) - Production Deployment Checklist requires operators to rotate seeded
admin credentials on first login (FIND-011)
Each security fix ships with a drift test that fails on the bug
state. FIND-004 (unsafe-eval for Alpine.js) remains open as a
structural dependency.
Full changelog: v0.19.0...v0.20.0