Skip to content

docs: add Secrets & Key Management inventory#1946

Open
swissky wants to merge 2 commits into
emdash-cms:mainfrom
swissky:docs/secrets-inventory
Open

docs: add Secrets & Key Management inventory#1946
swissky wants to merge 2 commits into
emdash-cms:mainfrom
swissky:docs/secrets-inventory

Conversation

@swissky

@swissky swissky commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a dedicated docs page, Deployment → Secrets & Key Management (/deployment/secrets/), covering the review requested in #1688: a complete inventory of every secret EmDash uses, with source, storage location, rotation steps, and loss impact for each.

Covered per the issue's acceptance criteria:

  • EMDASH_ENCRYPTION_KEY — format, emdash secrets generate / fingerprint, multi-key (comma-separated) rotation with kid-tagged envelopes, loss impact. Honestly documents that the consuming encryption layer is not active yet, and why setting the key today still makes sense.
  • Generated site secrets (preview HMAC secret, commenter-IP salt) — auto-generated and persisted atomically in the options table (stable persistence, already covered by config/secrets tests from feat(core): add centralized secrets module and emdash secrets CLI #811), env-var overrides (EMDASH_PREVIEW_SECRET, EMDASH_IP_SALT, legacy aliases), and what each rotation actually invalidates (outstanding preview links / rate-limit continuity — no data loss in either case).
  • Session & auth tokens — sessions via Astro's session store (no signing secret to manage); API/invite/magic-link/recovery tokens stored as SHA-256 hashes only; rotation = revoke/reissue from the admin.
  • User-provided service credentials (OAuth providers, Turnstile, S3) — env variable names and the rotate-at-provider flow; notes that R2 bindings need no credentials.
  • Plugin secrets — stored in the options table / plugin storage, with an explicit caution that they are currently unencrypted at rest and that encryption via EMDASH_ENCRYPTION_KEY is planned.
  • CLI & publishing credentialsemdash login token in ~/.config/emdash/auth.json (mode 0600), publishing tied to the publisher's AT Protocol DID with no site-held credentials.
  • A rotation quick-reference table at the end.

Also cross-links the new page from the Cloudflare deployment guide's environment-variables section, and adds it to the docs sidebar.

Every claim was verified against the current code (config/secrets.ts, auth/tokens.ts, cli/credentials.ts, provider/storage modules) rather than written from memory.

Closes #1688

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (n/a — docs only; the stable-persistence behavior this documents is already covered by tests/unit/config/secrets.test.ts)
  • User-visible strings in the admin UI are wrapped for translation (n/a — docs only)
  • I have added a changeset (n/a — docs only, no published package changed)
  • New features link to an approved Discussion: n/a

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Cursor + Fable 5

Screenshots / test output

pnpm --filter docs build passes (77 pages, search index built).

@changeset-bot

changeset-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c8e3e44

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added review/needs-review No maintainer or bot review yet area/docs size/M labels Jul 11, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1946

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1946

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1946

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1946

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1946

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1946

emdash

npm i https://pkg.pr.new/emdash@1946

create-emdash

npm i https://pkg.pr.new/create-emdash@1946

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1946

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1946

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1946

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1946

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1946

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1946

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1946

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1946

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1946

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1946

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1946

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1946

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1946

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1946

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1946

commit: c8e3e44

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a docs-only PR that adds a long-needed Secrets & Key Management inventory page. The structure is good and most entries line up with the code I checked (config/secrets.ts, auth/tokens.ts, cli/credentials.ts, preview tokens, S3/Turnstile env resolution, OAuth provider config). However, the CLI & publishing credentials section contains a factual error that undermines the page's core promise: it conflates the site emdash CLI with the separate @emdash-cms/plugin-cli (emdash-plugin) registry CLI. emdash plugin publish authenticates to the EmDash Marketplace via GitHub device flow and stores a marketplace JWT in the same ~/.config/emdash/auth.json file (or uses EMDASH_MARKETPLACE_TOKEN in CI). The AT Protocol / publisher-DID flow belongs to the emdash-plugin CLI, which persists OAuth state in ~/.emdash/oauth/. The page also omits EMDASH_MARKETPLACE_TOKEN from the user-provided service credentials table. These should be fixed before merge.

Comment on lines +116 to +118
The `emdash` CLI (`emdash login`) authenticates via a GitHub device flow and stores the resulting token in `~/.config/emdash/auth.json` (respecting `XDG_CONFIG_HOME`), created with owner-only permissions (`0600`). `emdash logout` removes it; `EMDASH_TOKEN` or `--token` override it per invocation.

Plugin publishing to the registry is tied to your **AT Protocol identity** (your publisher DID) — the site itself holds no publishing credentials, and installs verify artifacts against checksums from release records attributed to that DID. Rotating or revoking publishing access happens at your AT Protocol account (e.g. app passwords), not in EmDash. See [Atmosphere auth](/guides/atmosphere-auth/).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] This paragraph conflates two different CLIs and credential models.

  • The site emdash CLI's plugin publish command authenticates to the EmDash Marketplace via a GitHub device flow, receives a marketplace JWT, and stores it in ~/.config/emdash/auth.json under a marketplace:<origin> key (packages/core/src/cli/credentials.ts / commands/publish.ts). In CI it accepts EMDASH_MARKETPLACE_TOKEN.
  • The AT Protocol / publisher-DID flow is implemented by a separate CLI, @emdash-cms/plugin-cli (binary emdash-plugin), which stores atproto OAuth session/state blobs in ~/.emdash/oauth/ (packages/plugin-cli/src/oauth.ts, config.ts) and accepts CI identity via EMDASH_PUBLISHER_DID, EMDASH_PUBLISHER_HANDLE, and EMDASH_PUBLISHER_PDS.

Because the page claims to be a complete inventory, the two must be documented separately. Replace this section with distinct subsections for the site CLI (emdash login/logout/whoami, site API token) and the registry CLI (emdash-plugin login/publish, ~/.emdash/oauth/, and the env-var CI identity).

Suggested change
The `emdash` CLI (`emdash login`) authenticates via a GitHub device flow and stores the resulting token in `~/.config/emdash/auth.json` (respecting `XDG_CONFIG_HOME`), created with owner-only permissions (`0600`). `emdash logout` removes it; `EMDASH_TOKEN` or `--token` override it per invocation.
Plugin publishing to the registry is tied to your **AT Protocol identity** (your publisher DID) — the site itself holds no publishing credentials, and installs verify artifacts against checksums from release records attributed to that DID. Rotating or revoking publishing access happens at your AT Protocol account (e.g. app passwords), not in EmDash. See [Atmosphere auth](/guides/atmosphere-auth/).
## CLI credentials
The `emdash` CLI (`emdash login`) authenticates to an EmDash instance via OAuth device flow and stores the resulting token in `~/.config/emdash/auth.json` (respecting `XDG_CONFIG_HOME`), created with owner-only permissions (`0600`). `emdash logout` removes it; per invocation, `--token` or `EMDASH_TOKEN` overrides the stored token.
The same file also holds Marketplace credentials for `emdash plugin publish` / `emdash plugin login` (keyed by `marketplace:<origin>`). For CI publishing, set `EMDASH_MARKETPLACE_TOKEN`.
## Plugin registry CLI credentials
The separate `emdash-plugin` CLI (package `@emdash-cms/plugin-cli`) targets the experimental AT Protocol registry. It authenticates via atproto OAuth and stores session/state data in `~/.emdash/oauth/`. In CI you can provide identity via `EMDASH_PUBLISHER_DID`, `EMDASH_PUBLISHER_HANDLE`, and `EMDASH_PUBLISHER_PDS` (`EMDASH_REGISTRY_URL` overrides the aggregator). See [Atmosphere auth](/guides/atmosphere-auth/) for the related site-login flow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8e3e44 — split into two sections. CLI credentials now covers both entries in ~/.config/emdash/auth.json: the site token from emdash login (instance OAuth device flow, not GitHub — I corrected that too) and the marketplace JWT from emdash plugin publish (GitHub device flow, marketplace:<origin> key, EMDASH_MARKETPLACE_TOKEN in CI). Plugin registry CLI credentials documents the separate emdash-plugin CLI: atproto OAuth state in ~/.emdash/oauth/ and the EMDASH_PUBLISHER_DID/HANDLE/PDS + EMDASH_REGISTRY_URL env identity for CI. Overview table updated with both rows.

| Google sign-in | `EMDASH_OAUTH_GOOGLE_CLIENT_ID`, `EMDASH_OAUTH_GOOGLE_CLIENT_SECRET` (or unprefixed aliases) |
| GitHub sign-in | `EMDASH_OAUTH_GITHUB_CLIENT_ID`, `EMDASH_OAUTH_GITHUB_CLIENT_SECRET` (or unprefixed aliases) |
| Turnstile (comments) | `EMDASH_TURNSTILE_SECRET_KEY` (or `TURNSTILE_SECRET_KEY`) |
| S3-compatible storage | `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] The user-provided service credentials table omits EMDASH_MARKETPLACE_TOKEN. emdash plugin publish reads this variable to authenticate marketplace uploads in CI (packages/core/src/cli/commands/publish.ts), so it is a user-supplied secret that controls publishing access and belongs in this inventory.

Suggested change
| S3-compatible storage | `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION` |
| Marketplace publishing (CI) | `EMDASH_MARKETPLACE_TOKEN` |

Add it as a new row in the table (e.g., between GitHub sign-in and Turnstile).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8e3e44 — added a "Marketplace publishing (CI)" row with EMDASH_MARKETPLACE_TOKEN to the service-credentials table (verified against cli/commands/publish.ts: env var takes priority over the stored credential).

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 11, 2026
…ials

Review follow-up: the CLI section conflated two CLIs. `emdash login`
uses the instance's OAuth device flow and `emdash plugin publish`
stores a marketplace JWT (EMDASH_MARKETPLACE_TOKEN in CI) in the same
auth.json; the separate emdash-plugin CLI holds atproto OAuth state in
~/.emdash/oauth/ with EMDASH_PUBLISHER_* env identity for CI. Adds
EMDASH_MARKETPLACE_TOKEN to the service-credentials table.
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 11, 2026
@swissky swissky added the bot:review Trigger an emdashbot code review on this PR label Jul 11, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a much-needed Secrets & Key Management inventory docs page. The approach is sound: it’s documentation-only, it follows the docs structure (new page under Deployment, cross-link from the Cloudflare guide, sidebar entry), and the prior review’s two hard blockers have been addressed.

I re-verified the updated CLI/publishing section against the concrete code:

  • The page now correctly separates the site emdash CLI (~/.config/emdash/auth.json) from the registry emdash-plugin CLI (~/.emdash/oauth/).
  • EMDASH_MARKETPLACE_TOKEN is present in the user-provided service credentials table.
  • The emdash plugin publish flow uses GitHub device flow and stores a marketplace JWT keyed by marketplace:<origin> in ~/.config/emdash/auth.json, matching packages/core/src/cli/commands/publish.ts and cli/credentials.ts.
  • EMDASH_PUBLISHER_DID, EMDASH_PUBLISHER_HANDLE, EMDASH_PUBLISHER_PDS, and EMDASH_REGISTRY_URL line up with packages/plugin-cli/src/config.ts and packages/registry-client/src/credentials/env.ts.

I also spot-checked the other claims: EMDASH_ENCRYPTION_KEY format/rotation, preview/IP-salt persistence, OAuth/Turnstile/S3 env var names, API token hashing, and session handling all match the source I could reach.

One small remaining gap: the registry CLI section identifies ~/.emdash/oauth/ as the storage location, but emdash-plugin login also writes the publisher identity (DID/handle/PDS) to ~/.emdash/credentials.json via FileCredentialStore, and CI publish still needs the OAuth session files on disk because the env-var identity store currently does not carry the OAuth session blob. For a page that bills itself as a complete inventory, that distinction is worth calling out.

No build or test blocker; this is a documentation-only change and needs no changeset.

Comment on lines +125 to +131
## Plugin registry CLI credentials

The separate `emdash-plugin` CLI (package `@emdash-cms/plugin-cli`) targets the experimental **AT Protocol registry**. Publishing there is tied to your AT Protocol identity (your publisher DID) — the site itself holds no publishing credentials, and installs verify artifacts against checksums from release records attributed to that DID.

- It authenticates via atproto OAuth and stores session/state blobs in `~/.emdash/oauth/` (written atomically with mode `0600`).
- In CI, provide identity via `EMDASH_PUBLISHER_DID`, `EMDASH_PUBLISHER_HANDLE`, and `EMDASH_PUBLISHER_PDS`; `EMDASH_REGISTRY_URL` overrides the registry host.
- Rotating or revoking publishing access happens at your AT Protocol account (e.g. app passwords), not in EmDash. See [Atmosphere auth](/guides/atmosphere-auth/).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The registry CLI section lists ~/.emdash/oauth/ as the storage location, but emdash-plugin login also persists the publisher identity (DID, handle, PDS) in ~/.emdash/credentials.json via FileCredentialStore (packages/registry-client/src/credentials/file.ts). Additionally, the env-var identity store does not carry the OAuth session blob, so automated publish from CI still needs the OAuth session files in ~/.emdash/oauth/ on the runner. For a page that promises a complete inventory, mention both files and the CI caveat.

Suggested change
## Plugin registry CLI credentials
The separate `emdash-plugin` CLI (package `@emdash-cms/plugin-cli`) targets the experimental **AT Protocol registry**. Publishing there is tied to your AT Protocol identity (your publisher DID) — the site itself holds no publishing credentials, and installs verify artifacts against checksums from release records attributed to that DID.
- It authenticates via atproto OAuth and stores session/state blobs in `~/.emdash/oauth/` (written atomically with mode `0600`).
- In CI, provide identity via `EMDASH_PUBLISHER_DID`, `EMDASH_PUBLISHER_HANDLE`, and `EMDASH_PUBLISHER_PDS`; `EMDASH_REGISTRY_URL` overrides the registry host.
- Rotating or revoking publishing access happens at your AT Protocol account (e.g. app passwords), not in EmDash. See [Atmosphere auth](/guides/atmosphere-auth/).
The separate `emdash-plugin` CLI (package `@emdash-cms/plugin-cli`) targets the experimental **AT Protocol registry**. Publishing there is tied to your AT Protocol identity (your publisher DID) — the site itself holds no publishing credentials, and installs verify artifacts against checksums from release records attributed to that DID.
- It authenticates via atproto OAuth. The OAuth session/state blobs live in `~/.emdash/oauth/`, and the publisher identity (DID, handle, PDS) is cached in `~/.emdash/credentials.json`; both are written with owner-only permissions.
- In CI, you can provide identity via `EMDASH_PUBLISHER_DID`, `EMDASH_PUBLISHER_HANDLE`, and `EMDASH_PUBLISHER_PDS`; `EMDASH_REGISTRY_URL` overrides the registry host. Automated `publish` from CI still needs the OAuth session files in `~/.emdash/oauth/` on the runner — the env vars alone do not carry the OAuth session.
- Rotating or revoking publishing access happens at your AT Protocol account (e.g. app passwords), not in EmDash. See [Atmosphere auth](/guides/atmosphere-auth/).

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs bot:review Trigger an emdashbot code review on this PR review/awaiting-author Reviewed; waiting on the author to respond size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review secret management and rotation for stable release

1 participant