Skip to content

fix(#6148): map <details> blocks to ADF expand nodes in Jira converter - #6814

Open
fullsend-ai-coder[bot] wants to merge 5 commits into
mainfrom
agent/6148-details-to-adf-expand
Open

fix(#6148): map <details> blocks to ADF expand nodes in Jira converter#6814
fullsend-ai-coder[bot] wants to merge 5 commits into
mainfrom
agent/6148-details-to-adf-expand

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Summary

MarkdownToADF now converts HTML <details>/<summary> blocks into Jira's ADF expand node type, and ADFToMarkdown renders expand nodes back as <details>/<summary> HTML. Previously, <details> blocks fell through to a raw-text fallback, displaying literal HTML markup in Jira comments.

Changes

  • Write-side (MarkdownToADF): Added tryDetailsExpand in adfBlockContent to detect <details> HTML blocks and convert them to ADF expand nodes. Handles both single-block (no blank lines inside the markup) and multi-block (blank lines split across AST siblings, as produced by sticky.BuildUpdatedBody) layouts.
  • Sentinel stripping: <!-- sticky:history-start --> and <!-- sticky:history-end --> comments are consumed as parser metadata during conversion and do not appear as visible text inside the rendered expansion.
  • Read-side (ADFToMarkdown): Added expand case in adfMarkdownBlock that renders the node as <details><summary>TITLE</summary>\nBODY\n</details>.
  • Round-trip fidelity: <details>expand<details> converges to a stable format after one cycle.
  • Updated existing tests that used <details> as an example of "unknown" blocks to use <div> instead, and removed the expand entry from the "unknown container" test table.

Testing

  • go vet ./internal/forge/jira/... passes
  • go test ./internal/forge/jira/ passes (all 66 tests)
  • Tests added for single-block details, multi-block details, sentinel stripping, details without summary, expand → details rendering, expand without title, and round-trip stability
  • Patch coverage for new functions: tryDetailsExpand 94.6%, all other new functions 100%

Closes #6148

Post-script verification

  • Branch is not main/master (agent/6148-details-to-adf-expand)
  • Secret scan passed (gitleaks — b92ce41f54d5b93410e4e153fac6c4eed0e7c88e..HEAD)
  • PR body secret scan passed (gitleaks — no-git)

MarkdownToADF now converts HTML <details>/<summary> blocks into Jira's
ADF expand node type instead of falling back to raw-text paragraphs
containing literal HTML markup. This handles both single-block (no blank
lines inside) and multi-block (blank lines split across AST siblings, as
produced by sticky.BuildUpdatedBody) layouts.

Sticky history sentinels (<!-- sticky:history-start/end -->) are consumed
as parser metadata during conversion and do not appear as visible text
inside the rendered expansion.

ADFToMarkdown now renders expand nodes back as <details>/<summary> HTML,
providing round-trip fidelity: <details> → expand → <details> converges
to a stable format after one cycle.

Note: pre-commit could not fetch remote hook repos (network-restricted
sandbox). Local hooks (gofmt, go vet) and secret scan passed. Remote
hooks (pre-commit-hooks, gitleaks, ruff, etc.) were not run.

Closes #6148
@fullsend-ai-coder
fullsend-ai-coder Bot requested a review from a team as a code owner August 31, 2026 17:54
@fullsend-ai-coder fullsend-ai-coder Bot added the ready-for-review Agent PR ready for human review label Aug 31, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:56 PM UTC · Completed 6:15 PM UTC

Commit: 803dbe5 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $6.81

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/forge/jira/adf.go 96.26% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Aug 31, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Moderate risk unchanged from prior assessment: Tier 1 signals remain stable (3 files, no protected paths, no dependency or CI changes, bot author, 33% test ratio) despite a large blast radius tag; high churn and fix density in the ADF converter area continues to indicate an actively stabilizing subsystem but is offset by well-scoped changes proportionate to the linked issue and good test coverage.

Previous run

Risk Assessment: moderate (2/5)

Details

Moderate risk unchanged from prior assessment: Tier 1 signals remain stable (3 files, no protected paths, no dependency or CI changes, bot author, 33% test ratio) despite a large blast radius tag; high churn and fix density in the ADF converter area continues to indicate an actively stabilizing subsystem but is offset by well-scoped changes proportionate to the linked issue and good test coverage.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Moderate risk driven primarily by high churn and fix density in the ADF converter area (12 commits and 11 fixes in 30 days), indicating an actively stabilizing subsystem. The change itself is well-scoped: 2 files, strong 50% test ratio, no protected paths or security concerns, bot author with prior contributions, and proportionate to the linked issue scope. Score unchanged from prior assessment as Tier 1 signals remain stable and Tier 2/3 show no material shifts.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Moderate risk driven primarily by high churn and fix density in the ADF converter area (12 commits and 11 fixes in 30 days), indicating an actively stabilizing subsystem. The change itself is well-scoped: 2 files, strong 50% test ratio, no protected paths or security concerns, bot author with prior contributions, and proportionate to the linked issue scope. Score unchanged from prior assessment as Tier 1 signals remain stable and Tier 2/3 show no material shifts.

Previous run (4)

Risk Assessment: moderate (2/5)

Details

Moderate risk driven primarily by high churn and fix density in the ADF converter area (12 commits and 11 fixes in 30 days), indicating an actively stabilizing subsystem. The change itself is well-scoped: 2 files, strong 50% test ratio, no protected paths or security concerns, bot author with prior contributions, and proportionate to the linked issue scope.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [architectural coherence] internal/jirapoll/discover.go:474 — The PR adds "expand" to the duplicated isBlockType function in jirapoll/discover.go, keeping it in sync with the identical function in forge/jira/adf.go. The existing code comments in adf.go acknowledge this duplication as intentional and defer consolidation. However, this is now the second time the duplicated function has needed a synchronized update (the jirapoll copy was missed initially and fixed in a follow-up commit within the PR). Each new block type added in the future risks the same desynchronization. This is a pre-existing architectural issue not introduced by this PR, but the PR demonstrates the drift risk is materializing.

Low

  • [edge-case] internal/forge/jira/adf.go — In the multi-block path of tryDetailsExpand, isDetailsClose matches the first </details> HTMLBlock without tracking nesting depth. If body content contains an inner <details> whose closing tag is split into its own HTMLBlock (requires blank lines around it), the outer expand closes prematurely. In practice, inner <details> blocks appear as a single HTMLBlock, so this is a narrow edge case. Documented by TestMarkdownToADF_NestedDetailsLimitation.
Previous run

Review

Findings

Low

  • [edge-case] internal/forge/jira/adf.go — In the multi-block path of tryDetailsExpand, isDetailsClose matches the first </details> HTMLBlock without tracking nesting depth. If body content contains an inner <details> whose closing tag is split into its own HTMLBlock (requires blank lines around it), the outer expand closes prematurely. In practice, inner <details> blocks appear as a single HTMLBlock, so this is a narrow edge case. Documented by TestMarkdownToADF_NestedDetailsLimitation.
Previous run (2)

Review

Findings

Medium

  • [consumer-completeness] internal/jirapoll/discover.go:477 — The isBlockType function in internal/jirapoll/discover.go is a documented duplicate of internal/forge/jira/adf.go:isBlockType (acknowledged at adf.go:710–715). This PR adds "expand" to adf.go's copy but not to jirapoll's. When jirapoll's walkADFNode processes ADF documents containing expand nodes, the isBlockType check at line 466 returns false for expand nodes, causing newlines between block-level siblings inside or around expand nodes to be omitted in plain-text extraction — fusing adjacent text content.
    Remediation: Add "expand" to the switch case list in internal/jirapoll/discover.go:isBlockType.

Low

  • [edge-case] internal/forge/jira/adf.go — In the multi-block path of tryDetailsExpand, isDetailsClose matches the first </details> HTMLBlock without tracking nesting depth. If body content contains an inner <details> whose closing tag is split into its own HTMLBlock (requires blank lines around it), the outer expand closes prematurely. In practice, inner <details> blocks appear as a single HTMLBlock, so this is a narrow edge case. Documented by TestMarkdownToADF_NestedDetailsLimitation.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

Low

  • [edge-case] internal/forge/jira/adf.go — In the multi-block path of tryDetailsExpand, isDetailsClose matches the first </details> HTMLBlock without tracking nesting depth. If body content contains an inner <details> whose closing tag is split into its own HTMLBlock (requires blank lines around it), the outer expand closes prematurely. In practice, inner <details> blocks appear as a single HTMLBlock, so this is a narrow edge case. Documented by TestMarkdownToADF_NestedDetailsLimitation.

  • [edge-case] internal/forge/jira/adf.goextractSummary returns raw HTML content from the <summary> tag without decoding HTML entities, while adfMarkdownBlock's expand case applies html.EscapeString to the title. If a summary contains pre-existing entities like &amp;, the round-trip double-encodes them to &amp;amp;. The codebase's own BuildUpdatedBody never produces entities in summary text, so this only affects externally authored Markdown.

  • [naming-consistency] internal/forge/jira/adf.go:149 — In tryDetailsExpand, the local variable html (from html, ok := c.(*ast.HTMLBlock)) shadows the imported html package. The same function uses htmlBlock for the identical type assertion in the multi-block path (line 185), making the inconsistency visible within the same function.
    Remediation: Rename the variable from html to htmlBlock to match line 185.

  • [test-organization] internal/forge/jira/adf_test.goTestMarkdownToADF_DetailsExpandRoundTrips is placed after the ADFToMarkdown — expand → <details> section divider, breaking the file's strict section grouping by direction.
    Remediation: Move TestMarkdownToADF_DetailsExpandRoundTrips into the MarkdownToADF — <details> → expand section.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

Low

  • [edge case / silent content loss] internal/forge/jira/adf.go:199 — In the multi-block path of tryDetailsExpand, when foundClose is true but len(bodyContent) == 0 (all siblings between <details> and </details> were sticky sentinels that got skipped), the function returns (nil, nil). The caller then processes the opener and subsequent siblings individually as plain text, producing visible raw HTML in the Jira comment. Narrow edge case — real sticky history always has body content.
    Remediation: Emit a minimal expand node with a single empty paragraph when body is empty but close was found, or document as intentional (ADF expand requires minItems: 1).

  • [nested structure / silent content loss] internal/forge/jira/adf.go:186 — In the multi-block path, isDetailsClose matches the first </details> HTMLBlock without tracking nesting depth. If body content contains an inner <details> whose closing tag is split into its own HTMLBlock (requires blank lines), the outer expand closes prematurely. In practice, inner <details> blocks appear as a single HTMLBlock.
    Remediation: Track a nesting depth counter in the multi-block collection loop, or add a test documenting the limitation.

  • [edge case] internal/forge/jira/adf.go:264detailsInnerBody's no-summary branch uses strings.Index(body, ">") which would misparse a <details> tag with an attribute containing >. The codebase never produces attributed <details> tags.

  • [HTML injection] internal/forge/jira/adf.go:900 — In the ADFToMarkdown expand case, the title from attrs.title is written verbatim into <summary> tags without escaping. A title containing </summary> could break the round-trip. Consistent with existing codebase patterns for Markdown text output.
    Remediation: HTML-escape the title before inserting it between <summary> tags.

  • [naming-consistency] internal/forge/jira/adf.go:228containsDetailsClose uses the contains verb prefix, unique among this file's boolean helpers which all use is. The semantic distinction is valid but has would match the file's predicate naming style.
    Remediation: Consider renaming to hasDetailsClose.

  • [naming-consistency] internal/forge/jira/adf.go:147 — The godoc for tryDetailsExpand describes the return as "(expandNode, nextSibling)", overlapping with the helper function expandNode at line 208.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 6:17 PM UTC · Completed 6:25 PM UTC

Commit: 803dbe5 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.83

- Emit minimal expand node with empty paragraph when multi-block
  <details> body contains only sticky sentinels, preventing raw HTML
  from appearing in Jira comments.
- HTML-escape expand title in ADFToMarkdown to prevent injection via
  </summary> in attrs.title.
- Rename containsDetailsClose to hasDetailsClose for naming consistency
  with other boolean helpers in the file.
- Clarify tryDetailsExpand godoc return description to avoid overlap
  with the expandNode helper function name.
- Document detailsInnerBody limitation with attributed <details> tags.
- Add tests: empty-body multi-block, nested details limitation,
  and HTML-escaped title round-trip.

Addresses #6814
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (bot-triggered)

Addressed all 6 review findings: fixed empty-body multi-block silent content loss by emitting a minimal expand node, HTML-escaped expand titles in ADFToMarkdown, renamed containsDetailsClose to hasDetailsClose for naming consistency, clarified godoc to avoid overlap with helper name, documented detailsInnerBody limitation, and added tests for nested details limitation and HTML-escaped title round-trip.

Fixed (6):

  1. Empty body multi-block silent content loss (internal/forge/jira/adf.go): When foundClose is true but bodyContent is empty (all siblings were sticky sentinels), emit a minimal expand node with a single empty paragraph instead of returning nil. This prevents raw HTML from appearing in Jira comments. Added test TestMarkdownToADF_DetailsMultiBlockEmptyBody.
  2. Nested details silent content loss (internal/forge/jira/adf_test.go): Added TestMarkdownToADF_NestedDetailsLimitation documenting that the multi-block path matches the first without tracking nesting depth. The test exercises the single-block layout where nesting works correctly, and the comment explains the limitation for the multi-block path.
  3. detailsInnerBody attribute edge case (internal/forge/jira/adf.go): Added godoc comment documenting the limitation that the no-summary fallback uses the first > to find the end of the opening tag, which would misparse a
    Details tag with an attribute value containing >. The codebase never produces attributed
    tags.
  4. HTML injection in expand title (internal/forge/jira/adf.go): HTML-escape the title with html.EscapeString before inserting between tags in the ADFToMarkdown expand case. Added TestADFToMarkdown_ExpandNodeEscapesTitle to verify titles containing are escaped correctly.
  5. Naming consistency: containsDetailsClose (internal/forge/jira/adf.go): Renamed containsDetailsClose to hasDetailsClose to match the file's boolean helper naming convention (isDetailsOpen, isDetailsClose, isStickyHistorySentinel).
  6. Godoc return description overlaps with expandNode helper (internal/forge/jira/adf.go): Changed tryDetailsExpand godoc return description from '(expandNode, nextSibling)' to '(expand, nextSibling)' to avoid confusion with the expandNode helper function.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:27 PM UTC · Completed 6:47 PM UTC

Commit: d82827b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $4.92

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 6:49 PM UTC · Completed 6:56 PM UTC

Commit: d82827b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.75

- Rename shadowed `html` variable to `htmlBlock` in tryDetailsExpand
  to match the multi-block path's naming and avoid shadowing the
  imported html package.
- Decode HTML entities in extractSummary with html.UnescapeString so
  pre-existing entities (e.g. &amp;) are not double-encoded when
  adfMarkdownBlock re-encodes the title with html.EscapeString.
- Move TestMarkdownToADF_DetailsExpandRoundTrips into the
  MarkdownToADF section to match the file's direction-based grouping.
- Add TestMarkdownToADF_DetailsSummaryWithHTMLEntities exercising the
  entity round-trip fix.

Addresses #6814
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 2 (bot-triggered)

Fixed HTML entity double-encoding in extractSummary, renamed shadowed html variable to htmlBlock, moved round-trip test to correct section, and added entity round-trip test. Disagreed on nesting-depth finding as it is already documented by an existing test.

Fixed (3):

  1. extractSummary double-encodes pre-existing HTML entities (internal/forge/jira/adf.go): Applied html.UnescapeString in extractSummary so the ADF title stores decoded plain text. The read-side html.EscapeString then re-encodes correctly without double-encoding. Added TestMarkdownToADF_DetailsSummaryWithHTMLEntities to verify the round-trip.
  2. Variable html shadows imported html package in tryDetailsExpand (internal/forge/jira/adf.go): Renamed local variable from html to htmlBlock to match the multi-block path's naming convention at line 185 and avoid shadowing the imported html package.
  3. TestMarkdownToADF_DetailsExpandRoundTrips placed in wrong test section (internal/forge/jira/adf_test.go): Moved TestMarkdownToADF_DetailsExpandRoundTrips from after the ADFToMarkdown section divider into the MarkdownToADF section, restoring the file's strict direction-based grouping.

Disagreed (1):

  1. Multi-block path nesting depth limitation: Already documented by TestMarkdownToADF_NestedDetailsLimitation. The reviewer acknowledges this is a narrow edge case that does not arise with real-world input. No additional code change needed.

Tests: passed

Decision points
  • Whether to add nesting-depth tracking for multi-block details (alternatives: Add a depth counter in the collection loop, Keep the current behavior documented by a test; rationale: The limitation is already documented by TestMarkdownToADF_NestedDetailsLimitation. In practice, goldmark keeps inner
    Details blocks as a single HTMLBlock, so the edge case does not arise with real-world input. Adding complexity is not warranted.)

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:58 PM UTC · Completed 7:12 PM UTC

Commit: 36221f7 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.10

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 7:14 PM UTC · Completed 7:21 PM UTC

Commit: 36221f7 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.31

The duplicate isBlockType in internal/jirapoll/discover.go was not
updated when "expand" was added to internal/forge/jira/adf.go's copy,
causing walkADFNode to omit newlines between block-level siblings
inside or around expand nodes in plain-text extraction.

Addresses #6814
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 3 (bot-triggered)

Added "expand" to jirapoll's duplicate isBlockType function to match adf.go, fixing newline omission around expand nodes in plain-text extraction. The nested details nesting limitation is already documented by an existing test.

Fixed (1):

  1. consumer-completeness: isBlockType in jirapoll/discover.go missing expand (internal/jirapoll/discover.go): Added "expand" to the switch case list in jirapoll's isBlockType to match the addition in adf.go's copy, ensuring walkADFNode correctly separates expand nodes with newlines in plain-text extraction.

Disagreed (1):

  1. edge-case: multi-block nested details nesting depth tracking: Already documented by TestMarkdownToADF_NestedDetailsLimitation. The review itself acknowledges this is a narrow edge case that does not arise with real-world input because goldmark keeps inner
    Details blocks as a single HTMLBlock. No code change needed beyond the existing test documentation.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:23 PM UTC · Completed 7:39 PM UTC

Commit: 86fed70 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.81

@fullsend-ai-review
fullsend-ai-review Bot dismissed stale reviews from themself August 31, 2026 19:39

Superseded by updated review

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review August 31, 2026 19:39

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Aug 31, 2026
@ralphbean

Copy link
Copy Markdown
Member

/fs-fix fix the HTML injection issue

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 8:21 PM UTC · Completed 8:33 PM UTC

Commit: 86fed70 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $2.65

…injection

extractSummary previously preserved HTML tags found inside <summary>
elements (e.g. <script>, <img onerror=...>, <a>) and stored them
verbatim in the ADF expand title attribute. If the consuming renderer
interpreted the title as HTML, this created an injection risk.

Now decode entities first (to avoid double-encoding on round-trip),
then strip all HTML tags with a regex, ensuring the ADF title is
guaranteed plain text.

Addresses #6814
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 4 (human-triggered)

Fixed HTML injection in extractSummary by stripping HTML tags from

content before storing in ADF expand title. Previous iterations fixed output escaping (html.EscapeString on the read-side), but the write-side still stored raw HTML tags in the ADF title attribute. Now both sides are protected: input is sanitized (tags stripped) and output is escaped (html.EscapeString).

Fixed (1):

  1. HTML injection in extractSummary — nested HTML tags in stored verbatim in ADF title (internal/forge/jira/adf.go): Added htmlTagPattern regex and applied it in extractSummary to strip HTML tags after decoding entities. This ensures the ADF expand title is guaranteed plain text, preventing injection when the consuming renderer interprets the title as HTML. For example, now produces title 'Title' instead of storing the raw tag.

Tests: passed

Strategy change: Iteration 4 shifts from output-escaping-only (iterations 1-3) to defense-in-depth: sanitize the input by stripping HTML tags during extraction, not just escaping on output. This addresses the root cause — the ADF title should never contain HTML tags regardless of how downstream systems render it.

Decision points
  • Sanitize input (strip tags in extractSummary) vs rely only on output escaping (html.EscapeString on read-side) (alternatives: Strip HTML tags during extraction so ADF title is always plain text (defense in depth), Rely solely on html.EscapeString on the read-side to escape tags at render time; rationale: Defense in depth: the read-side already escapes with html.EscapeString, but the ADF title attribute is consumed by Jira's renderer which we don't control. Stripping tags at the source ensures the title is plain text regardless of how it's rendered downstream. This is the fundamentally different approach for iteration 4 — previous iterations only addressed output escaping.)

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:35 PM UTC · Completed 8:53 PM UTC

Commit: eb3047f · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $4.36

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed ready-for-merge All reviewers approved — ready to merge labels Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review Agent PR ready for human review requires-manual-review Review requires human judgment risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MarkdownToADF doesn't map <details> history blocks to ADF's expand node on Jira

1 participant