Skip to content

Bump ask-bonk/ask-bonk from c39e982defd0114385df54e72012a3fc4333c4d4 to 5704e1685d6efc3951efb9166a8bd17a9ca28509 - #1

Merged
ascorbic merged 2 commits into
mainfrom
dependabot/github_actions/ask-bonk/ask-bonk-5704e1685d6efc3951efb9166a8bd17a9ca28509
Apr 8, 2026
Merged

Bump ask-bonk/ask-bonk from c39e982defd0114385df54e72012a3fc4333c4d4 to 5704e1685d6efc3951efb9166a8bd17a9ca28509#1
ascorbic merged 2 commits into
mainfrom
dependabot/github_actions/ask-bonk/ask-bonk-5704e1685d6efc3951efb9166a8bd17a9ca28509

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Apr 1, 2026

Copy link
Copy Markdown
Contributor

Bumps ask-bonk/ask-bonk from c39e982defd0114385df54e72012a3fc4333c4d4 to 5704e1685d6efc3951efb9166a8bd17a9ca28509.

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Bumps [ask-bonk/ask-bonk](https://github.com/ask-bonk/ask-bonk) from c39e982defd0114385df54e72012a3fc4333c4d4 to 5704e1685d6efc3951efb9166a8bd17a9ca28509.
- [Release notes](https://github.com/ask-bonk/ask-bonk/releases)
- [Commits](Cloudflare-Studio/ask-bonk@c39e982...5704e16)

---
updated-dependencies:
- dependency-name: ask-bonk/ask-bonk
  dependency-version: 5704e1685d6efc3951efb9166a8bd17a9ca28509
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code labels Apr 1, 2026
@changeset-bot

changeset-bot Bot commented Apr 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 407ebb3

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 4, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 407ebb3 Apr 08 2026, 09:05 AM
@pkg-pr-new

pkg-pr-new Bot commented Apr 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

emdash

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

create-emdash

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

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

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 407ebb3

jdevalk added a commit to jdevalk/emdash that referenced this pull request Apr 7, 2026
- Fix emdash-cms#1: Use @phosphor-icons/react instead of lucide-react
- Fix emdash-cms#2: Send minimal patch { image } instead of spreading stale seo props
- Fix emdash-cms#3: Only show OG Image next to the featured_image field, not all image fields
- Fix emdash-cms#4: Only add description text for the featured_image field
- Fix emdash-cms#5: Add responsive breakpoint (grid-cols-1 md:grid-cols-2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ascorbic ascorbic closed this in 12d73ff Apr 7, 2026
@dependabot @github

dependabot Bot commented on behalf of github Apr 7, 2026

Copy link
Copy Markdown
Contributor Author

OK, I won't notify you again about this release, but will get in touch when a new version is available. You can also ignore all major, minor, or patch releases for a dependency by adding an ignore condition with the desired update_types to your config file.

If you change your mind, just re-open this PR and I'll resolve any conflicts on it.

@dependabot
dependabot Bot deleted the dependabot/github_actions/ask-bonk/ask-bonk-5704e1685d6efc3951efb9166a8bd17a9ca28509 branch April 7, 2026 22:15
@ascorbic
ascorbic restored the dependabot/github_actions/ask-bonk/ask-bonk-5704e1685d6efc3951efb9166a8bd17a9ca28509 branch April 8, 2026 09:03
@ascorbic ascorbic reopened this Apr 8, 2026
@ascorbic
ascorbic merged commit e982741 into main Apr 8, 2026
39 of 42 checks passed
@ascorbic
ascorbic deleted the dependabot/github_actions/ask-bonk/ask-bonk-5704e1685d6efc3951efb9166a8bd17a9ca28509 branch April 8, 2026 15:46
BenjaminPrice added a commit to BenjaminPrice/emdash that referenced this pull request Apr 11, 2026
The enrollment component for the authenticator-app path in the
first-run setup wizard. Handles the entire TOTP setup sub-flow
behind one component: fetch secret → render QR → collect code →
verify → success bridge → recovery codes → continue.

Five internal stages driven by a discriminated-union state:

  1. "loading"         — POST /admin-totp, render a spinner
  2. "qr"              — pure-white QR (240×240 PNG data URL), base32
                          fallback behind a "Can't scan?" disclosure,
                          single 6-digit input with inputMode="numeric"
                          and autoComplete="one-time-code" (NOT six
                          separate boxes — single input is the design
                          spec from Phase 2 Pass 4)
  3. "success-bridge"  — 800ms green check, "Authenticator connected"
                          (200ms when prefers-reduced-motion is set)
  4. "recovery"        — 10 codes in a <ol> with aria-label, copy-all
                          + download buttons, required "I have saved
                          these" checkbox that gates Continue, and a
                          beforeunload guard that fires until the
                          checkbox is ticked

Copy decisions sourced from the Phase 2 design review:

  - QR renders on pure #FFFFFF, NOT kumo-base, for scanner contrast
  - After 2 consecutive failures, show a clock-drift hint ("If your
    codes keep failing, check that your phone's time is set
    automatically") — calendar drift is the emdash-cms#1 TOTP failure mode
    and users rarely self-diagnose it
  - Error copy tells the user WHY the code might be wrong, never
    the generic "invalid TOTP"
  - Paste handler strips whitespace and hyphens so "123 456" and
    "123-456" both work
  - Auto-submit when 6 digits are entered — matches the
    authenticator-app ergonomic of "type the code, done"

The recovery codes screen is the one that could silently lose a
user's data if it's handled carelessly. Specifically:

  - beforeunload guard fires ONLY until the checkbox is ticked,
    not forever (otherwise the Continue button becomes its own
    noise source). Attaches and detaches via useEffect.
  - "Copy all" writes the codes to the clipboard with newlines
  - "Download" writes a plain-text file with a header explaining
    what the codes are for
  - The Continue button is disabled until the checkbox is ticked

All text goes through Lingui t\`...\` macros so the strings show
up in the existing locale extraction. No raw English.

Not wired into SetupWizard yet — next commit.
fmhall pushed a commit to fmhall/emdash that referenced this pull request Apr 13, 2026
* Add OG Image field to content editor

The `_emdash_seo` table and content API already support `seo_image`, but
the admin UI had no way to set it. This adds:

- `SeoImageField` component using the existing `MediaPickerModal`
- 2-column grid layout placing the OG Image next to the Featured Image
- `description` prop on `ImageFieldRenderer` for helper text below images
- Preserve `seo.image` in `SeoPanel.emitChange` so sidebar edits don't
  clear the image

Closes emdash-cms#327

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: format

* Add changeset and fix stale seo.image in SeoPanel

- Add changeset for @emdash-cms/admin patch release
- Remove image from SeoPanel.emitChange to avoid overwriting
  a freshly-selected OG image with a stale prop value

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review feedback on OG Image field

- Fix emdash-cms#1: Use @phosphor-icons/react instead of lucide-react
- Fix emdash-cms#2: Send minimal patch { image } instead of spreading stale seo props
- Fix emdash-cms#3: Only show OG Image next to the featured_image field, not all image fields
- Fix emdash-cms#4: Only add description text for the featured_image field
- Fix emdash-cms#5: Add responsive breakpoint (grid-cols-1 md:grid-cols-2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: format

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
fmhall pushed a commit to fmhall/emdash that referenced this pull request Apr 13, 2026
Bumps [ask-bonk/ask-bonk](https://github.com/ask-bonk/ask-bonk) from c39e982defd0114385df54e72012a3fc4333c4d4 to 5704e1685d6efc3951efb9166a8bd17a9ca28509.
- [Release notes](https://github.com/ask-bonk/ask-bonk/releases)
- [Commits](Cloudflare-Studio/ask-bonk@c39e982...5704e16)

---
updated-dependencies:
- dependency-name: ask-bonk/ask-bonk
  dependency-version: 5704e1685d6efc3951efb9166a8bd17a9ca28509
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
ascorbic added a commit that referenced this pull request May 13, 2026
Addresses 7 findings from the round-6 adversarial review and
documents the eighth.

#1 (high) Capability consent bypass [registry.ts, RegistryPluginDetail.tsx]
The drift check was gated on the client sending acknowledgedDeclaredAccess.
If the publisher's release record had no extension, the admin saw an
empty permission dialog, omitted the acknowledgement, and the server
skipped the check entirely -- letting a bundle whose manifest declares
real capabilities slip through behind an empty consent UI. Server now
extracts capabilities from the bundle manifest after download and
refuses with DECLARED_ACCESS_REQUIRED if the bundle declares any
capabilities and no acknowledgement was sent. Client always sends the
list (empty when no extension) so the new server check is always armed.

#2 (high) Concurrent install bundle deletion [registry.ts]
Two parallel installs of the same (did, slug, version) both passed the
pre-existing-row check, both uploaded to the same deterministic R2
prefix, and one then won the state-row PK race. The loser's catch block
deleted the R2 bundle the winner had just written. On state-write
failure we now re-query the state row: if a winner exists, we lost the
race and must not touch the R2 bundle. Cleanup runs only when the
failure is a real DB error, not a lost concurrent install.

#3 (high) SSRF via DNS-resolving public hostnames [registry.ts, ssrf.ts moved]
Literal-IP blocklist alone left a DNS-rebinding gap: any public DNS
service resolving an attacker-chosen hostname to loopback / RFC1918 /
169.254.169.254 passed the URL check. The import pipeline already
shipped resolveAndValidateExternalUrl which does Cloudflare DoH
resolution and rejects on any forbidden resolved address; reuse it
for artifact downloads. Move src/import/ssrf.ts to src/security/ssrf.ts
to reflect that it's not import-specific. Leave a re-export shim at
the old path so 13 existing callers keep working unchanged. Add
#security/* path alias.

#5 (high) Aggregator-supplied handles treated as verified [PublisherHandle.tsx]
usePublisherHandle returned status: 'ok' with the aggregator-supplied
handle whenever one was present, skipping local DID->handle round-trip.
A compromised aggregator could label an attacker DID as e.g.
'stripe.com' and the UI would render it as verified. Always run
LocalActorResolver via resolveDidToHandle; use the aggregator handle
only for a cross-check. If the aggregator's claim differs from the
verified handle, mark the publisher invalid.

#6 (medium) Postgres migration 038 schema-qualification [038_registry_plugin_state.ts]
The columns probe queried information_schema.columns without filtering
by table_schema. A _plugin_state table in another schema (multi-tenant
Postgres, per-test schemas) could make the migration skip the column
adds. Filter by table_schema = current_schema().

#7 (medium) Install errors leak full artifact URLs [registry.ts]
fetchArtifact recorded each full URL in the joined error message that
bubbled up to the admin client. Artifacts hosted on storage backends
often carry presigned tokens in the query string; failed installs were
leaking those into HTTP responses and logs. Strip query and fragment
when building client-visible errors (origin + path only); log the full
URL server-side for debugging.

#8 (medium) Credentialed aggregator URLs accepted [config.ts]
validateAggregatorUrl accepted https://user:pass@example.com.
The normalized URL ends up in the admin manifest and is shipped to
every admin browser; browser fetch() also rejects credentialed URLs
outright. Reject them at config-validation time.

#4 (high, documented not fixed) Aggregator-trust-root scope [types.ts]
Full MST proof / publisher signature verification is not in this PR;
the server still trusts the aggregator-supplied (did, slug, checksum,
artifact URL). Expand the JSDoc on EmDashConfig.experimental.registry
to spell out exactly what the v1 trust contract is, what EmDash does
verify independently (checksum, manifest id/version/capabilities), and
what it doesn't (release-record signatures, replay). Recommendation:
point aggregatorUrl only at an aggregator you operate or trust at
centralized-source level until signature verification lands.
ascorbic added a commit that referenced this pull request May 14, 2026
…#1011)

* fix(deps): catalog-pin zod so trusted plugins typecheck

Astro bundles its own Zod and re-exports it as 'astro/zod'. Trusted
plugins like @emdash-cms/plugin-forms import their route schemas via
'astro/zod', then pass those schemas to definePlugin() in core. With
emdash's 'zod: ^4.3.5' resolving independently of Astro's caret,
pnpm kept two Zod 4 patches in the tree (e.g. 4.3.6 alongside 4.4.1).

Zod 4 embeds its semver in the type system, so two patches of Zod 4
are not assignable to each other. The forms plugin's route schemas
(ZodObject<..., $strip>) were rejected by PluginRoute<TInput>['input']
(ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>) with
'Type "3" is not assignable to type "4"' on the internal version
field. The native definePlugin overload silently failed, TS fell
through to the StandardPluginDefinition overload, and reported a
misleading 'id does not exist' error -- masking 8 cascading errors.

Catalog-pinning Zod forces a single workspace-wide instance and
restores normal overload resolution. No code changes needed in core
or plugins/forms.

Also adds a pnpm-workspace.yaml comment explaining the gotcha so the
next person doesn't bump emdash's pin past Astro's range.

* feat(registry): experimental decentralized plugin registry

Adds opt-in support for installing sandboxed plugins from the
decentralized plugin registry described in RFC #694. Enabled via
`experimental.registry.aggregatorUrl` in the EmDash integration
options; when set, the admin UI replaces marketplace browse/install
with the registry path.

Server: new install handler (RFC verification chain), endpoint at
POST /_emdash/api/admin/plugins/registry/install, migration 038 adds
`source = 'registry'` plus `registry_publisher_did` /
`registry_slug` columns on `_plugin_state`, runtime sync split into
shared marketplace + registry tiers via a normalized opaque
`r_<hash>` plugin id.

Browser: aggregator XRPC calls go direct from the admin UI via
@emdash-cms/registry-client. Install POST runs through the server.
Includes a minimum-release-age policy with a per-publisher exclude
allowlist, enforced both client-side (UX) and server-side (gate).

Hardening (5 rounds of adversarial review): bundle id rewritten to
the derived pluginId before storage, aggregator identity
cross-checked, artifact and aggregator URLs validated for SSRF
(https-only in prod, IPv6 brackets handled), per-request and total
budgets on every outbound call, decompressed bundle capped at 256
KiB to match the RFC publish-time limit, migration 038 idempotent
on both SQLite and Postgres.

Known gaps tracked for follow-up: full MST signature verification
against the publisher's PDS, multibase multihash decoding (hex SHA-256
is accepted today), registry plugin update + uninstall handlers.

* fix(registry-lexicons): drop codegen from build script

The generated lexicon types are committed to git so consumers don't
need the codegen toolchain. Running lex-cli generate as part of the
default build pipeline broke Cloudflare Pages builds for sites that
pull registry-lexicons in transitively, because lex-cli imports
lex.config.ts directly and Node in the CF Pages build environment
can't load .ts natively.

Codegen moves to a separate `regen` script (`pnpm regen` runs
codegen + full build). Maintainers run it when they edit the
lexicons; consumers just consume the committed output.

* fix(registry): copilot review fixes

- Drift check normalizes capabilities (filter strings, dedupe, sort) on
  both browser and server so reorderings or junk entries can't trigger
  spurious rejection. Adds a shared normalizeCapabilities helper in
  registry/config.ts and a mirror in admin/lib/api/registry.ts.
- RegistryPluginDetail no longer trusts the aggregator-supplied
  ext?.capabilities as already-validated string[]; runs it through
  normalizeCapabilities before display and before send.
- Fix stale '32 MiB' docstring on extractBundle (cap is actually
  MAX_DECOMPRESSED_BUNDLE_BYTES = 256 KiB).
- Fix plugin-id.ts JSDoc: validatePluginIdentifier regex is
  /^[a-z][a-z0-9_-]*$/ (allows hyphens); the prior 'cannot collide
  with marketplace ids' claim was too strong and is now framed as
  'syntactically distinct, plus an explicit pre-existing-row check
  in the install handler.'

* fix(registry): address review findings + CI failures

CI fixes:
- Rename normalizeCapabilities -> canonicalCapabilitiesForDriftCheck
  to avoid namespace clash with the existing capability normalizer
  exported from @emdash-cms/plugin-types via core's index. The old
  name shadowed plugin-types' helper at the top level of core's dist,
  which made the definePlugin() overload set look ambiguous to TS in
  plugins/forms and caused a typecheck cascade there.
- [...seen].toSorted() instead of [...seen].sort() to clear the
  e18e/prefer-spread-syntax + unicorn/no-array-sort lint errors.

Review findings (ask-bonk[bot]):
- HIGH: drift check tripped on every install when the release record's
  extension was empty. The browser now omits acknowledgedDeclaredAccess
  when capabilities is empty, opting out of the server-side drift gate
  for the (currently common) case where publishers haven't filled in
  the extension block. The bundle's real capabilities are still bound
  to the checksum-verified bytes.
- HIGH: DID-only publishers (no resolvable handle) could be linked from
  the browse grid but never installed because the server rejects
  handles without a '.'. Cards now render as non-interactive with a
  'Publisher handle unresolved' badge; the detail page surfaces a
  matching warning and disables Install.
- MEDIUM: registry-enabled sites were unconditionally routing existing
  marketplace plugin detail URLs to RegistryPluginDetail, breaking deep
  links. Detail-route selection now discriminates by param shape
  (pluginId.includes('/')) rather than the manifest flag.
- MEDIUM: state-row write failure after storeBundleInR2 left orphan
  bundles. Best-effort cleanup in the catch via deleteBundleFromR2.
- LOW: parseDurationSeconds runs on the user-supplied integration
  option per install (not the already-normalized manifest shape). Wrap
  in try/catch and surface as REGISTRY_POLICY_INVALID rather than
  letting it bubble to a generic INSTALL_FAILED.
- LOW: validator-pattern doc drift in plugin-id.ts (already fixed in
  the prior commit).

* fix(registry): move registry config types to their own module

The new RegistryConfig + ExperimentalConfig interfaces lived alongside
definePlugin's overloads in astro/integration/runtime.ts. tsdown +
rolldown's chunking decided to inline a bigger subset of plugin-related
types into the entry chunk as a result, which broke definePlugin()
overload resolution for trusted plugins building against core's dist
on CI (plugins/forms failed with 'id does not exist in type
StandardPluginDefinition').

Move both types to packages/core/src/registry/types.ts (still re-exported
from runtime.ts for backwards compatibility) so the chunking matches
main's layout and definePlugin's overloads resolve as before.

* fix(registry): wire up real-world install + display polish

Aggregator (apps/aggregator):
- Add CORS to /xrpc/* so the admin UI can call it from any origin
  (preflight 204, response headers on every method). Aggregator is a
  public read-only service; * is correct here.

Core (packages/core):
- Implement multibase-multihash checksum verification by re-encoding
  our SHA-256 digest in the same 'b<base32>' shape the registry CLI
  produces, rather than decoding the publisher's checksum. Same trust
  contract, no base32 decoder needed. Bare hex SHA-256 still accepted
  as a convenience fallback.
- Switch install handler to take 'did' (not handle) so packages whose
  handle the aggregator couldn't resolve are still installable. The
  browser resolves handle→DID via the aggregator before posting and
  sends DID directly; the server skips resolvePackage and goes
  straight to getPackage.
- Coerce 'experimental.registry' bare-string shorthand into the full
  RegistryConfig object via 'coerceRegistryConfig'. 'registry:
  "..."' is now equivalent to 'registry: { aggregatorUrl: "..." }'.
- Plumb 'experimental' through the integration's serializableConfig
  so the manifest endpoint actually sees the user's registry block.
  Previously it was being stripped, so the admin UI never branched to
  the registry path.
- Split RegistryConfig + ExperimentalConfig types into their own
  module (registry/types.ts) so they don't get bundled into the
  astro/integration/runtime.ts dist chunk -- the wider inlining was
  breaking definePlugin overload resolution for trusted plugins
  building against core's dist.

Admin (packages/admin):
- New <PublisherHandle> component + usePublisherHandle hook with
  tri-state result ('ok' / 'invalid' / 'missing'). Renders @handle,
  'Unverified publisher' (red), or DID respectively. Uses
  @atcute/identity-resolver's LocalActorResolver for bidirectional
  handle verification, localStorage-cached for 24h.
- Detail page disables install on 'invalid' status (publisher claims
  a handle that doesn't round-trip back to its DID -- impersonation
  risk). Surfaces 'We couldn't verify this publisher's identity'
  alert in plain language.
- Detail page reads installed state from fetchPlugins() and swaps
  the Install button to 'Installed' (disabled) when the package
  already has a 'source = "registry"' row matching its DID + slug.
  React Query's existing ['plugins'] invalidation handles the
  post-install UI update.
- Browse cards reuse <PublisherHandle> (variant='card') and link by
  handle when available, DID otherwise. Detail page parses either
  form from the URL.
- Browser sends 'did' (not handle) in the install POST.

Workspace:
- '@cloudflare/kumo' moved to the pnpm catalog and bumped to ^1.16.0
  workspace-wide. Older 1.10.0 was missing Sidebar export and being
  hoisted into the admin via packages/blocks's transitive dep.
- Add '@atcute/multibase' to core (for checksum encoding) and
  '@atcute/identity-resolver' to admin (for DID->handle resolution).
- Update DEFAULT_AGGREGATOR_URL + DiscoveryClient doc example from
  'experimental-registry.emdashcms.com' to 'registry.emdashcms.com'
  (the actual production host).

* fix(registry): adversarial review round 6 findings

Addresses 7 findings from the round-6 adversarial review and
documents the eighth.

#1 (high) Capability consent bypass [registry.ts, RegistryPluginDetail.tsx]
The drift check was gated on the client sending acknowledgedDeclaredAccess.
If the publisher's release record had no extension, the admin saw an
empty permission dialog, omitted the acknowledgement, and the server
skipped the check entirely -- letting a bundle whose manifest declares
real capabilities slip through behind an empty consent UI. Server now
extracts capabilities from the bundle manifest after download and
refuses with DECLARED_ACCESS_REQUIRED if the bundle declares any
capabilities and no acknowledgement was sent. Client always sends the
list (empty when no extension) so the new server check is always armed.

#2 (high) Concurrent install bundle deletion [registry.ts]
Two parallel installs of the same (did, slug, version) both passed the
pre-existing-row check, both uploaded to the same deterministic R2
prefix, and one then won the state-row PK race. The loser's catch block
deleted the R2 bundle the winner had just written. On state-write
failure we now re-query the state row: if a winner exists, we lost the
race and must not touch the R2 bundle. Cleanup runs only when the
failure is a real DB error, not a lost concurrent install.

#3 (high) SSRF via DNS-resolving public hostnames [registry.ts, ssrf.ts moved]
Literal-IP blocklist alone left a DNS-rebinding gap: any public DNS
service resolving an attacker-chosen hostname to loopback / RFC1918 /
169.254.169.254 passed the URL check. The import pipeline already
shipped resolveAndValidateExternalUrl which does Cloudflare DoH
resolution and rejects on any forbidden resolved address; reuse it
for artifact downloads. Move src/import/ssrf.ts to src/security/ssrf.ts
to reflect that it's not import-specific. Leave a re-export shim at
the old path so 13 existing callers keep working unchanged. Add
#security/* path alias.

#5 (high) Aggregator-supplied handles treated as verified [PublisherHandle.tsx]
usePublisherHandle returned status: 'ok' with the aggregator-supplied
handle whenever one was present, skipping local DID->handle round-trip.
A compromised aggregator could label an attacker DID as e.g.
'stripe.com' and the UI would render it as verified. Always run
LocalActorResolver via resolveDidToHandle; use the aggregator handle
only for a cross-check. If the aggregator's claim differs from the
verified handle, mark the publisher invalid.

#6 (medium) Postgres migration 038 schema-qualification [038_registry_plugin_state.ts]
The columns probe queried information_schema.columns without filtering
by table_schema. A _plugin_state table in another schema (multi-tenant
Postgres, per-test schemas) could make the migration skip the column
adds. Filter by table_schema = current_schema().

#7 (medium) Install errors leak full artifact URLs [registry.ts]
fetchArtifact recorded each full URL in the joined error message that
bubbled up to the admin client. Artifacts hosted on storage backends
often carry presigned tokens in the query string; failed installs were
leaking those into HTTP responses and logs. Strip query and fragment
when building client-visible errors (origin + path only); log the full
URL server-side for debugging.

#8 (medium) Credentialed aggregator URLs accepted [config.ts]
validateAggregatorUrl accepted https://user:pass@example.com.
The normalized URL ends up in the admin manifest and is shipped to
every admin browser; browser fetch() also rejects credentialed URLs
outright. Reject them at config-validation time.

#4 (high, documented not fixed) Aggregator-trust-root scope [types.ts]
Full MST proof / publisher signature verification is not in this PR;
the server still trusts the aggregator-supplied (did, slug, checksum,
artifact URL). Expand the JSDoc on EmDashConfig.experimental.registry
to spell out exactly what the v1 trust contract is, what EmDash does
verify independently (checksum, manifest id/version/capabilities), and
what it doesn't (release-record signatures, replay). Recommendation:
point aggregatorUrl only at an aggregator you operate or trust at
centralized-source level until signature verification lands.

* fix(registry): adversarial review round 7 followups

Two LOW findings from the round-7 review (PR #1011 comment).

NSID exact-match in RegistryPluginDetail.tsx
  Round-6 left a startsWith() match on the release-extension key.
  RFC 0001 fixes the NSID for the release extension; accepting prefix
  variants (...releaseExtensionV2, ...releaseExtension.deprecated)
  would let a publisher render a different capability list than the
  canonical key would. Use exact-equality keyed lookup.

Registry plugin uninstall affordance in PluginManager.tsx
  Registry-installed plugins appear in PluginManager but the Uninstall
  button is gated on isMarketplace. Admins see a permanent-looking
  install with no way to remove it short of editing the DB and R2 by
  hand. Add an inline note for source === 'registry' rows that says
  uninstall isn't available yet and points the admin at the disable
  toggle. Full uninstall handler lands in a follow-up PR.
pitscher added a commit to pitscher/emdash that referenced this pull request May 22, 2026
pitscher added a commit to pitscher/emdash that referenced this pull request May 24, 2026
pitscher added a commit to pitscher/emdash that referenced this pull request May 28, 2026
pitscher added a commit to pitscher/emdash that referenced this pull request May 29, 2026
pitscher added a commit to pitscher/emdash that referenced this pull request May 30, 2026
ascorbic pushed a commit that referenced this pull request May 31, 2026
* feat(i18n/de): Fix inconsistent language usage

* feat(i18n/de): Add German translations #1

* feat(i18n/de): Fix incorrect translation of 'scopes'

* feat(i18n/de): Add German translations #2

* feat(i18n/de): Fix typo

* feat(i18n/de): Translate ICU message format plural strings

* feat(i18n/de): Add German translations

* feat(i18n/de): Add German translations

* feat(i18n/de): Translate ICU message format plural strings

* feat(i18n/de): Update comment after verifying that msgid "Add" & msgid "Edit" cannot be translated properly

* feat(i18n/de): Add changes to all German mgstr strings after consistency check

* feat(i18n/de): Add final corrections after final review

* feat(i18n/de): Adjust user and role related translations

* feat(i18n/de): Adjust prefix for new collection item creation page

* feat(i18n/de): Add language tweaks and comments for temp fixes I had to use

* feat(i18n/de): Fix 5 semantic mistranslations in permission/scope labels
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci bot cla: signed dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code size/XS

1 participant