Skip to content

enable page plugin contributions for public routes and enhance GTM plugin#156

Open
ryota-yamada-jp wants to merge 17 commits into
emdash-cms:mainfrom
ryota-yamada-jp:feat/plugin-gtm
Open

enable page plugin contributions for public routes and enhance GTM plugin#156
ryota-yamada-jp wants to merge 17 commits into
emdash-cms:mainfrom
ryota-yamada-jp:feat/plugin-gtm

Conversation

@ryota-yamada-jp
Copy link
Copy Markdown

What does this PR do?

This PR enables the Google Tag Manager (GTM) plugin to function correctly on public pages and adds the missing advanced configuration options for GTM.

1. Enable Page Plugin Contributions for Public Routes
Previously, the Astro middleware (onRequest in middleware.ts) bypassed EmDashRuntime initialization for unauthenticated public requests as a performance optimization. This inadvertently prevented trusted plugins from injecting scripts via the page:fragments and page:metadata hooks on public pages, effectively breaking plugins like Google Tag Manager that need to run for all visitors.

  • Removed the early return for public requests so getRuntime(config) is properly initialized.
  • Exposed collectPageMetadata and collectPageFragments methods on locals.emdash so Astro components like EmDashBodyStart can correctly render the injected HTML.
  • Added comprehensive unit tests in middleware.test.ts to ensure runtime initialization and method binding work as expected even without an active session.

2. GTM Plugin Enhancements

  • Added advanced options to customize the Data Layer Variable Name (defaults to dataLayer), GTM Script URL (defaults to https://www.googletagmanager.com/gtm.js), and GTM NoScript URL (defaults to https://www.googletagmanager.com/ns.html) to support Server-Side GTM (SGTM) setups.

Type of change

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

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm --silent lint:json | jq '.diagnostics | length' returns 0
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code

Screenshots / test output

**Test Output**
✓ tests/unit/astro/middleware.test.ts (1 test)
  ✓ EmDash Middleware (1)
    ✓ initializes runtime and attaches methods for all pages

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 3, 2026

⚠️ No Changeset found

Latest commit: a68a9d8

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
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 3, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@ryota-yamada-jp ryota-yamada-jp changed the title Feat/plugin gtm enable page plugin contributions for public routes and enhance GTM plugin Apr 3, 2026
@ryota-yamada-jp
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@ryota-yamada-jp
Copy link
Copy Markdown
Author

recheck

github-actions Bot added a commit that referenced this pull request Apr 3, 2026
@ryota-yamada-jp
Copy link
Copy Markdown
Author

recheck

@z-ryota-yamada
Copy link
Copy Markdown

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Apr 3, 2026
@ascorbic
Copy link
Copy Markdown
Collaborator

Hey! #119 fixed the issue with hooks for anonymous visitors, so this will need some reworking.

@ryota-yamada-jp
Copy link
Copy Markdown
Author

@ascorbic Okay! I will fix this. Please wait a moment.

@ryota-yamada-jp ryota-yamada-jp marked this pull request as draft April 15, 2026 11:01
@github-actions github-actions Bot added size/M and removed size/L labels Apr 15, 2026
@ryota-yamada-jp ryota-yamada-jp marked this pull request as ready for review April 15, 2026 11:20
@ryota-yamada-jp
Copy link
Copy Markdown
Author

@ascorbic I did fix it!

@github-actions github-actions Bot added size/L and removed size/M labels Apr 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This PR has been inactive for 14 days. It will be closed automatically in 7 days if there is no further activity.

If you're still working on this, please push an update or leave a comment.

@github-actions github-actions Bot added the stale label May 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

PR template validation failed

Please fix the following issues by editing your PR description:

See CONTRIBUTING.md for the full contribution policy.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 11, 2026

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

emdash

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

create-emdash

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

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

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 05f4cb4

@github-actions github-actions Bot removed the stale label May 12, 2026
@github-actions github-actions Bot added stale review/needs-review No maintainer or bot review yet and removed stale labels May 26, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label May 31, 2026
Copy link
Copy Markdown
Contributor

@emdashbot emdashbot Bot left a comment

Choose a reason for hiding this comment

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

The PR description claims two changes: (1) middleware fixes to enable page:fragments/page:metadata for public routes, and (2) GTM plugin enhancements. The actual diff contains only a new @emdash-cms/plugin-google-tag-manager package. The middleware narrative is stale—the fixes described were already merged to main in a prior release (see packages/core/CHANGELOG.md line 713 and the current middleware.ts). Reviewers should ignore the description and evaluate only the new plugin.

The plugin is a reasonable addition, but the implementation introduces stored XSS vulnerabilities and has several convention gaps that need fixing before merge:

  1. Unescaped user input in page:fragments. The hook interpolates KV values (gtmScriptUrl, gtmNoScriptUrl, dataLayerName, containerId) directly into JavaScript string literals and HTML attributes without escaping or validation. A single quote in a URL breaks the JS string; a double quote breaks the HTML attribute. The core renderFragment helper does not protect against this—for inline-script it only escapes </, and for html it returns the markup verbatim.
  2. No input validation on the admin form. Arbitrary strings are persisted to KV. URLs should be validated as https:// and the container ID should match the GTM-XXXXXXX format.
  3. Deprecated capability. The plugin uses "page:inject", which is in the deprecation window. New plugins should use "hooks.page-fragments:register".
  4. Missing changeset. A new published package needs a changeset to trigger a release.
  5. No tests. Other plugins in packages/plugins/ have unit/integration tests; this package has none.
  6. Missing approved Discussion. Per AGENTS.md, feature PRs require a prior approved Discussion.

Sandboxed plugins currently have no Lingui integration path (none of the existing plugins localize Block Kit strings), so I’m not flagging the hardcoded English as a dedicated finding—it’s a systemic gap.

Once the XSS/validation issues are fixed, the capability is updated, tests are added, and a changeset is included, this plugin is good to merge.

Comment on lines +22 to +26
code: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'${gtmScriptUrl}?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','${dataLayerName}','${containerId}');`,
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 page:fragments hook interpolates user-controlled KV values (gtmScriptUrl, dataLayerName, containerId) into single-quoted JavaScript string literals without escaping. A single quote or backslash in any of these values breaks the string literal and allows arbitrary code execution on every public page. For example, a gtmScriptUrl of https://evil.com/x';alert(1);var x=' would execute alert(1). The core renderFragment helper only escapes </ sequences in inline scripts; it does not escape single quotes. The plugin must sanitize before interpolation—at minimum by escaping \ and ', or by segmenting the values safely, and ideally by validating against expected formats.

{
kind: "html",
placement: "body:start",
html: `<noscript><iframe src="${gtmNoScriptUrl}?id=${containerId}" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>`,
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 html fragment interpolates gtmNoScriptUrl and containerId directly into an <iframe src> attribute without HTML-escaping or URL-encoding. A double quote in either value breaks out of the src attribute and injects arbitrary HTML. An ampersand in containerId injects extra query parameters. The renderFragment helper returns html contributions verbatim, so the plugin must escape attribute metacharacters ("&quot;, &&amp;) and use encodeURIComponent for the query string.

Comment on lines +54 to +65
if (interaction.type === "form_submit" && interaction.action_id === "save_gtm") {
const values = interaction.values || {};
await ctx.kv.set("settings:gtmContainerId", values.gtm_container_id || "");
await ctx.kv.set("settings:gtmDataLayerName", values.gtm_data_layer_name || "dataLayer");
await ctx.kv.set(
"settings:gtmScriptUrl",
values.gtm_script_url || "https://www.googletagmanager.com/gtm.js",
);
await ctx.kv.set(
"settings:gtmNoScriptUrl",
values.gtm_noscript_url || "https://www.googletagmanager.com/ns.html",
);
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 admin form submission handler stores arbitrary strings from interaction.values directly into KV without validation. gtm_script_url and gtm_noscript_url should be validated as HTTPS URLs (or at least valid URLs), and gtm_container_id should match the expected GTM-XXXXXXX format. Without validation, invalid or malicious input can break every public page or be exploited as stored XSS via the page:fragments hook.

format: "standard",
entrypoint: "@emdash-cms/plugin-google-tag-manager/sandbox",
options: {},
capabilities: ["page:inject"],
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 plugin declares the deprecated capability "page:inject". This name exists only in the deprecation window and is normalized to "hooks.page-fragments:register" at runtime. New plugins should use the current canonical capability name to avoid future breakage.

Suggested change
capabilities: ["page:inject"],
capabilities: ["hooks.page-fragments:register"],

],
"author": "Your Name",
"license": "MIT",
"dependencies": {},
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 author field contains the placeholder value "Your Name". Update it to the actual author or remove the field.

Suggested change
"dependencies": {},
"author": "Ryota Yamada",

@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 May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core area/plugins Awaiting author response bot:review Trigger an emdashbot code review on this PR cla: signed review/awaiting-author Reviewed; waiting on the author to respond size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants