fix(core): REST + CLI audit, 34 bug fixes#820
Conversation
…ling, data integrity, and client parity
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | 3c5bc24 | Apr 29 2026, 02:08 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | 3c5bc24 | Apr 29 2026, 02:09 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 3c5bc24 | Apr 29 2026, 02:09 PM |
|
Scope checkThis PR changes 734 lines across 38 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 3c5bc24 | Apr 29 2026, 02:09 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 3c5bc24 | Apr 29 2026, 02:10 PM |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Pull request overview
Systematic bug-fix sweep across packages/core REST API + client SDK + CLI to align response envelopes, harden auth/scope enforcement, fix data integrity edge cases, and add targeted defense-in-depth/perf improvements discovered during an audit.
Changes:
- Tighten authorization and data-leak protections (draft/revision/search restrictions, media confirm ownership, scope-rule fixes, DB guards).
- Fix client/CLI parity issues (response envelope handling, refresh token parsing, CLI JSON output consistency, PT conversion on draft overlays).
- Improve robustness/perf (transactional revision restore + auth-code token issuance, redirect LIKE escaping, batched taxonomy term counts, pagination/length clamps).
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/tests/unit/client/transport.test.ts | Adds unit coverage for refreshInterceptor handling of { data: ... } token envelopes. |
| packages/core/tests/unit/client/client.test.ts | Adds regression tests for /taxonomies and /menus response envelope shapes. |
| packages/core/tests/integration/cli/cli.test.ts | Adds CLI integration coverage for taxonomy/menu JSON output. |
| packages/core/src/database/repositories/taxonomy.ts | Adds batched term entry-count query to remove N+1 counting. |
| packages/core/src/database/repositories/redirect.ts | Escapes LIKE wildcards and adds explicit ESCAPE handling for redirect/404 search. |
| packages/core/src/client/transport.ts | Makes refresh-token parsing tolerant of wrapped and bare JSON shapes. |
| packages/core/src/client/index.ts | Fixes SDK methods to match actual API response shapes for taxonomies/menus. |
| packages/core/src/cli/credentials.ts | Ensures credential directory is created with restrictive permissions. |
| packages/core/src/cli/commands/login.ts | Fixes CLI whoami refresh parsing for { data: ... } responses. |
| packages/core/src/cli/commands/content.ts | Applies PT-to-markdown conversion to draft overlays; emits { success: true } for void JSON commands. |
| packages/core/src/cli/commands/bundle-utils.ts | Expands Node builtin import detection for more ESM/export forms. |
| packages/core/src/astro/routes/api/snapshot.ts | Restores session-auth access path for snapshot route while keeping preview-signature auth. |
| packages/core/src/astro/routes/api/sections/index.ts | Adds requireDb guard to avoid crashes on uninitialized runtime. |
| packages/core/src/astro/routes/api/sections/[slug].ts | Adds requireDb guard to avoid crashes on uninitialized runtime. |
| packages/core/src/astro/routes/api/search/index.ts | Forces non-draft readers to status=published to prevent draft search leakage. |
| packages/core/src/astro/routes/api/revisions/[revisionId]/index.ts | Requires content:read_drafts for revision reads (security hardening). |
| packages/core/src/astro/routes/api/redirects/index.ts | Adds requireDb guard to avoid crashes on uninitialized runtime. |
| packages/core/src/astro/routes/api/redirects/[id].ts | Adds requireDb guard to avoid crashes on uninitialized runtime. |
| packages/core/src/astro/routes/api/redirects/404s/summary.ts | Adds requireDb guard to avoid crashes on uninitialized runtime. |
| packages/core/src/astro/routes/api/redirects/404s/index.ts | Adds requireDb guard to avoid crashes on uninitialized runtime. |
| packages/core/src/astro/routes/api/openapi.json.ts | Moves OpenAPI caching to globalThis/Symbol.for and hardens error handling/cache headers. |
| packages/core/src/astro/routes/api/media/providers/[providerId]/index.ts | Clamps provider list limit; adds basic upload size/type checks. |
| packages/core/src/astro/routes/api/media/[id]/confirm.ts | Adds ownership-based authorization to confirm/fail pending uploads. |
| packages/core/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts | Ensures term assignment uses canonical content ID (slug→id correctness). |
| packages/core/src/astro/routes/api/content/[collection]/[id]/revisions.ts | Clamps revision list limit to avoid negative/unbounded behavior. |
| packages/core/src/astro/routes/api/content/[collection]/[id]/restore.ts | Uses resolved canonical ID for restore + cache invalidation tags. |
| packages/core/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts | Uses resolved canonical ID for discard-draft + cache invalidation tags. |
| packages/core/src/astro/routes/api/content/[collection]/[id].ts | Strips draft overlay fields for users without content:read_drafts. |
| packages/core/src/astro/middleware/auth.ts | Updates scope rules for granular taxonomies:manage/menus:manage and settings read/manage. |
| packages/core/src/api/schemas/users.ts | Adds max-length clamp for cursor parameters. |
| packages/core/src/api/schemas/sections.ts | Tightens section creation source values and clamps list cursor/limit defaults. |
| packages/core/src/api/schemas/comments.ts | Clamps list cursor/limit defaults for comments. |
| packages/core/src/api/handlers/taxonomies.ts | Switches term counts to use new batched repository method. |
| packages/core/src/api/handlers/revision.ts | Makes revision restore transactional (best-effort on D1). |
| packages/core/src/api/handlers/oauth-authorization.ts | Uses constant-time PKCE compare; revalidates role/scopes; stores tokens transactionally. |
| packages/core/src/api/handlers/device-flow.ts | Adds commentary around client_id behavior in device code request. |
| packages/core/src/api/handlers/content.ts | Maps EmDashValidationError to VALIDATION_ERROR for publish/unpublish/unschedule paths. |
| packages/blocks/src/validation.ts | Tightens numeric type checks for block validation bounds. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
The Apply workflow has been silently broken for ~2 days, so PRs that landed in that window (#820, #816, #811) merged with stale snapshots in the repo. CI's Measure on this PR shows the real per-route counts on top of current main: uniform +1 cold and +1 warm on all public routes except /pages/about and /rss.xml. This is pre-existing drift, not caused by this PR — it's the natural consequence of the Apply workflow failing to commit the auto-updated snapshots that the Measure step correctly produced on those merged PRs. Apply CI's regenerated snapshots from this PR's Measure run so main ends up in a consistent state once this lands. Future PRs will then get their own real drift signal rather than inheriting this baseline correction.
* fix(ci): query-counts-apply workflow no longer silently fails
The Apply workflow has been silently broken for ~2 days, never pushing
auto-updated snapshots to PRs that drifted. Two distinct bugs:
(1) Same-repo PRs: the artifact was unpacked into
$GITHUB_WORKSPACE/artifact, but the later actions/checkout step
defaults to clean: true and wipes the workspace, deleting our
payload before the Apply step's cp could read it. Stage under
$RUNNER_TEMP/qc-artifact instead, which sits outside the workspace.
(2) Fork PRs: the cross-check used listPullRequestsAssociatedWithCommit
against the base repo, which doesn't reliably surface fork PRs from
that view, so valid fork artifacts were rejected as 'not associated
with commit'. Replace with pulls.get(prNumber) and verify
pr.head.sha === workflow_run.head_sha — equivalent tamper-resistance
(a forged pr-number won't match), works for forks.
Add pull-requests: read so pulls.get works when the default token
permissions are restricted.
* ci: reconcile query-count snapshot baseline
The Apply workflow has been silently broken for ~2 days, so PRs that
landed in that window (#820, #816, #811) merged with stale snapshots
in the repo. CI's Measure on this PR shows the real per-route counts
on top of current main: uniform +1 cold and +1 warm on all public
routes except /pages/about and /rss.xml. This is pre-existing drift,
not caused by this PR — it's the natural consequence of the Apply
workflow failing to commit the auto-updated snapshots that the
Measure step correctly produced on those merged PRs.
Apply CI's regenerated snapshots from this PR's Measure run so main
ends up in a consistent state once this lands. Future PRs will then
get their own real drift signal rather than inheriting this baseline
correction.
What does this PR do?
Fixes 34 bugs across the REST API and CLI discovered during a systematic audit that exercised every API surface against the deployed beta site and then traced each failure through source. Mirrors the approach from PR #777 (MCP audit) but covers the REST and CLI surfaces.
The audit ran 6 parallel adversarial review agents across content routes, auth/scope middleware, pagination, schema/sections/settings, media/search/redirects/comments, and CLI parity. The fixes went through 2 adversarial review passes before finalizing.
Security fixes (6)
GET /content/:col/:idcontent:read_draftsGET /revisions/:idcontent:read_draftsinstead ofcontent:readGET /search?status=draftstatus=publishedfor users withoutcontent:read_draftstaxonomies:manage/menus:managetokenssettings:read/settings:managetokensPOST /media/:id/confirmrequireOwnerPermcheckError handling fixes (4)
POST /content/:col/:id/publishEmDashValidationError, returnVALIDATION_ERRORPOST /content/:col/:id/unpublishDELETE /content/:col/:id/schedulewhoamirefresh corrupts stored credentialswhoami{ data: ... }envelope from token refresh responseData integrity fixes (4)
withTransactionentry_idPOST /content/:col/:slug/terms/:taxwithTransactionresolvedIdfrom existing itemClient + CLI fixes (7)
client.taxonomies()returnsundefined{ taxonomies }not{ items }client.menus()returnsundefined.itemsrefreshInterceptorparses wrong envelope{ data: { access_token } }and bare shapesconvertDataForReadon overlay data{ success: true }in--jsonmodeimport X from,export { } from,node:fs/promisesDefense in depth (12)
requireDbguardsrequireDbguardssource: "theme"(undeletable)"user" | "import"client_id_emdash_oauth_clientsMath.max(1, ...).max(2048)to sections, comments, users schemas%/_/\withESCAPE '\\'clausesecureCompareletsingleton violates globalThis conventionSymbol.forpatternmode: 0o700Performance (1)
countEntriesForTermswith chunking atSQL_BATCH_SIZEIntentional breaking changes
All three are security fixes where the old behavior was the bug:
content:read_drafts(wascontent:read). Subscribers can no longer fetch individual revisions.?status=draftforced topublishedfor anonymous/subscriber users. Draft search now requirescontent:read_drafts.source: "theme". Only"user"and"import"are accepted via API.Skipped (3)
Type of change
Checklist
pnpm typecheckpasses (pre-existing@oslojsimport errors excluded)pnpm lintpasses (0 diagnostics)pnpm testpasses (or targeted tests for my change)pnpm formathas been runAI-generated code disclosure
Driven by Claude (Opus 4.6) with systematic audit against the deployed beta site, 6 parallel adversarial review agents for bug discovery, and 2 adversarial review passes on the final diff.
Test output
Full unit suite: 2043 passing, 0 new failures (4 pre-existing failures from
@oslojsimport in secrets module).