Skip to content

feat: auto rel=noopener on target=_blank links (configurable)#9

Merged
PhilMeyr merged 3 commits into
masterfrom
feat/blank-link-rel
Jul 1, 2026
Merged

feat: auto rel=noopener on target=_blank links (configurable)#9
PhilMeyr merged 3 commits into
masterfrom
feat/blank-link-rel

Conversation

@PhilMeyr

@PhilMeyr PhilMeyr commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What

Anchors with target="_blank" (<cta>, <button>, <item>) now emit a rel automatically, hardening links against reverse-tabnabbing.

  • New config ActiveMail.config.blank_link_rel (default "noopener"); set nil to disable.
  • An explicit rel="…" on the tag always wins and is never duplicated (rel added to IGNORED_ON_PASSTHROUGH).
  • Emission centralized in Base#link_attributes (renamed from target_attribute), shared by all anchor components — no per-project template edits.

Why

Generic, consumer-agnostic default: security without stripping the referrer (analytics-safe). Each project overrides via config.

Tests

Component tests (cta/button/menu) + configuration tests: rel on _blank, absent otherwise, config override, nil opt-out, explicit-rel precedence without duplication. rake test / srb tc / rubocop green.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6f1310792

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

sig { params(node: Nokogiri::XML::Node).returns(String) }
def target_attribute(node)
node.attributes['target'] ? %( target="#{escape_attr(node.attributes['target'])}") : ''
def link_attributes(node)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve target_attribute for custom components

README.md:285-287 documents target_attribute as an available Base helper for custom components, so any existing component that calls it from #transform will now raise NoMethodError as soon as it renders after upgrading to this 1.2.0 change. Since link_attributes is a superset, keep a target_attribute wrapper/alias for compatibility rather than removing the documented helper outright.

Useful? React with 👍 / 👎.

Comment thread lib/activemail/components/base.rb Outdated
def link_attributes(node)
target = node.attributes['target']&.value
rel = node.attributes['rel']&.value
rel ||= ActiveMail.configuration.blank_link_rel if target == '_blank'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize target before injecting rel

When an author writes target="_BLANK" or another mixed-case spelling, the link still targets a blank browsing context, but this exact comparison skips blank_link_rel and emits no rel, leaving the new reverse-tabnabbing hardening disabled for that input. Compare the target case-insensitively before deciding whether to inject the configured rel value.

Useful? React with 👍 / 👎.

@PhilMeyr
PhilMeyr merged commit 1ec2cdd into master Jul 1, 2026
11 of 12 checks passed
@PhilMeyr
PhilMeyr deleted the feat/blank-link-rel branch July 1, 2026 12:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c44bc48d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


IGNORED_ON_PASSTHROUGH = T.let(
%w[class id href size large no-expander small target up size-sm size-lg style].freeze,
%w[class id href size large no-expander small target rel up size-sm size-lg style].freeze,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve rel in generic pass-through attributes

For custom components that use the documented pass_through_attributes helper to build their own anchors, e.g. emitting <a #{pass_through_attributes(node)}>, a source attribute like rel="nofollow" is now stripped globally and that custom component never gets the built-in link_attributes path that re-emits it. This silently drops author-supplied link semantics/security outside the three built-in link components; keep rel out of the generic passthrough ignore list or make the extra filtering local to the built-in anchor emitters.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant