Skip to content

Add Confluence tools, attachment download, and standardize env vars#59

Merged
SaintPatrck merged 2 commits intomainfrom
plugins/atlassian-tools-2-confluence
Mar 9, 2026
Merged

Add Confluence tools, attachment download, and standardize env vars#59
SaintPatrck merged 2 commits intomainfrom
plugins/atlassian-tools-2-confluence

Conversation

@SaintPatrck
Copy link
Contributor

@SaintPatrck SaintPatrck commented Mar 2, 2026

📔 Objective

Add read-only Confluence tools to the Atlassian MCP server, plus Jira attachment downloads and standardized ATLASSIAN_* environment variables.

Why a Custom MCP Server for Confluence?

Atlassian's official MCP server supports markdown-formatted Confluence responses, so raw ADF/XHTML parsing isn't the primary differentiator. The real value of our plugin is in three areas:

1. Cleaner Output

Atlassian's markdown conversion leaves <custom data-type="smartlink"> and <custom data-type="emoji"> tags unresolved in the output. Our plugin strips these to plain text/URLs. The response is also flat markdown instead of a JSON wrapper with nodes[], _links, avatarUrls, and summary fields the model must navigate.

2. Aggregated Multi-Call Tools

get_confluence_page_comments fetches footer comments, inline comments, and all reply threads in a single tool call—four separate API requests aggregated into one structured response with author, date, resolution status, and highlighted text selections. The official MCP server requires separate calls for each.

3. Consistent Read-Only Scoping

All tools enforce read-only access at the API token level and include Zod input validation with SSRF protection on attachment downloads. Environment variables use a standardized ATLASSIAN_* prefix with Confluence-specific overrides falling back to Jira credentials.

Example: Side-by-side page retrieval

Atlassian's MCP server (getConfluencePage with contentFormat: "markdown"):

{"content":{"totalCount":1,"nodes":[{
  "id":"9876543210","type":"page","status":"current",
  "title":"Deployment Runbook",
  "lastModified":"5 minutes ago",
  "summary":"Step-by-step guide for deploying services to production...",
  "space":{"key":"SRE","name":"Site Reliability Engineering"},
  "_links":{"webui":"/spaces/SRE/pages/9876543210/Deployment+Runbook"},
  "author":{"displayName":"Jane Smith",
    "avatarUrls":{"48x48":"https://example.atlassian.net/wiki/aa-avatar/..."}},
  "body":"# Pre-deployment Checklist\n\n* All CI checks pass\n* <custom data-type=\"smartlink\" data-id=\"id-0\">https://wiki.example.com/migrations</custom> reviewed\n* Rollback plan documented\n\n# Deployment Steps\n\nRun with `--production` flag.",
  "webUrl":"https://example.atlassian.net/wiki/spaces/SRE/pages/9876543210"
}]}}

Body is markdown, but wrapped in JSON with metadata fields. Smart links appear as unresolved <custom> tags.

Our plugin (get_confluence_page):

# Deployment Runbook

**Page ID:** 9876543210
**Status:** current
**Space:** Site Reliability Engineering (SRE)
**Version:** 14
**Modified By:** Jane Smith
**URL:** https://example.atlassian.net/wiki/spaces/SRE/pages/9876543210

---

## Content

# Pre-deployment Checklist

- All CI checks pass
- https://wiki.example.com/migrations reviewed
- Rollback plan documented

# Deployment Steps

Run with `--production` flag.

Flat markdown. No JSON navigation. Smart links resolved to plain URLs.

What's Included

  • 6 Confluence tools: get_confluence_page, get_confluence_page_comments, get_child_pages, search_confluence, search_confluence_cql, list_spaces
  • 1 cross-domain tool: download_attachment (Jira/Confluence, with SSRF protection)
  • Environment variable standardization: ATLASSIAN_CONFLUENCE_URL / ATLASSIAN_CONFLUENCE_READ_ONLY_TOKEN with fallback to Jira vars
  • 185 tests across 8 files (110 new)

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

Logo
Checkmarx One – Scan Summary & Detailsfaa96183-231c-4cbc-af8a-7cb7d1ba361c

Great job! No new security vulnerabilities introduced in this pull request

@SaintPatrck SaintPatrck added the ai-review Request a Claude code review label Mar 2, 2026
@claude
Copy link

claude bot commented Mar 2, 2026

Claude finished @SaintPatrck's task in 3m 10s —— View job


Claude finished reviewing the latest changes — re-review of commit 5f4a965


Code Review Summary

PR: Add Confluence tools, attachment download, and standardize env vars
Risk Level: Low
Re-review scope: Commit 5f4a965 ("Extract shared format utilities and harden attachment URL validation")

Latest Commit Analysis

This commit makes two well-scoped changes:

1. Security hardening -- attachment URL pathname validation

The .regex() check on DownloadAttachmentSchema has been replaced with a .refine() that validates against new URL(url).pathname instead of the full URL string (validation.ts L123-130). This directly addresses theMickster's feedback about the unanchored regex allowing the attachment path pattern to match in the query string, which could have been used to make authenticated requests to arbitrary endpoints on the same Atlassian instance.

A corresponding test case was added (validation.spec.ts L373-380) that verifies URLs with the attachment pattern only in the query string are rejected.

2. Shared format utility extraction

Duplicate HTML-to-markdown and URL resolution logic was consolidated from 6 tool files into a new utils/format.ts module:

  • htmlToMarkdown() -- superset of the previous formatStorageContent() (page content) and formatCommentContent() (comment bodies), now handling headings, lists, code blocks, links, and entity decoding uniformly
  • resolveWebuiUrl() -- replaces identical inline startsWith('http') fallback logic repeated in 5 files

This is a clean DRY refactoring with no behavioral regressions. The consolidated htmlToMarkdown is additive -- comments now gain heading/list/pre support, and pages gain link parsing.

Thread Status

Thread Status
🔴 SSRF origin check (validation.ts) Resolved in prior commit
💡 Wrong env var in error message (confluence/client.ts) Resolved in prior commit
⚠️ Missing DownloadAttachmentSchema tests (validation.spec.ts) Resolved in prior commit
💡 Unanchored regex allows query-string bypass (theMickster) Resolved in this commit -- regex now validates pathname only

New Findings

No new issues found. All outstanding review feedback has been addressed.

Recommendation

Approve -- The security hardening correctly prevents same-origin endpoint access via query string injection, the format utility extraction is a clean refactoring, and test coverage includes the new attack vector.

@SaintPatrck SaintPatrck force-pushed the plugins/atlassian-tools-2-confluence branch from 02ac60f to 2ae9095 Compare March 3, 2026 14:37
SaintPatrck added a commit that referenced this pull request Mar 3, 2026
- Add .refine() origin validation to DownloadAttachmentSchema so
  attachment URLs are rejected at the validation layer if their origin
  does not match the configured Atlassian instance (defense in depth
  alongside the client-layer check from the prior commit).
- Fix incorrect env var name in Confluence client error message:
  CONFLUENCE_URL → ATLASSIAN_CONFLUENCE_URL.
SaintPatrck added a commit that referenced this pull request Mar 3, 2026
- Add .refine() origin validation to DownloadAttachmentSchema so
  attachment URLs are rejected at the validation layer if their origin
  does not match the configured Atlassian instance (defense in depth
  alongside the client-layer check from the prior commit).
- Fix incorrect env var name in Confluence client error message:
  CONFLUENCE_URL → ATLASSIAN_CONFLUENCE_URL.
@SaintPatrck SaintPatrck force-pushed the plugins/atlassian-tools-2-confluence branch from bc9b3ef to d4379f8 Compare March 3, 2026 22:00
@SaintPatrck SaintPatrck deleted the branch main March 3, 2026 22:00
@SaintPatrck SaintPatrck closed this Mar 3, 2026
@SaintPatrck SaintPatrck reopened this Mar 3, 2026
@SaintPatrck SaintPatrck changed the base branch from plugins/atlassian-tools-1-jira-mvp to main March 3, 2026 22:05
@SaintPatrck SaintPatrck force-pushed the plugins/atlassian-tools-2-confluence branch from d4379f8 to 0b82490 Compare March 3, 2026 22:08
@SaintPatrck SaintPatrck marked this pull request as ready for review March 4, 2026 23:11
@SaintPatrck SaintPatrck requested a review from a team as a code owner March 4, 2026 23:11

export type ListSpacesInput = z.infer<typeof ListSpacesSchema>;

export const DownloadAttachmentSchema = z.object({
Copy link
Contributor

Choose a reason for hiding this comment

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

💡One item from a local Claude Code review

Details

The DownloadAttachmentSchema validates attachment URLs with an unanchored regex:

.regex(/\/secure\/attachment\/|\/rest\/api\/.*\/attachment\//, 'Must be a JIRA attachment URL')

This regex runs against the full URL string, not just the pathname. An attacker who controls the attachmentUrl input could craft a URL where the matching pattern appears in the query string:

https://mycompany.atlassian.net/rest/api/2/user?key=admin&x=/secure/attachment/

The origin check (.refine()) correctly restricts requests to the configured Atlassian host, so this is not a full SSRF to arbitrary hosts. However, it does allow authenticated GET requests to any endpoint on the same Atlassian instance (e.g., admin APIs, user enumeration endpoints), since the Jira client sends credentials with the request.

Suggested fix: Validate against new URL(url).pathname instead of the full URL string:

.refine((url) => {
  const { pathname } = new URL(url);
  return /\/secure\/attachment\/|\/rest\/api\/.*\/attachment\//.test(pathname);
}, 'Must be a JIRA attachment URL path')

Copy link
Contributor

@theMickster theMickster left a comment

Choose a reason for hiding this comment

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

Only one item from a local review to consider

Add six Confluence MCP tools (page reading, search, CQL search, child
pages, spaces listing, page comments) and Jira attachment download.

Standardize all environment variables to use the ATLASSIAN_* prefix:
- ATLASSIAN_CONFLUENCE_URL (falls back to ATLASSIAN_JIRA_URL)
- ATLASSIAN_CONFLUENCE_READ_ONLY_TOKEN (falls back to ATLASSIAN_JIRA_READ_ONLY_TOKEN)
- ATLASSIAN_EMAIL (shared across Jira and Confluence)
- Extract htmlToMarkdown() and resolveWebuiUrl() into src/utils/format.ts
- Deduplicate HTML-to-markdown conversion from get-confluence-page and
  get-confluence-page-comments into a single shared function with superset
  of both feature sets (headings, lists, code blocks, links)
- Replace repeated webui URL resolution pattern across 5 Confluence tools
  with centralized resolveWebuiUrl() helper
- Harden attachment URL validation to match against pathname only,
  preventing query-string bypass of SSRF origin check
@SaintPatrck SaintPatrck force-pushed the plugins/atlassian-tools-2-confluence branch from a6771cf to 5f4a965 Compare March 7, 2026 18:15
@SaintPatrck SaintPatrck requested a review from theMickster March 9, 2026 18:55
@SaintPatrck SaintPatrck merged commit ea7483c into main Mar 9, 2026
11 checks passed
@SaintPatrck SaintPatrck deleted the plugins/atlassian-tools-2-confluence branch March 9, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants