Repository navigation
Seal workflow secrets across agent and sub-workflow handoffs - #2109
Merged
ppXD merged 1 commit intoOct 12, 2026
Merged
Conversation
A secret resolved into an agent.run goal or a flow.subworkflow input left the scope that knew it was a secret. The engine persisted the suspend payload, the agent's task_jsonb and the child's request payload in plaintext, and Viewers read them back as the pending wait, the agent goal and the child's NormalizedPayload. The agent run's redactor never knew the value, so every echo froze into its events, native records, result and the critic's capture. The child built SecretPaths from its own rows only, so a parent wf/project secret leaked through the child's ledger, outputs and Terminal guard. The suspend wait row is now masked with the node scope's secrets for every wait kind. A task or child payload handed to another run is sealed: the public column holds the masked copy, and the exact copy plus the secret values it embeds is encrypted with the existing sensitive-payload protector (0247 adds the two nullable ciphertext columns). The executor and the child's scope build open the exact copy, so the platform never runs on a mask. The executor folds the values into its redactor inside the credential resolve, so launch, re-attach and the re-opened MCP endpoint rebuild the same fingerprint. The critic scope gets a capture redactor. A child keeps the inherited values as secrets and taints the trigger/input keys that carry them, so its records, derived scopes, map snapshots and Terminal guard treat them like its own. Tasks created inside a node scope (supervisor spawns, plan reviewers) are sealed from the ambient capture redactor, and a review child inherits its producer's secrets. The supervisor's work-plan goal is masked the same way. The run's verdicts on that work carried the value back out. The output critic is shown the exact goal, and a model restates the task it judges, so its rationale, issues and critique are masked with the task's secrets before the executor writes them into the review.completed beat, the flagged warning and the result's reviewFeedback. An acceptance argv that resolves a secret runs exactly, but the grade's evidence repeats the argv and both output tails into a team artifact a Viewer can download and onto the result. Every executor grade request now carries the task's secrets, and the grader masks the evidence, its tail, the oracle note and the detail before anything is stored. Rows written before this change carry no ciphertext and keep executing from their public columns, so in-flight waits, agent runs and child runs resume unchanged, and their redactor fingerprints still match. Those rows also keep any plaintext they already hold. A worker on the previous build does not know the sealed columns and would run a sealed child or agent from its masked public copy, so old engine and executor workers must be drained before new ones stage work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
agent.rungoal or aflow.subworkflowinput was stored in plaintext. It landed in the suspend wait row,agent_run.task_jsonband the child'sworkflow_run_request.normalized_payload_json. Viewers read it back as the pending wait, the agent goal andNormalizedPayload. The fix:0247adds two nullable ciphertext columns, and the existing sensitive-payload protector does the encryption.SecretRedactorfolds in the task's secret values, inside the credential resolve. Launch, re-attach and the re-opened MCP endpoint therefore rebuild the same fingerprint.agent.criticscope gets aCaptureRedactor.review.completedbeat, the flagged warning event andreviewFeedback(AgentRunExecutor.MaskedVerdict).["sh","-c","… {{team.X}} …"]still runs exactly. Every executor grade request now carries the task's secrets, andSupervisorAcceptanceGradermasks the evidence text, tail, oracle note and detail before storing the evidence artifact a Viewer can download (SupervisorAcceptanceGrader.MaskedGrade).Deploy notes
[REDACTED]inputs, because it reads onlynormalized_payload_json. Stop or drain every old engine and agent-executor worker before any new-build worker stages agent runs or sub-workflow children; do not overlap versions.agent_run.task_jsonbof earlier secret-bearing goals, pending wait payloads suspended at deploy, earlier child request payloads, and anything already written to events, records, results and evidence artifacts. A one-off backfill is possible. It is safe for pending wait and child request payloads.task_jsonbof terminal agent runs needs care, because the completion composer re-derives contract hashes from it.SupervisorTurnService.Rehydrate) do not carry workflow secrets yet. A supervisoracceptanceChecksargv that resolves a secret would still put it into its own grade evidence.Test plan
CodeSpace.UnitTestspasses (13435 passed, 1 skipped). New coverage:MaskedVerdict;AcceptanceEvidenceSecretMaskingTests, using the real grader andTestsPassGrader: exact argv executed, stored evidence, tail, note and detail masked, setup-failure prefix and class preserved, no-secret grade byte-identical;SecretTaintAcrossSuspendsFlowTests(16 cases). Each case runs an all-table + artifact canary sweep (every text/jsonb/bytea column) and checks every Viewer read. Cases include:SecretTaintRunSurfaceE2ETests:GET /api/artifacts/{evidenceId}and finds only the mask.MaskedVerdict, removingMaskedGrade, dropping the local or repository-lane secret wiring, and dropping the replay-path taint union inWorkflowEngine.BuildScopeForReplayAsync.