Add Confluence tools, attachment download, and standardize env vars#59
Add Confluence tools, attachment download, and standardize env vars#59SaintPatrck merged 2 commits intomainfrom
Conversation
|
Great job! No new security vulnerabilities introduced in this pull request |
|
Claude finished @SaintPatrck's task in 3m 10s —— View job Claude finished reviewing the latest changes — re-review of commit Code Review SummaryPR: Add Confluence tools, attachment download, and standardize env vars Latest Commit AnalysisThis commit makes two well-scoped changes: 1. Security hardening -- attachment URL pathname validation The A corresponding test case was added ( 2. Shared format utility extraction Duplicate HTML-to-markdown and URL resolution logic was consolidated from 6 tool files into a new
This is a clean DRY refactoring with no behavioral regressions. The consolidated Thread Status
New FindingsNo new issues found. All outstanding review feedback has been addressed. RecommendationApprove -- 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. |
plugins/bitwarden-atlassian-tools/mcp/bitwarden-atlassian-mcp-server/src/utils/validation.ts
Outdated
Show resolved
Hide resolved
plugins/bitwarden-atlassian-tools/mcp/bitwarden-atlassian-mcp-server/src/confluence/client.ts
Outdated
Show resolved
Hide resolved
02ac60f to
2ae9095
Compare
- 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.
- 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.
bc9b3ef to
d4379f8
Compare
d4379f8 to
0b82490
Compare
...ns/bitwarden-atlassian-tools/mcp/bitwarden-atlassian-mcp-server/src/utils/validation.spec.ts
Show resolved
Hide resolved
|
|
||
| export type ListSpacesInput = z.infer<typeof ListSpacesSchema>; | ||
|
|
||
| export const DownloadAttachmentSchema = z.object({ |
There was a problem hiding this comment.
💡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')
theMickster
left a comment
There was a problem hiding this comment.
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
a6771cf to
5f4a965
Compare

📔 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 withnodes[],_links,avatarUrls, andsummaryfields the model must navigate.2. Aggregated Multi-Call Tools
get_confluence_page_commentsfetches 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 (
getConfluencePagewithcontentFormat: "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):Flat markdown. No JSON navigation. Smart links resolved to plain URLs.
What's Included
get_confluence_page,get_confluence_page_comments,get_child_pages,search_confluence,search_confluence_cql,list_spacesdownload_attachment(Jira/Confluence, with SSRF protection)ATLASSIAN_CONFLUENCE_URL/ATLASSIAN_CONFLUENCE_READ_ONLY_TOKENwith fallback to Jira vars