Skip to content

v0.5.101: circular dependency mitigation, confluence enhancements, google tasks and bigquery integrations, workflow lock#3349

Draft
waleedlatif1 wants to merge 16 commits intomainfrom
staging
Draft

v0.5.101: circular dependency mitigation, confluence enhancements, google tasks and bigquery integrations, workflow lock#3349
waleedlatif1 wants to merge 16 commits intomainfrom
staging

Conversation

@waleedlatif1
Copy link
Collaborator

@waleedlatif1 waleedlatif1 commented Feb 26, 2026

icecrasher321 and others added 14 commits February 25, 2026 08:41
…3332)

* fix(call-chain): x-sim-via propagation for API blocks and MCP tools

* addres bugbot comment
* feat(google-sheets): add filter support to read operation

* ran lint
* feat(google-translate): add Google Translate integration

* fix(google-translate): api key as query param, fix docsLink, rename tool file
#3338)

* feat(google): add missing tools for Gmail, Drive, Sheets, and Calendar

* fix(google-drive): remove dead transformResponse from move tool
* feat(confluence): return page content in get page version tool

* lint
* feat(api): audit log read endpoints for admin and enterprise

* fix(api): address PR review — boolean coercion, cursor validation, detail scope

* ran lint
* feat(workflow): lock/unlock workflow from context menu and panel

* lint

* fix(workflow): prevent duplicate lock notifications, no-op guard, fix orphaned JSDoc

* improvement(workflow): memoize hasLockedBlocks to avoid inline recomputation

* feat(google-translate): add Google Translate integration (#3337)

* feat(google-translate): add Google Translate integration

* fix(google-translate): api key as query param, fix docsLink, rename tool file

* feat(google): add missing tools for Gmail, Drive, Sheets, and Calendar (#3338)

* feat(google): add missing tools for Gmail, Drive, Sheets, and Calendar

* fix(google-drive): remove dead transformResponse from move tool

* feat(confluence): return page content in get page version tool (#3344)

* feat(confluence): return page content in get page version tool

* lint

* feat(api): audit log read endpoints for admin and enterprise (#3343)

* feat(api): audit log read endpoints for admin and enterprise

* fix(api): address PR review — boolean coercion, cursor validation, detail scope

* ran lint

* unified list of languages for google translate

* fix(workflow): respect snapshot view for panel lock toggle, remove unused disableAdmin prop

* improvement(canvas-menu): remove lock icon from workflow lock toggle

* feat(audit): record audit log for workflow lock/unlock
* feat(confluence): add get user by account ID tool

* feat(confluence): add missing tools for tasks, blog posts, spaces, descendants, permissions, and properties

Add 16 new Confluence operations: list/get/update tasks, update/delete blog posts,
create/update/delete spaces, get page descendants, list space permissions,
list/create/delete space properties. Includes API routes, tool definitions,
block config wiring, OAuth scopes, and generated docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(confluence): add missing OAuth scopes to auth.ts provider config

The OAuth authorization flow uses scopes from auth.ts, not oauth.ts.
The 9 new scopes were only added to oauth.ts and the block config but
not to the actual provider config in auth.ts, causing re-auth to still
return tokens without the new scopes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(confluence): fix truncated get_user tool description in docs

Remove apostrophe from description that caused MDX generation to
truncate at the escape character.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(confluence): address PR review feedback

- Move get_user from GET to POST to avoid exposing access token in URL
- Add 400 validation for missing params in space-properties create/delete
- Add null check for blog post version before update to prevent TypeError

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(confluence): add missing response fields for descendants and tasks

- Add type and depth fields to page descendants (from Confluence API)
- Add body field (storage format) to task list/get/update responses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(confluence): use validatePathSegment for Atlassian account IDs

validateAlphanumericId rejects valid Atlassian account IDs that contain
colons (e.g. 557058:6b9c9931-4693-49c1-8b3a-931f1af98134). Use
validatePathSegment with a custom pattern allowing colons instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ran lint

* update mock

* upgrade turborepo

* fix(confluence): reject empty update body for space PUT

Return 400 when neither name nor description is provided for space
update, instead of sending an empty body to the Confluence API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(confluence): remove spaceId requirement for create_space and fix list_tasks pagination

- Remove create_space from spaceId condition array since creating a space
  doesn't require a space ID input
- Remove list_tasks from generic supportsCursor array so it uses its
  dedicated handler that correctly passes assignedTo and status filters
  during pagination

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ran lint

* fixed type errors

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…s for loop support (#3346)

* fix(terminal): thread executionOrder through child workflow SSE events for loop support

* ran lint

* fix(terminal): render iteration children through EntryNodeRow for workflow block expansion

IterationNodeRow was rendering all children as flat BlockRow components,
ignoring nodeType. Workflow blocks inside loop iterations were never
rendered as WorkflowNodeRow, so they had no expand chevron or child tree.

* fix(terminal): add childWorkflowBlockId to matchesEntryForUpdate

Sub-executors reset executionOrderCounter, so child blocks across loop
iterations share the same blockId + executionOrder. Without checking
childWorkflowBlockId, updateConsole for iteration N overwrites entries
from iterations 0..N-1, causing all child blocks to be grouped under
the last iteration's workflow instance.
* feat(bigquery): add Google BigQuery integration

* fix(bigquery): add auth provider, fix docsLink and insertedRows count

* fix(bigquery): set pageToken visibility to user-or-llm for pagination

* fix(bigquery): use prefixed export names to avoid aliased imports

* lint

* improvement(bigquery): destructure tool outputs with structured array/object types

* lint
* feat(google-tasks): add Google Tasks integration

* fix(google-tasks): return actual taskId in delete response

* fix(google-tasks): use absolute imports and fix registry order

* fix(google-tasks): rename list-task-lists to list_task_lists for doc generator

* improvement(google-tasks): destructure task and taskList outputs with typed schemas

* ran lint

* improvement(google-tasks): add wandConfig for due date timestamp generation
@vercel
Copy link

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Feb 26, 2026 7:42am

Request Review

Comment on lines +354 to +362
const response = await fetch(currentUrl, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(updateBody),
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

General fix approach: Ensure that user-controlled values used in URL construction cannot change the host, scheme, or unintended parts of the path, and cannot smuggle URL syntax. This can be done by (a) validating the parameter against a strict pattern and/or (b) encoding it safely as a path segment (e.g., encodeURIComponent). Because the host is already hard‑coded here, we primarily need to make sure blogPostId is strictly constrained and clearly treated as a safe path segment.

Best way to fix without changing functionality: Explicitly restrict blogPostId to a safe pattern (e.g., digits only, or a specific alphanumeric regex) via zod schema for the request body, so that the value is validated before it reaches the code that constructs currentUrl. That makes it clear to both humans and tools that blogPostId cannot contain /, ?, #, or other URL‑control characters. We can then optionally still apply encodeURIComponent when interpolating into the URL as defense in depth. The existing code is already using zod schemas (getBlogPostSchema, createBlogPostSchema); we should use createBlogPostSchema to parse and validate the request JSON at the beginning of the handler instead of destructuring directly from body. Within the schema, we can define blogPostId as a z.string().regex(...) that only allows safe characters, and retain the current validateAlphanumericId check for backward compatibility or remove redundancy later. After parsing with the schema, we use the parsed blogPostId (optionally URL-encoded) in currentUrl. This is a minimal, local change inside apps/sim/app/api/tools/confluence/blogposts/route.ts.

Concretely:

  • In the handler around lines 291–305, replace const body = await request.json() and the subsequent manual presence checks with const parsed = createBlogPostSchema.safeParse(await request.json()), returning a 400 if parsing fails. Then destructure from parsed.data.
  • When constructing currentUrl at line 320, wrap blogPostId in encodeURIComponent(blogPostId) to ensure it is treated as a literal path segment, even if future changes loosen validation.
  • No new imports are required; encodeURIComponent is globally available in Node/Next runtimes.

Suggested changeset 1
apps/sim/app/api/tools/confluence/blogposts/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sim/app/api/tools/confluence/blogposts/route.ts b/apps/sim/app/api/tools/confluence/blogposts/route.ts
--- a/apps/sim/app/api/tools/confluence/blogposts/route.ts
+++ b/apps/sim/app/api/tools/confluence/blogposts/route.ts
@@ -294,16 +294,16 @@
       return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
     }
 
-    const body = await request.json()
-    const { domain, accessToken, blogPostId, title, content, cloudId: providedCloudId } = body
-
-    if (!domain || !accessToken || !blogPostId) {
+    const parsedBody = createBlogPostSchema.safeParse(await request.json())
+    if (!parsedBody.success) {
       return NextResponse.json(
-        { error: 'Domain, access token, and blog post ID are required' },
+        { error: parsedBody.error.flatten().fieldErrors || 'Invalid request body' },
         { status: 400 }
       )
     }
 
+    const { domain, accessToken, blogPostId, title, content, cloudId: providedCloudId } = parsedBody.data
+
     const blogPostIdValidation = validateAlphanumericId(blogPostId, 'blogPostId', 255)
     if (!blogPostIdValidation.isValid) {
       return NextResponse.json({ error: blogPostIdValidation.error }, { status: 400 })
@@ -317,7 +312,9 @@
     }
 
     // Fetch current blog post to get version number
-    const currentUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/blogposts/${blogPostId}`
+    const currentUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/blogposts/${encodeURIComponent(
+      blogPostId
+    )}`
     const currentResponse = await fetch(currentUrl, {
       headers: {
         Accept: 'application/json',
EOF
@@ -294,16 +294,16 @@
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}

const body = await request.json()
const { domain, accessToken, blogPostId, title, content, cloudId: providedCloudId } = body

if (!domain || !accessToken || !blogPostId) {
const parsedBody = createBlogPostSchema.safeParse(await request.json())
if (!parsedBody.success) {
return NextResponse.json(
{ error: 'Domain, access token, and blog post ID are required' },
{ error: parsedBody.error.flatten().fieldErrors || 'Invalid request body' },
{ status: 400 }
)
}

const { domain, accessToken, blogPostId, title, content, cloudId: providedCloudId } = parsedBody.data

const blogPostIdValidation = validateAlphanumericId(blogPostId, 'blogPostId', 255)
if (!blogPostIdValidation.isValid) {
return NextResponse.json({ error: blogPostIdValidation.error }, { status: 400 })
@@ -317,7 +312,9 @@
}

// Fetch current blog post to get version number
const currentUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/blogposts/${blogPostId}`
const currentUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/blogposts/${encodeURIComponent(
blogPostId
)}`
const currentResponse = await fetch(currentUrl, {
headers: {
Accept: 'application/json',
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +60 to +66
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

General approach: ensure that all user-influenced values interpolated into the request URL path (cloudId, pageId) are strictly validated against a tight, local allow-list of characters/format before being used. This means rejecting any value that contains /, ?, #, \, or other characters that could break out of the intended path segment, and constraining IDs to the formats Confluence expects, rather than just “alphanumeric up to length N”.

Best concrete fix here: in apps/sim/app/api/tools/confluence/page-descendants/route.ts, add small helper functions (or inline checks) to enforce a strict pattern for pageId and to harden cloudId usage. For pageId, Confluence Cloud page IDs are typically numeric; if we assume that, we can enforce /^\d+$/. If we don’t want to change semantics too much, we can at least require a restricted set like /^[A-Za-z0-9_-]+$/ and disallow any path separators or URL control characters. For cloudId, even though validateJiraCloudId is called, we can add a cheap local guard ensuring it only contains hex digits and dashes (the usual Atlassian cloud ID format) and no slashes or other reserved characters. These local checks are added in this route only, so they don’t change global behavior and are easy to reason about.

Concretely:

  • Leave imports as-is; no new dependencies are required.
  • After parsing the body and before constructing the URL:
    • Replace the current pageIdValidation = validateAlphanumericId(...) block with a stricter local check using a regular expression, returning a 400 if it fails.
    • Keep the validateJiraCloudId call, but additionally enforce a strict regex for cloudId (e.g., /^[0-9a-fA-F-]+$/) before using it in the URL.
  • Do not change the structure of the fetch call or its headers; only ensure the values going into the URL are tightly sanitized.

These changes maintain the existing functionality for valid inputs (Confluence-like IDs) while preventing any malformed or malicious IDs from influencing the request URL path.


Suggested changeset 1
apps/sim/app/api/tools/confluence/page-descendants/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sim/app/api/tools/confluence/page-descendants/route.ts b/apps/sim/app/api/tools/confluence/page-descendants/route.ts
--- a/apps/sim/app/api/tools/confluence/page-descendants/route.ts
+++ b/apps/sim/app/api/tools/confluence/page-descendants/route.ts
@@ -38,9 +38,15 @@
       return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
     }
 
-    const pageIdValidation = validateAlphanumericId(pageId, 'pageId', 255)
-    if (!pageIdValidation.isValid) {
-      return NextResponse.json({ error: pageIdValidation.error }, { status: 400 })
+    // Enforce a strict, URL-safe pageId format to prevent SSRF via path manipulation.
+    // Confluence page IDs are numeric; if that ever changes, this can be relaxed to a
+    // more general but still URL-safe pattern like /^[A-Za-z0-9_-]+$/.
+    const pageIdPattern = /^[0-9]+$/
+    if (!pageIdPattern.test(pageId)) {
+      return NextResponse.json(
+        { error: 'Invalid pageId format; expected a numeric Confluence page ID.' },
+        { status: 400 }
+      )
     }
 
     const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
@@ -50,6 +56,15 @@
       return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
     }
 
+    // Additional hardening: ensure cloudId cannot break out of its URL path segment.
+    const cloudIdPattern = /^[0-9a-fA-F-]+$/
+    if (!cloudIdPattern.test(cloudId)) {
+      return NextResponse.json(
+        { error: 'Invalid cloudId format; expected a hexadecimal Atlassian cloud ID.' },
+        { status: 400 }
+      )
+    }
+
     const queryParams = new URLSearchParams()
     queryParams.append('limit', String(Math.min(limit, 250)))
 
EOF
@@ -38,9 +38,15 @@
return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
}

const pageIdValidation = validateAlphanumericId(pageId, 'pageId', 255)
if (!pageIdValidation.isValid) {
return NextResponse.json({ error: pageIdValidation.error }, { status: 400 })
// Enforce a strict, URL-safe pageId format to prevent SSRF via path manipulation.
// Confluence page IDs are numeric; if that ever changes, this can be relaxed to a
// more general but still URL-safe pattern like /^[A-Za-z0-9_-]+$/.
const pageIdPattern = /^[0-9]+$/
if (!pageIdPattern.test(pageId)) {
return NextResponse.json(
{ error: 'Invalid pageId format; expected a numeric Confluence page ID.' },
{ status: 400 }
)
}

const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
@@ -50,6 +56,15 @@
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}

// Additional hardening: ensure cloudId cannot break out of its URL path segment.
const cloudIdPattern = /^[0-9a-fA-F-]+$/
if (!cloudIdPattern.test(cloudId)) {
return NextResponse.json(
{ error: 'Invalid cloudId format; expected a hexadecimal Atlassian cloud ID.' },
{ status: 400 }
)
}

const queryParams = new URLSearchParams()
queryParams.append('limit', String(Math.min(limit, 250)))

Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +66 to +72
fetch(versionUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}),

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Comment on lines +61 to +67
const getResponse = await fetch(getUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Comment on lines +86 to +94
const response = await fetch(url, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(updateBody),
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

In general, SSRF mitigation requires ensuring that user-controlled data cannot affect the destination host/scheme and can only influence tightly-constrained parts of the path or query, with strong validation. This code already fixes the scheme and host; the remaining concern is ensuring taskId cannot be used to alter the path structure or inject additional URL components. The best fix here is to (1) enforce a strict pattern on taskId locally, independent of external helpers, and/or (2) treat taskId as a path segment and reject or sanitize any value that is not a single safe segment. This preserves existing functionality while eliminating the possibility that a malicious taskId could lead to unintended endpoints.

Concretely, in apps/sim/app/api/tools/confluence/tasks/route.ts, inside the if (action === 'update' && taskId) block, right after the existing validateAlphanumericId check, add an additional explicit validation step that constrains taskId to a safe pattern, e.g. /^[A-Za-z0-9_-]+$/. If the value fails this check, return a 400 error. Then, when constructing URLs, use this validated taskId (and the validated cloudId) directly in the template strings. This does not change observable behavior for legitimate callers whose taskId already conforms, but it guarantees that no path traversal or special characters can be introduced via taskId. No new imports are strictly necessary; we can use a local regular expression.

Suggested changeset 1
apps/sim/app/api/tools/confluence/tasks/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sim/app/api/tools/confluence/tasks/route.ts b/apps/sim/app/api/tools/confluence/tasks/route.ts
--- a/apps/sim/app/api/tools/confluence/tasks/route.ts
+++ b/apps/sim/app/api/tools/confluence/tasks/route.ts
@@ -61,6 +61,15 @@
         return NextResponse.json({ error: taskIdValidation.error }, { status: 400 })
       }
 
+      // Extra hardening: ensure taskId is a single safe URL path segment
+      const safeTaskIdPattern = /^[A-Za-z0-9_-]+$/
+      if (!safeTaskIdPattern.test(taskId)) {
+        return NextResponse.json(
+          { error: 'Invalid taskId format' },
+          { status: 400 }
+        )
+      }
+
       // First fetch the current task to get required fields
       const getUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/tasks/${taskId}`
       const getResponse = await fetch(getUrl, {
EOF
@@ -61,6 +61,15 @@
return NextResponse.json({ error: taskIdValidation.error }, { status: 400 })
}

// Extra hardening: ensure taskId is a single safe URL path segment
const safeTaskIdPattern = /^[A-Za-z0-9_-]+$/
if (!safeTaskIdPattern.test(taskId)) {
return NextResponse.json(
{ error: 'Invalid taskId format' },
{ status: 400 }
)
}

// First fetch the current task to get required fields
const getUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/tasks/${taskId}`
const getResponse = await fetch(getUrl, {
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +139 to +145
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

General approach: Ensure that any user-controlled portion of a URL cannot affect the scheme, host, or path structure. For path segments, enforce a strict character allowlist and/or encode the value so special characters cannot alter the URL.

Best fix here: Harden the taskId usage by (1) explicitly constraining it to safe characters and (2) ensuring it is treated as a literal path segment, not raw URL syntax. We already have validateAlphanumericId, but CodeQL does not understand that. We can add a small, explicit validation/normalization step right before building the URL that guarantees the taskId is a safe path segment, using a simple regex. This does not change behavior for valid IDs (which are already alphanumeric) but makes the safety condition self-contained and obvious.

Concretely in apps/sim/app/api/tools/confluence/tasks/route.ts around lines 134–141:

  • After the existing validateAlphanumericId check passes, add an explicit check enforcing a strict pattern such as /^[A-Za-z0-9_-]+$/. If it fails, return a 400 error.
  • Optionally (and conservatively), keep the URL templating but rely on this restrictor so no reserved URL characters can ever be present. Using encodeURIComponent(taskId) is also safe, but if taskId must remain unchanged visually in logs or downstream API expectations, the explicit regex is clearer and preserves the string as-is for valid values.

Changes needed:

  • No new imports; we only use built-in regex and existing Next.js/TS features.
  • Only modify the if (taskId) { ... } block where url is constructed, inserting a short validation block just before const url = ....

Suggested changeset 1
apps/sim/app/api/tools/confluence/tasks/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sim/app/api/tools/confluence/tasks/route.ts b/apps/sim/app/api/tools/confluence/tasks/route.ts
--- a/apps/sim/app/api/tools/confluence/tasks/route.ts
+++ b/apps/sim/app/api/tools/confluence/tasks/route.ts
@@ -137,6 +137,15 @@
         return NextResponse.json({ error: taskIdValidation.error }, { status: 400 })
       }
 
+      // Ensure taskId is safe to use as a URL path segment and cannot alter the request target
+      const safeTaskIdPattern = /^[A-Za-z0-9_-]+$/
+      if (!safeTaskIdPattern.test(taskId)) {
+        return NextResponse.json(
+          { error: 'Invalid taskId format' },
+          { status: 400 }
+        )
+      }
+
       const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/tasks/${taskId}`
 
       logger.info(`Fetching task ${taskId}`)
EOF
@@ -137,6 +137,15 @@
return NextResponse.json({ error: taskIdValidation.error }, { status: 400 })
}

// Ensure taskId is safe to use as a URL path segment and cannot alter the request target
const safeTaskIdPattern = /^[A-Za-z0-9_-]+$/
if (!safeTaskIdPattern.test(taskId)) {
return NextResponse.json(
{ error: 'Invalid taskId format' },
{ status: 400 }
)
}

const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/tasks/${taskId}`

logger.info(`Fetching task ${taskId}`)
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +193 to +199
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

In general, SSRF must be mitigated by ensuring that user input cannot change the destination host or introduce dangerous path components, and by validating or mapping any user-supplied identifiers used in URLs against a strict allow‑list or format. Here, cloudId can be supplied by the client via providedCloudId, and even though we already call validateJiraCloudId, CodeQL still considers it tainted. The best targeted fix is to add a strict, local validation/sanitization step for cloudId in this route, ensuring that only an expected safe format is accepted before constructing the URL.

Concretely, in apps/sim/app/api/tools/confluence/tasks/route.ts, immediately after computing cloudId and running validateJiraCloudId, we can add an extra check that cloudId matches a conservative pattern (for example, a UUID-like pattern or at least alphanumeric with dashes) and reject the request with HTTP 400 if it does not. This guarantees there are no /, ?, #, or similar special characters that could alter the path or query structure of the URL. We do not need any new imports: we can use a local RegExp. This keeps functionality the same for valid cloudId values while making the data demonstrably safe for use in the URL and silencing the CodeQL SSRF warning.

Suggested changeset 1
apps/sim/app/api/tools/confluence/tasks/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sim/app/api/tools/confluence/tasks/route.ts b/apps/sim/app/api/tools/confluence/tasks/route.ts
--- a/apps/sim/app/api/tools/confluence/tasks/route.ts
+++ b/apps/sim/app/api/tools/confluence/tasks/route.ts
@@ -54,6 +54,16 @@
       return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
     }
 
+    // Extra hardening: ensure cloudId is safe to embed in a URL path segment
+    // Allow only UUID-like format: hex digits and dashes
+    const CLOUD_ID_SAFE_PATTERN = /^[a-fA-F0-9-]+$/
+    if (!CLOUD_ID_SAFE_PATTERN.test(cloudId)) {
+      return NextResponse.json(
+        { error: 'Invalid cloudId format' },
+        { status: 400 }
+      )
+    }
+
     // Update a task
     if (action === 'update' && taskId) {
       const taskIdValidation = validateAlphanumericId(taskId, 'taskId', 255)
EOF
@@ -54,6 +54,16 @@
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}

// Extra hardening: ensure cloudId is safe to embed in a URL path segment
// Allow only UUID-like format: hex digits and dashes
const CLOUD_ID_SAFE_PATTERN = /^[a-fA-F0-9-]+$/
if (!CLOUD_ID_SAFE_PATTERN.test(cloudId)) {
return NextResponse.json(
{ error: 'Invalid cloudId format' },
{ status: 400 }
)
}

// Update a task
if (action === 'update' && taskId) {
const taskIdValidation = validateAlphanumericId(taskId, 'taskId', 255)
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +56 to +62
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI about 7 hours ago

In general, SSRF issues are fixed by ensuring that untrusted input cannot directly control the destination of outgoing HTTP requests, particularly the hostname and significant parts of the path. For path segments derived from user input, either (1) strictly validate them against an allow-list or strong format that cannot be abused for path traversal, or (2) avoid trusting client-supplied identifiers by resolving them server-side based on trusted data.

For this specific code, the safest and simplest fix that preserves functionality is to stop allowing the client to provide cloudId at all. Instead, always derive cloudId using the trusted helper getConfluenceCloudId(domain, accessToken), which presumably talks to Atlassian and returns the correct ID for the given domain. This removes the tainted providedCloudId from the data flow entirely. Concretely:

  • In apps/sim/app/api/tools/confluence/user/route.ts, change line 23 so that you only destructure domain, accessToken, and accountId from body, removing cloudId: providedCloudId.
  • Replace line 47 so that cloudId is always obtained from getConfluenceCloudId(domain, accessToken) and is not conditionally overridden by a user-provided value.
  • Keep the existing validateJiraCloudId check so that whatever getConfluenceCloudId returns is still validated before being used in the URL.

No new imports or helper methods are required; we are just simplifying the assignment of cloudId inside the existing file.

Suggested changeset 1
apps/sim/app/api/tools/confluence/user/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sim/app/api/tools/confluence/user/route.ts b/apps/sim/app/api/tools/confluence/user/route.ts
--- a/apps/sim/app/api/tools/confluence/user/route.ts
+++ b/apps/sim/app/api/tools/confluence/user/route.ts
@@ -20,7 +20,7 @@
     }
 
     const body = await request.json()
-    const { domain, accessToken, accountId, cloudId: providedCloudId } = body
+    const { domain, accessToken, accountId } = body
 
     if (!domain) {
       return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
@@ -44,7 +44,7 @@
       return NextResponse.json({ error: accountIdValidation.error }, { status: 400 })
     }
 
-    const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
+    const cloudId = await getConfluenceCloudId(domain, accessToken)
 
     const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
     if (!cloudIdValidation.isValid) {
EOF
@@ -20,7 +20,7 @@
}

const body = await request.json()
const { domain, accessToken, accountId, cloudId: providedCloudId } = body
const { domain, accessToken, accountId } = body

if (!domain) {
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
@@ -44,7 +44,7 @@
return NextResponse.json({ error: accountIdValidation.error }, { status: 400 })
}

const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
const cloudId = await getConfluenceCloudId(domain, accessToken)

const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
Copilot is powered by AI and may make mistakes. Always verify output.
* feat(sidebar): add lock/unlock to workflow registry context menu

* docs(tools): add manual descriptions to google_books and table

* docs(tools): add manual descriptions to google_bigquery and google_tasks

* fix(sidebar): avoid unnecessary store subscriptions and fix mixed lock state toggle

* fix(sidebar): use getWorkflowLockToggleIds utility for lock toggle

Replaces manual pivot-sorting logic with the existing utility function,
which handles block ordering and no-op guards consistently.

* lint
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.

2 participants