Skip to content

v0.7.14: perf improvements, code hygiene, GitLab private host support#5201

Open
waleedlatif1 wants to merge 6 commits into
mainfrom
staging
Open

v0.7.14: perf improvements, code hygiene, GitLab private host support#5201
waleedlatif1 wants to merge 6 commits into
mainfrom
staging

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

icecrasher321 and others added 6 commits June 24, 2026 00:04
…s) (#5193)

* improvement(mistral): update OCR pricing to OCR 4 rate ($4/1,000 pages)

* docs(mistral): document mistral-ocr-latest alias resolves to OCR 4
…rimitive (#5195)

Replace four hand-rolled client SSE decode loops with two layered
primitives in lib/core/utils/sse.ts:

- readSSELines: the single byte-stream decode engine. Splits on \n,
  strips trailing \r, tolerates data: with/without a leading space,
  skips the [DONE] sentinel, honors an AbortSignal before each chunk and
  between events, and releases the reader lock only when it acquired it.
- readSSEEvents<T>: a thin JSON layer that parses each payload and routes
  unparseable lines to onParseError (default: skip).

An SSESource union accepts a Response, a ReadableStream, or an
already-acquired reader so callers that must stash the reader for
external cancellation keep ownership of the lock.

Migrates use-execution-stream, chat use-chat-streaming, home use-chat
(via readSSELines for schema-validated decode), and the workflow chat
panel. Legacy server/wand exports (encodeSSE, SSE_HEADERS,
readSSEStream) are untouched. Behavior is preserved across abort, RAF
batching, TTS, [DONE], delimiter tolerance, and reader-lock ownership.

Tests in sse.test.ts pin the prior behavior: \n and \n\n framing,
mid-chunk splits, [DONE], data: with/without leading space, \r\n
stripping, sync/async early-stop, pre-aborted and mid-stream abort,
lock release/non-release per source, lock release on a throwing
handler, and Response/stream/reader sources.
…riggers, webhook, and connector (#5200)

* feat(gitlab): support self-managed GitLab host across tools, block, triggers, webhook, and connector

Add an optional `host` so the GitLab integration can target a self-managed
instance (e.g. gitlab.example.com) instead of gitlab.com. Defaults to
gitlab.com everywhere, so existing workflows, blocks, triggers, and stored
webhooks are unchanged.

- Shared host helper (normalizeGitLabHost/getGitLabApiBase) used by all 19
  tools, the block, triggers, the webhook provider, and the connector
- SSRF hardening: reject structurally unsafe hosts (userinfo `@`, whitespace,
  control chars, embedded path/query, empty labels) before the token-bearing
  request is built; allow self-managed hosts, ports, and IDN punycode
- Route the webhook provider's previously-raw fetches through
  secureFetchWithValidation (DNS + private-IP rejection + IP pinning), matching
  the tool and connector paths
- Add regression tests for the host validator

* fix(gitlab): handle unsafe-host errors gracefully in webhook provider

Address review feedback:
- Validate the optional self-managed host up front in createSubscription and
  deleteSubscription so a structurally unsafe value surfaces as a clear error
  (create) or a graceful non-strict skip (delete) instead of an unhandled
  UnsafeGitLabHostError, mirroring the connector's handling
- Document the layered SSRF defense: bare IP literals pass the structural host
  guard by design and are rejected at the fetch layer (validateUrlWithDNS); add
  a confirming test group making that intent explicit

* fix(slack): drop assistant:write scope pending app review approval

Requesting assistant:write before Slack approved it fails the OAuth/install
flow for users. Remove it from both request paths until approval lands:
- Remove from the user OAuth scope list (oauth.ts), matching the existing
  users:read.email TODO pattern
- Remove the action_assistant capability from the bot manifest generator
  (capabilities.ts), leaving a TODO to restore it after approval

The set_status/set_title/set_suggested_prompts tools remain and surface their
existing graceful "reconnect with assistant:write" message until re-enabled.
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 24, 2026 6:38pm

Request Review

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Self-managed GitLab host handling routes user tokens through new URL validation and multiple server surfaces (tools, webhooks, connector); SSE and prefetch refactors touch several user-facing streaming and navigation paths but are heavily tested.

Overview
v0.7.14 bundles performance, refactors, and GitLab self-managed support.

GitLab gains an optional GitLab Host across the block, tools, triggers, webhooks, and connector. API URLs go through shared normalizeGitLabHost / getGitLabApiBase, with structural rejection of unsafe hosts (userinfo, paths, etc.) and clearer validation on webhook create/delete. Outbound GitLab webhook calls use secureFetchWithValidation.

Workspace list pages (home, knowledge, tables, files) now server-prefetch React Query data via cookie-forwarded internal /api fetches and HydrationBoundary, matching client query keys (including mapFolder for folders) so first paint is populated.

Client SSE is centralized in readSSELines / readSSEEvents; chat, copilot home chat, workflow chat, and execution streaming drop duplicated decode loops. Reader lock release behavior is covered by tests.

Execution and workflow-diff stores use status enums with derived legacy booleans (deriveExecutionFlags, deriveDiffFlags), including updated toggle/clear and undo-redo paths for diff UI.

Smaller changes: Mistral OCR cost per page updated to the OCR 4 rate ($4/1k pages); Pi docs (nav order, anchor setup links, BYOK wording); Slack assistant:write deferred pending app review (OAuth scopes and trigger capability).

Reviewed by Cursor Bugbot for commit 067f9e9. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This release bundles four independent improvements: GitLab self-managed host support wired across tools, blocks, triggers, webhook provider and connector; a consolidated typed SSE primitive (readSSELines/readSSEEvents) that all three chat streaming paths now share; ExecutionStatus and WorkflowDiffStatus enums that replace the old sets of boolean flags in their respective stores; and server-side React Query prefetches for the home, knowledge, tables, and files pages.

  • GitLab host: a shared normalizeGitLabHost/getGitLabApiBase utility validates the user-supplied hostname structurally (rejects userinfo, embedded paths, whitespace) and relies on the fetch layer's DNS guard for private/loopback IPs; the webhook provider and connector explicitly route through secureFetchWithValidation, but the tool executor path is not shown in the diff.
  • SSE refactor: readSSELines and readSSEEvents handle framing, [DONE] sentinel, \ \ , abort signals, and reader-lock ownership in one place; the three call-sites that grew their own loops are replaced, and a latent double-finalizeMessageStream bug in the old chat.tsx success path is fixed as a side effect.
  • Store enums + prefetches: both store refactors keep legacy boolean fields as derived values so no consumer code changes; the prefetch helpers cache API wire-shape responses under the exact React Query keys the client hooks read, preventing shape drift from Date serialisation.

Confidence Score: 4/5

Safe to merge after confirming the tool executor routes GitLab API calls through the SSRF-protected fetch layer; all other changes are clean refactors with good test coverage.

Every changed surface — SSE primitive, store enums, prefetches — is well-tested and the logic is correct. The one open question is whether the 20 GitLab tool configs, which now build URLs from a user-supplied hostname, are executed through the same secureFetchWithValidation chokepoint that the webhook provider and connector explicitly use. The structural guard in normalizeGitLabHost prevents authority-confusion attacks, but bare IPs pass through and would reach internal infrastructure carrying the user's PRIVATE-TOKEN header if the tool executor uses plain fetch.

apps/sim/tools/gitlab/utils.ts and all 20 tool files under apps/sim/tools/gitlab/ — confirm the tool executor applies DNS/IP SSRF protection when resolving ToolConfig.request URLs.

Security Review

  • GitLab tool SSRF (unconfirmed gap): All 20 GitLab tools now accept a user-supplied host that is passed to getGitLabApiBase, which validates structure (no userinfo, path, whitespace) but allows bare IPs. The webhook provider and connector explicitly call secureFetchWithValidation before using the resulting URL; the ToolConfig.request pattern delegates the HTTP call to the tool executor, which is not shown in the diff. If the tool executor does not route through secureFetchWithValidation (or equivalent DNS/IP blocklisting), a user could set host: "169.254.169.254" and the tool would issue a credentialed request (PRIVATE-TOKEN header) to the instance metadata endpoint or other internal services. Requires confirmation that the tool executor applies the same fetch-layer SSRF chokepoint.

Important Files Changed

Filename Overview
apps/sim/lib/core/utils/sse.ts New typed SSE primitive: readSSELines (raw) + readSSEEvents (JSON). Handles both \n and \n\n framing, \r stripping, [DONE] sentinel, abort signal, and correct lock-ownership semantics. Comprehensive tests accompany it.
apps/sim/tools/gitlab/utils.ts New shared normalizeGitLabHost/getGitLabApiBase utilities with structural host validation (UnsafeGitLabHostError). Correctly rejects userinfo, embedded paths, whitespace; intentionally allows bare IPs delegating DNS/SSRF blocking to the fetch layer — needs confirmation the tool executor honours the same chokepoint.
apps/sim/lib/webhooks/providers/gitlab.ts Host parameter threaded through subscribe/unsubscribe; all outbound GitLab API calls switched to secureFetchWithValidation and UnsafeGitLabHostError handled gracefully in both strict and non-strict modes.
apps/sim/stores/execution/types.ts Introduces ExecutionStatus enum ('idle'
apps/sim/stores/workflow-diff/types.ts Introduces WorkflowDiffStatus ('none'
apps/sim/app/workspace/[workspaceId]/lib/prefetch-internal-fetch.ts Server-side cookie-forwarding prefetch helper; workspaceId comes from Next.js router params, getInternalApiBaseUrl is a fixed value. Safe pattern, well-documented rationale for routing through API routes instead of the data layer directly.
apps/sim/app/chat/hooks/use-chat-streaming.ts Replaced manual SSE decode loop with readSSEEvents; adds terminated flag to gate post-stream TTS/flush only when no final/error event was received. Also fixes a latent double-finalizeMessageStream bug from the old success-final path that didn't return.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx Replaced manual SSE decode loop with readSSEEvents. Minor: flushChunks() is called redundantly after readSSEEvents returns in the error-final path (harmless no-op but slightly inconsistent with use-chat-streaming.ts pattern).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User supplies optional host] --> B{normalizeGitLabHost}
    B -->|empty / undefined| C[default: gitlab.com]
    B -->|structurally unsafe\nuserinfo / path / whitespace| D[UnsafeGitLabHostError]
    B -->|bare IP / valid hostname| E[getGitLabApiBase]
    E --> F[https://host/api/v4]

    F --> G{Call surface}
    G -->|Webhook provider subscribe / unsubscribe| H[secureFetchWithValidation\nDNS + IP blocklist applied]
    G -->|Connector sync| I[secureFetchWithValidation\nDNS + IP blocklist applied]
    G -->|Tools ToolConfig.request| J[Tool executor\nSSRF protection unconfirmed]

    D --> K[Surfaced as user-facing validation error]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[User supplies optional host] --> B{normalizeGitLabHost}
    B -->|empty / undefined| C[default: gitlab.com]
    B -->|structurally unsafe\nuserinfo / path / whitespace| D[UnsafeGitLabHostError]
    B -->|bare IP / valid hostname| E[getGitLabApiBase]
    E --> F[https://host/api/v4]

    F --> G{Call surface}
    G -->|Webhook provider subscribe / unsubscribe| H[secureFetchWithValidation\nDNS + IP blocklist applied]
    G -->|Connector sync| I[secureFetchWithValidation\nDNS + IP blocklist applied]
    G -->|Tools ToolConfig.request| J[Tool executor\nSSRF protection unconfirmed]

    D --> K[Surfaced as user-facing validation error]
Loading

Comments Outside Diff (1)

  1. apps/sim/tools/gitlab/get_project.ts, line 31-36 (link)

    P1 security Tool executor SSRF gap for user-controlled host

    The webhook provider and connector both explicitly call secureFetchWithValidation when using getGitLabApiBase(host), routing the request through the DNS/IP blocklist. The GitLab tools (this file and all 20 sibling tools) build a URL from getGitLabApiBase(params.host) but the HTTP call is made by the tool executor framework — which is not visible in the diff and may use plain fetch. If the tool executor doesn't go through secureFetchWithValidation, a value like 127.0.0.1 or 169.254.169.254 would pass the structural check and the request (carrying PRIVATE-TOKEN) would reach internal infrastructure.

    The test file explicitly documents this assumption: "SSRF to private/loopback/metadata addresses is the responsibility of validateUrlWithDNS / secureFetchWithValidation at fetch time, the single SSRF chokepoint" — but unlike cleanupGitLabHookByUrl or the connector, nothing in the diff shows the tool executor enforcing that chokepoint. Please confirm that ToolConfig.request URLs are processed through secureFetchWithValidation (or equivalent) before this merges.

Reviews (1): Last reviewed commit: "feat(gitlab): support self-managed GitLa..." | Re-trigger Greptile

Comment on lines +572 to +592
if (event === 'final' && eventData) {
if ('success' in eventData && !eventData.success) {
const errorMessage = eventData.error || 'Workflow execution failed'
flushChunks()
finalizeMessageStream(responseMessageId)
} else if (contentChunk) {
accumulatedContent += contentChunk
pendingChunks += contentChunk
scheduleFlush()
appendMessageContent(
responseMessageId,
`${accumulatedContent ? '\n\n' : ''}Error: ${errorMessage}`
)
}
} catch (e) {
logger.error('Error parsing stream data:', e)
return true
}
}
}

if (contentChunk) {
accumulatedContent += contentChunk
pendingChunks += contentChunk
scheduleFlush()
}
},
})
flushChunks()
finalizeMessageStream(responseMessageId)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant flushChunks() call in error path

When the final event carries a failure, flushChunks() is called inside onEvent (line 575), then return true stops the reader. After readSSEEvents resolves, flushChunks() is called unconditionally again (line 591) — a no-op since pendingChunks is empty, but still confusing. Consider guarding the post-loop flush so it only runs when no final event was processed, similar to how use-chat-streaming.ts uses the terminated flag for this purpose.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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