Skip to content

fix(executor): carry child provenance across the workflow agent tool result - #6619

Merged
waleedlatif1 merged 1 commit into
stagingfrom
fix/workflow-tool-result-secret-provenance
Aug 12, 2026
Merged

fix(executor): carry child provenance across the workflow agent tool result#6619
waleedlatif1 merged 1 commit into
stagingfrom
fix/workflow-tool-result-secret-provenance

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

A workflow invoked as an agent tool now resolves {{VAR}} to the real decrypted value in the child run (#6611). The tool result handed back to the model vendor is projected through a registry that had dropped those entries, so the plaintext crosses to the vendor verbatim. This is on staging only — the release it belongs to has not been promoted — but it is a promotion blocker for staging → main.

What reaches the vendor today on staging. For an agent whose tool is a workflow, the tool message serialized into the next model request contains the child's decrypted environment-variable values — in output.result, and also inside output.childTraceSpans, which carries every child block's inputs and outputs. Reproduced with the real registry, the real EnvResolver, and the real model projection: a live sk-live-… style token appears in both.

Mechanism.

  • Model-facing projection (projectToolResultForCopilot) forks with registry.forkForPropagatedEntries(), which keeps only entries a result explicitly carried.
  • EnvResolver (executor/variables/resolvers/env.ts) records the resolution without propagated, unlike the other boundaries that hand a value onward (tools/index.ts, providers/runtime-context.ts).
  • The child shares the caller's registry object, so blockLogs, trace spans and error diagnostics still redact correctly — they read the unforked registry. Only the model fork loses the entries.
  • The blast radius is larger than the child's final output: mapChildOutputToParent puts the full childTraceSpans into the returned object, and postProcessToolOutput strips only __-prefixed keys.

Why main does not have this. There, workflow_executor was an HTTP tool: the execute route emitted __resolvedSecretTraceProvenance, the tool imported it as propagated: true, and transformResponse curated the body so childTraceSpans never crossed. The in-process branch returns before any of that runs.

The fix

runWorkflowTool exports committed provenance for the value it is about to return and imports it back with { trusted: true }, which marks those entries propagated. This is the crossing the custom-block branch already performs in workflow-handler.ts; the non-custom branch skipped it only because the two registries are the same object — exactly the assumption the model fork breaks.

  • Output-projection only. The returned result is byte-identical and the child executes exactly as before; only registry metadata changes.
  • Applied on both the success and the failure return, so a child error message that embeds a resolved secret is covered too.
  • Redacted values render as {{NAME}} — the same literal the model saw before the regression.
  • Fails closed: an unusable envelope marks the registry incomplete, which reduces (never widens) what the model receives.

Why not the alternative (marking EnvResolver's records propagated): propagated means "this result carries this secret across a boundary", not "a block consumed this secret". EnvResolver runs at input resolution for the executing block, in every workflow run, into the run-scoped registry — flipping it there would restate an output-crossing fact at an input site and change what crosses at every unrelated boundary in the product, for a defect that exists at exactly one. Smaller diff, much wider reach, and it does not become more correct than the precedent it replaces. Fixing the boundary that is actually broken keeps the canvas workflow block and the custom-block path untouched.

On childTraceSpans. Curation alone would not have fixed result, so it was never a substitute. I also did not drop it here: the canvas workflow_input block runs through the same runWorkflowTool, and block-executor lifts childTraceSpans off that output into the block log for trace nesting (span-factory reads it) before stripping it from block state. Dropping it in the runner would silently break child-run traces in the UI. Child block I/O reaching the vendor is still questionable on its own merits, but it needs a model-facing output projection hook, which does not exist today (tools have request.modelInput, no output equivalent) — worth a follow-up, and it is now redacted either way.

Short-literal caveat, stated plainly. MIN_SUBSTITUTABLE_LITERAL_LENGTH = 8 governs detection as well as substitution, so an environment value of 7 characters or fewer is not covered by this fix — not in the model result, and not in logs or traces either. That floor is deliberate (it subsumed the per-value exception lists that fixed prior false-positive incidents) and this PR does not touch it. A test pins the behavior so it is documented rather than silent.

Testing

  • New tests in workflow-tool-runner.test.ts drive the real ResolvedSecretTraceRegistry, the real EnvResolver, and the real projectToolResultForCopilot. The two leak tests fail on plain staging (secret present in result and in childTraceSpans; secret present in the projected error) and pass with the fix.
  • A third test asserts an unrelated configured secret stays inert, and a fourth pins the short-literal limitation.
  • Green: full executor/ suite (1947), tools/index, resolved-secret registry / projection / refusal suites, providers/runtime-context.
  • bun run type-check clean; biome clean.

Type of Change

  • Bug fix

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)

…result

A workflow invoked as an agent tool resolves `{{VAR}}` to the real decrypted
value in the child run, but the tool result handed back to the model vendor was
projected through a registry that had dropped those entries — so the plaintext
crossed to the vendor verbatim.

Mechanism. Model-facing projection runs `registry.forkForPropagatedEntries()`,
which keeps only entries a result explicitly carried. `EnvResolver` records a
resolution without `propagated`, unlike every other boundary that hands a value
onward. The child shares the caller's registry object, so `blockLogs`, trace
spans and error diagnostics still redact (they read the unforked registry) —
only the model fork loses them. The exposure is wider than the child's final
output: `mapChildOutputToParent` puts the full `childTraceSpans` — every child
block's inputs and outputs — into the returned object, and `postProcessToolOutput`
strips only `__`-prefixed keys.

The previous implementation ran the child over HTTP: the execute route emitted
`__resolvedSecretTraceProvenance`, the tool imported it as `propagated: true`,
and `transformResponse` curated the body so `childTraceSpans` never crossed. The
in-process branch returns before any of that.

Fix. `runWorkflowTool` exports committed provenance for the value it returns and
imports it back with `{ trusted: true }`, which marks those entries propagated —
the same crossing the custom-block branch already performs in `workflow-handler`.
Output-projection only: the returned result is unchanged, and the child executes
exactly as before. Redacted values render as `{{NAME}}`, matching the literal the
model saw before this regression.

Values shorter than `MIN_SUBSTITUTABLE_LITERAL_LENGTH` are still not redacted
anywhere — that floor governs detection as well as substitution, and is a
documented accepted cost. A test pins the behavior rather than leaving it silent.
@vercel

vercel Bot commented Aug 12, 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 Aug 12, 2026 9:00am

Request Review

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches secret provenance at the model-vendor boundary; incorrect handling could leak plaintext credentials into vendor requests. Fail-closed behavior and focused tests reduce but do not eliminate that risk.

Overview
Prevents decrypted child environment secrets from reaching the model vendor when a workflow runs as an agent tool.

runWorkflowTool now marks secrets present in the success or failure result as propagated via markResultProvenanceCrossing, so projectToolResultForCopilot's propagated-only fork can redact them back to {{NAME}}. The returned tool payload itself is unchanged; only registry metadata is updated, failing closed on import errors.

Tests cover success and error paths, inert unused secrets, and the existing short-literal redaction floor.

Reviewed by Cursor Bugbot for commit b5a099c. Configure here.

@gitguardian

gitguardian Bot commented Aug 12, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
- - Generic High Entropy Secret b5a099c apps/sim/executor/handlers/workflow/workflow-tool-runner.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR carries child-workflow secret provenance across the agent-tool result boundary so model-facing projection can redact resolved environment values from successful outputs, trace spans, and errors.

  • Exports provenance associated with the completed workflow-tool response and reimports it as trusted propagated provenance.
  • Applies the crossing to both successful and failed child executions.
  • Adds integration-oriented tests for output, trace-span, and error redaction, unrelated secrets, and the existing short-literal limitation.

Confidence Score: 5/5

The PR appears safe to merge and closes the child-workflow secret disclosure path without introducing an actionable regression.

The returned bytes remain unchanged while provenance for secrets actually present in the result is promoted for model-facing projection on both success and failure, with incomplete provenance continuing to fail closed.

Important Files Changed

Filename Overview
apps/sim/executor/handlers/workflow/workflow-tool-runner.ts Adds a fail-closed provenance crossing on both completion paths, enabling the existing model projection to redact child-resolved secrets.
apps/sim/executor/handlers/workflow/workflow-tool-runner.test.ts Exercises real registry, resolver, and projection behavior for successful results, trace spans, failures, unrelated secrets, and short literals.

Sequence Diagram

sequenceDiagram
  participant Agent as Workflow agent
  participant Runner as runWorkflowTool
  participant Child as Child workflow
  participant Registry as Secret provenance registry
  participant Projection as Model-facing projection
  participant Vendor as Model vendor
  Agent->>Runner: Invoke workflow tool
  Runner->>Child: Execute with shared registry
  Child->>Registry: Record resolved environment secret
  Child-->>Runner: Output or error containing resolved value
  Runner->>Registry: Export provenance for returned value
  Runner->>Registry: Import as trusted propagated provenance
  Runner-->>Projection: Raw tool result
  Projection->>Registry: Fork propagated entries
  Projection->>Projection: Replace plaintext with placeholder
  Projection-->>Vendor: Redacted tool result
Loading

Reviews (1): Last reviewed commit: "fix(executor): carry child provenance ac..." | Re-trigger Greptile

@waleedlatif1
waleedlatif1 merged commit 7b3e6a6 into staging Aug 12, 2026
24 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/workflow-tool-result-secret-provenance branch August 12, 2026 09:03
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