Skip to content

fix(workflow, custom): billing attribution passthrough#5657

Merged
icecrasher321 merged 1 commit into
stagingfrom
fix/custom-block-billing
Jul 14, 2026
Merged

fix(workflow, custom): billing attribution passthrough#5657
icecrasher321 merged 1 commit into
stagingfrom
fix/custom-block-billing

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

Billing attribution passthrough for custom blocks, workflow blocks to allow blocks within that need attribution to work.

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 14, 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 Jul 14, 2026 12:43am

Request Review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches billing attribution on nested execution paths; incorrect passthrough could mis-attribute usage or break internal routes that require matching actor/workspace scope.

Overview
Nested workflow and custom-block runs now carry a frozen billing attribution snapshot so internal tool calls (knowledge, guardrails, MCP, Mothership) can send the required attribution header.

ContextExtensions gains optional billingAttribution, which the executor copies into execution metadata and overrides metadata.billingAttribution when both are present. The workflow block handler forwards the parent’s snapshot into the child executor for same-workspace nested workflows.

For custom blocks, the child runs as the source owner in the source workspace; the handler calls resolveBillingAttribution for that actor/workspace instead of reusing the consumer’s snapshot, so internal routes’ actor/workspace checks still pass.

A unit test asserts the parent snapshot is passed via contextExtensions without calling resolveBillingAttribution for the regular child path.

Reviewed by Cursor Bugbot for commit f5e44bd. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR passes billing attribution into nested workflow execution contexts. The main changes are:

  • Adds billingAttribution to executor context extensions.
  • Copies context-level attribution into execution metadata.
  • Forwards parent attribution into workflow-block child executors.
  • Resolves a fresh attribution snapshot for custom-block child workflows.
  • Adds a test for non-custom child workflow attribution passthrough.

Confidence Score: 4/5

The custom-block billing path needs a fix before merging.

  • Cross-workspace custom blocks can resolve payer state from the source workspace.
  • A valid consumer execution can fail when the source workspace lacks billing setup.
  • Usage can be attributed to the source owner instead of the consuming workflow.

apps/sim/executor/handlers/workflow/workflow-handler.ts

Security Review

Cross-workspace custom blocks can resolve billing against the source workflow workspace instead of the consuming workspace, which can misattribute charges or fail execution based on the source workspace payer state.

Important Files Changed

Filename Overview
apps/sim/executor/execution/executor.ts Copies context extension billing attribution into execution metadata so nested tool calls can read it.
apps/sim/executor/execution/types.ts Adds the typed billing attribution extension and documents its precedence over metadata.
apps/sim/executor/handlers/workflow/workflow-handler.ts Forwards attribution into child executors and resolves custom-block attribution against the source workflow context.
apps/sim/executor/handlers/workflow/workflow-handler.test.ts Adds mocks and coverage for non-custom child workflow attribution passthrough.

Reviews (1): Last reviewed commit: "fix(workflow, custom): billing attributi..." | Re-trigger Greptile

Comment on lines +381 to +384
childBillingAttribution = await resolveBillingAttribution({
actorUserId: loadUserId,
workspaceId: childWorkflow.workspaceId,
})

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.

P1 security Custom Blocks Charge Source Workspace

When a cross-workspace custom block runs, this branch resolves the billing snapshot for loadUserId and childWorkflow.workspaceId instead of the consuming execution. A consumer with valid billing can now fail because the source workspace has no payer, or usage can be attributed to the source owner even though this custom-block path is supposed to bill the consumer workflow.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There is no cross-customer charge. Custom blocks are org-scoped: getCustomBlockAuthority only resolves blocks where customBlock.organizationId matches the consumer workspace's organization, and returns null for non-org workspaces. Source and consumer workspaces always bill the same org account. The difference is bookkeeping only (usage-log actor, member cap), identical to pre-enforcement.

The consumer's snapshot cannot be used here. The internal routes reject any header whose actorUserId/workspaceId don't match the authenticated request scope — and the request scope for a custom-block child is the source owner in the source workspace by design (same identity as a deployed API/schedule run of the source workflow). Attaching the consumer's snapshot would fail that check on every call.

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.

You're right on both counts. The getCustomBlockAuthority code confirms it:

const consumerWs = await getWorkspaceWithOwner(consumerWorkspaceId, { includeArchived: true })
if (!consumerWs?.organizationId) return null
// ...
.where(and(eq(customBlock.type, type), eq(customBlock.organizationId, consumerWs.organizationId)))

The org-id equality enforcer means source and consumer are always in the same org (and personal workspaces can't use custom blocks at all). The "cross-workspace" concern I raised collapses to intra-org bookkeeping, and since the internal routes authenticate requests as the source owner in the source workspace, attaching the consumer's snapshot would fail that scope check on every call. The implementation is correct. Withdrawing the concern.

@icecrasher321 icecrasher321 merged commit 8b9f593 into staging Jul 14, 2026
18 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/custom-block-billing branch July 14, 2026 02:13
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.

1 participant