Security Model
Signed requests, body hashes, timestamps, nonces, and server-owned sync authority.
Security Model
The sync endpoint is designed to be strict by default.
Production sync should use GitHub OIDC or Ed25519 signed requests. Basic auth is not a production sync model.
GitHub Actions OIDC is also supported and avoids long-lived private keys in GitHub workflows.
Signed Headers
The endpoint expects:
1X-VL-MD-DOCS-Key-Id2X-VL-MD-DOCS-Timestamp3X-VL-MD-DOCS-Nonce4X-VL-MD-DOCS-Body-SHA2565X-VL-MD-DOCS-Signature
Canonical String
The sender signs:
1v12POST3<endpoint pathname>4<timestamp>5<nonce>6<sha256(body)>
The CLI derives the endpoint pathname from the full endpoint URL.
Server-Owned Controls
The manifest cannot choose:
- target collection
- target field names
- route base
- publish authority
- hard delete authority
- allowed docs set slugs
- source-specific auth allowlists
Payload Admin docs sets own package routing, branch, OIDC audience, and
GitHub OIDC restrictions (allowed repositories, tag-ref acceptance, workflow
refs, pull requests). Access records own reusable publishing credentials and
trust: Ed25519 public keys for signed manual, local, or non-GitHub sync, and
GitHub OIDC owner/repository allowlists for GitHub Actions sync. An Access
record is valid for every docs set unless its Allowed docs sets list is set;
scope keys to the docs sets they publish. Nonces provide replay protection. Sync runs provide audit history. Plugin
config owns collection setup and lifecycle gates such as write, publish, and
hard-delete authority.
Replay Protection
Nonces are stored in the docs-sync-nonces collection as soon as a request is
authenticated, before the manifest is validated or applied. The collection has
a unique (keyId, nonce) index, so concurrent requests with the same nonce
cannot both be accepted, and a request rejected later (invalid manifest, route
collision, policy error) cannot be replayed once the problem is fixed.
- Ed25519 nonces are kept at least until the signed timestamp leaves the allowed clock skew window.
- GitHub OIDC
jtivalues are kept until the token'sexpplus the allowed skew, the full time the token can still be accepted. - Expired nonce rows are deleted opportunistically during later syncs.
Request Order
The endpoint authenticates before it reads any docs-set data. Requests without
valid credentials get a 401 that does not depend on whether the docs set in
source.id exists. source.id must be a docs-set slug string, and bodies
larger than endpoint.maxBodyBytes are rejected with 413 without being
buffered. For GitHub OIDC, the token signature, issuer, audience (source.id),
expiry, and trusted owner/repository are verified first; the docs set's branch,
workflow, and pull-request rules are checked after the docs set is resolved.
Common Rejections
See troubleshooting for invalid_signature, body_hash_mismatch, nonce_replay, source_not_allowed, publish_disabled, and other endpoint errors.