Skip to content

feat: forward SSH_SIGNING_KEY secret to code agent sandbox#5349

Open
lahavyuv86 wants to merge 1 commit into
fullsend-ai:mainfrom
lahavyuv86:ssh_sign_key_secret
Open

feat: forward SSH_SIGNING_KEY secret to code agent sandbox#5349
lahavyuv86 wants to merge 1 commit into
fullsend-ai:mainfrom
lahavyuv86:ssh_sign_key_secret

Conversation

@lahavyuv86

@lahavyuv86 lahavyuv86 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Enables repos to opt into SSH commit signing by setting an SSH_SIGNING_KEY repo secret.
The setup-agent-env script strips the CODE_ prefix and exports it for fullsend run to expand into env.sandbox.

Related Issue

related to: #1685

Changes

  • Declared SSH_SIGNING_KEY as an optional secret input in on.workflow_call.secrets
  • Added CODE_SSH_SIGNING_KEY to the code agent's "Setup agent environment" step, following the same pattern as CODE_ANTHROPIC_VERTEX_PROJECT_ID

Testing

  • make lint passes (stage changes first, then run)
  • Tests added/updated for new or modified logic

Checklist

  • PR title follows Conventional Commits (correct type, ! for breaking changes)
  • Commits are signed off (DCO) — human and human-directed agent sessions only
  • I wrote this contribution myself and can explain all changes in it

@lahavyuv86
lahavyuv86 requested a review from a team as a code owner July 20, 2026 10:56
@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Forward SSH_SIGNING_KEY secret into code agent sandbox

✨ Enhancement ⚙️ Configuration changes 🕐 10-20 Minutes

Grey Divider

AI Description

• Add optional SSH_SIGNING_KEY secret to reusable workflow_call interface.
• Forward CODE_SSH_SIGNING_KEY into setup-agent-env for sandbox environment expansion.
• Enable opt-in SSH-based commit signing for code agent runs.
Diagram

graph TD
  S[("Repo secret: SSH_SIGNING_KEY")] --> W["reusable-dispatch.yml"] --> E["Setup agent env step"] --> SH["setup-agent-env.sh"] --> G["Runner env (GITHUB_ENV)"] --> SB[["Code agent sandbox"]] --> GW["git signing wrapper"]

  subgraph Legend
    direction LR
    _sec[("Secret")] ~~~ _wf["Workflow/File"] ~~~ _step["Step"] ~~~ _sb[["Sandbox"]]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Fetch signing key from a secret manager at runtime (OIDC)
  • ➕ Avoids storing a long-lived private key as a GitHub repo secret
  • ➕ Improves auditability/rotation and enables centralized policy controls
  • ➖ Requires additional infra (secret manager + IAM/OIDC wiring)
  • ➖ More moving parts and failure modes in CI
2. Use a GitHub App/HSM-backed signing approach
  • ➕ Eliminates distributing a private key into CI/sandbox
  • ➕ Strongest protection against key exfiltration
  • ➖ Significant implementation complexity and operational overhead
  • ➖ May not integrate cleanly with current sandbox git signing wrapper

Recommendation: The PR’s approach (optional secret + existing CODE_-prefix forwarding) is the right incremental step: it reuses established workflow plumbing and keeps the feature opt-in. If adoption increases or key-handling risk becomes a concern, consider migrating to OIDC + secret manager retrieval to reduce long-lived key exposure.

Files changed (1) +7 / -0

Enhancement (1) +7 / -0
reusable-dispatch.ymlAdd optional SSH_SIGNING_KEY secret and forward into agent env +7/-0

Add optional SSH_SIGNING_KEY secret and forward into agent env

• Declares SSH_SIGNING_KEY as an optional workflow_call secret input. Wires the secret into the "Setup agent environment" step as CODE_SSH_SIGNING_KEY so the existing prefix-stripping export script can propagate it to the agent sandbox environment.

.github/workflows/reusable-dispatch.yml

@lahavyuv86
lahavyuv86 force-pushed the ssh_sign_key_secret branch from 84a230e to 5aa38a7 Compare July 20, 2026 10:57
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

Site preview

Preview: https://f0b32494-site.fullsend-ai.workers.dev

Commit: a5ede36a6be5c69ce1ba099434a761efbf981a85

    Enables repos to opt into SSH commit signing by setting an
    SSH_SIGNING_KEY repo secret. The setup-agent-env script strips
    the CODE_ prefix and exports it for fullsend run to expand
    into env.sandbox.

Signed-off-by: Yuval Lahav <ylahav@redhat.com>
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 54 rules

Grey Divider


Remediation recommended

1. Incomplete secret forwarding 🐞 Bug ≡ Correctness
Description
SSH_SIGNING_KEY is added as a workflow_call secret and only forwarded into the direct code job
as CODE_SSH_SIGNING_KEY, but other agent entrypoints (the harness-run matrix path and
reusable-code.yml) never accept/propagate this secret. Repos invoking the code agent via those
paths cannot enable SSH commit signing even if the repo secret exists.
Code

.github/workflows/reusable-dispatch.yml[R74-79]

+      SSH_SIGNING_KEY:
+        description: >-
+          SSH private key for commit signing. Repos that opt into SSH
+          signing set this as a repo secret; the code agent's
+          gpg.ssh.program wrapper writes it to a temp file at sign time.
+        required: false
Relevance

⭐⭐⭐ High

Team previously accepted explicitly declaring/forwarding workflow_call secrets across entrypoints
(OTLP headers) in PR #3903.

PR-#3903

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow exposes SSH_SIGNING_KEY as a callable secret and forwards it only for the direct
Code job, while the harness-run matrix setup exports only repo dir / GCP variables and never maps
any *_SSH_SIGNING_KEY into the prefixed env space. Separately, the standalone reusable-code.yml
reusable workflow does not declare SSH_SIGNING_KEY at all, so callers cannot pass it in that
entrypoint.

.github/workflows/reusable-dispatch.yml[62-79]
.github/workflows/reusable-dispatch.yml[665-674]
.github/workflows/reusable-dispatch.yml[1517-1530]
.github/workflows/reusable-code.yml[44-49]
PR-#3903

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SSH_SIGNING_KEY` is exposed on `reusable-dispatch.yml`'s `workflow_call` interface, but it is only mapped into the direct `code` job environment (`CODE_SSH_SIGNING_KEY`). Other execution paths that run agents (notably the harness-run matrix execution, and the standalone `reusable-code.yml` workflow) do not declare or forward this secret, so SSH signing cannot work consistently across the system.

## Issue Context
`setup-agent-env.sh` only forwards env vars that are explicitly present in the workflow step env; it does not magically access `secrets.*`. So each workflow/entrypoint that can run the code agent must (1) declare the secret on its `workflow_call` interface and (2) map it into the agent-prefixed env vars before calling `setup-agent-env.sh`.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[1517-1530]
- .github/workflows/reusable-code.yml[44-49]
- .github/workflows/reusable-dispatch.yml[62-79]
- .github/workflows/reusable-dispatch.yml[665-674]

### Suggested implementation sketch
1) **harness-run matrix path:** add `SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}` to the step env and export `${ROLE_UPPER}_SSH_SIGNING_KEY` (optionally only when non-empty) before running `setup-agent-env.sh`.

2) **reusable-code.yml:**
- Declare `SSH_SIGNING_KEY` under `on.workflow_call.secrets`.
- Map it as `CODE_SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}` in its "Setup agent environment" step (same pattern as in `reusable-dispatch.yml`).

3) If fix/review agents are also expected to produce signed commits, repeat the pattern in their reusable workflows as well (declare secret + map to `FIX_SSH_SIGNING_KEY`, etc.).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. SSH_SIGNING_KEY missing in scaffold dispatch 📘 Rule violation ⚙ Maintainability
Description
.github/workflows/reusable-dispatch.yml now declares and forwards SSH_SIGNING_KEY (as
CODE_SSH_SIGNING_KEY) but the scaffolded dispatch workflow does not declare/thread the same
secret, creating workflow divergence. This violates the requirement to keep dispatch
secret-threading logic in sync unless the divergence is explicitly documented inline.
Code

.github/workflows/reusable-dispatch.yml[671]

+          CODE_SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}
Relevance

⭐⭐ Medium

Mixed history: some scaffold↔reusable parity fixes accepted (PR #1688), but “keep in sync” requests
also rejected (PR #3820).

PR-#1688
PR-#3820

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062045 requires dispatch workflows to stay in sync for input/secret threading
unless an intentional divergence is documented inline. The PR adds SSH_SIGNING_KEY under
on.workflow_call.secrets and forwards it as CODE_SSH_SIGNING_KEY in
.github/workflows/reusable-dispatch.yml, while the scaffold dispatch workflow has no corresponding
secret declaration, demonstrating an undocumented mismatch.

Rule 1062045: Keep jq payload, stage routing, and secret threading logic in dispatch workflows in sync
.github/workflows/reusable-dispatch.yml[62-79]
.github/workflows/reusable-dispatch.yml[665-674]
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[9-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The reusable dispatch workflow added support for `SSH_SIGNING_KEY`, but `internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml` was not updated to match (and there is no inline comment documenting an intentional divergence).

## Issue Context
Compliance requires `.github/workflows/reusable-dispatch.yml` and `internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml` to keep jq payload/routing and input/secret threading logic in sync, or explicitly document any intentional differences inline.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[62-79]
- .github/workflows/reusable-dispatch.yml[665-674]
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[9-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

AGENT_PREFIX: CODE_
CODE_TARGET_REPO_DIR: target-repo
CODE_ANTHROPIC_VERTEX_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }}
CODE_SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. ssh_signing_key missing in scaffold dispatch 📘 Rule violation ⚙ Maintainability

.github/workflows/reusable-dispatch.yml now declares and forwards SSH_SIGNING_KEY (as
CODE_SSH_SIGNING_KEY) but the scaffolded dispatch workflow does not declare/thread the same
secret, creating workflow divergence. This violates the requirement to keep dispatch
secret-threading logic in sync unless the divergence is explicitly documented inline.
Agent Prompt
## Issue description
The reusable dispatch workflow added support for `SSH_SIGNING_KEY`, but `internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml` was not updated to match (and there is no inline comment documenting an intentional divergence).

## Issue Context
Compliance requires `.github/workflows/reusable-dispatch.yml` and `internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml` to keep jq payload/routing and input/secret threading logic in sync, or explicitly document any intentional differences inline.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[62-79]
- .github/workflows/reusable-dispatch.yml[665-674]
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[9-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

secret). Optional — unset leaves export disabled unless the
endpoint variable alone suffices.
required: false
SSH_SIGNING_KEY:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Incomplete secret forwarding 🐞 Bug ≡ Correctness

SSH_SIGNING_KEY is added as a workflow_call secret and only forwarded into the direct code job
as CODE_SSH_SIGNING_KEY, but other agent entrypoints (the harness-run matrix path and
reusable-code.yml) never accept/propagate this secret. Repos invoking the code agent via those
paths cannot enable SSH commit signing even if the repo secret exists.
Agent Prompt
## Issue description
`SSH_SIGNING_KEY` is exposed on `reusable-dispatch.yml`'s `workflow_call` interface, but it is only mapped into the direct `code` job environment (`CODE_SSH_SIGNING_KEY`). Other execution paths that run agents (notably the harness-run matrix execution, and the standalone `reusable-code.yml` workflow) do not declare or forward this secret, so SSH signing cannot work consistently across the system.

## Issue Context
`setup-agent-env.sh` only forwards env vars that are explicitly present in the workflow step env; it does not magically access `secrets.*`. So each workflow/entrypoint that can run the code agent must (1) declare the secret on its `workflow_call` interface and (2) map it into the agent-prefixed env vars before calling `setup-agent-env.sh`.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[1517-1530]
- .github/workflows/reusable-code.yml[44-49]
- .github/workflows/reusable-dispatch.yml[62-79]
- .github/workflows/reusable-dispatch.yml[665-674]

### Suggested implementation sketch
1) **harness-run matrix path:** add `SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}` to the step env and export `${ROLE_UPPER}_SSH_SIGNING_KEY` (optionally only when non-empty) before running `setup-agent-env.sh`.

2) **reusable-code.yml:**
- Declare `SSH_SIGNING_KEY` under `on.workflow_call.secrets`.
- Map it as `CODE_SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}` in its "Setup agent environment" step (same pattern as in `reusable-dispatch.yml`).

3) If fix/review agents are also expected to produce signed commits, repeat the pattern in their reusable workflows as well (declare secret + map to `FIX_SSH_SIGNING_KEY`, etc.).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@rh-hemartin

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:38 PM UTC · Completed 1:52 PM UTC
Commit: 02a0757 · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

Medium

  • [consumer completeness] .github/workflows/reusable-dispatch.yml:671 — The diff forwards SSH_SIGNING_KEY into the code agent sandbox via CODE_SSH_SIGNING_KEY, and setup-agent-env.sh will correctly strip the CODE_ prefix. However, there is no consumer of this environment variable anywhere in the codebase. The sandbox image (images/code/Containerfile) is configured for gitsign (x509 signing via gpg.format=x509 and gpg.x509.program=gitsign), not SSH-based signing. The PR description references a "gpg.ssh.program wrapper" that does not exist yet. Without a consumer, this secret is forwarded into the sandbox environment but never used, and the signing infrastructure is incomplete.

  • [secret-exposure] .github/workflows/reusable-dispatch.yml:673 — The SSH private key secret is passed as CODE_SSH_SIGNING_KEY and written to GITHUB_ENV by setup-agent-env.sh. GitHub Actions automatically masks values referenced via ${{ secrets.* }}, but once the secret is re-exported to GITHUB_ENV through the shell script (via printenv and heredoc), the re-exported value is a new environment variable that GitHub's masking may not cover. If any subsequent step logs the SSH_SIGNING_KEY env var, the private key material could appear in workflow logs.
    Remediation: Add an explicit echo "::add-mask::${CODE_SSH_SIGNING_KEY}" before writing the secret to GITHUB_ENV, or add masking logic to setup-agent-env.sh for all secret-sourced values.

  • [dispatch workflow sync] internal/scaffold/fullsend-repo/.github/workflows/code.yml:42 — Per AGENTS.md, secret threading changes to reusable-dispatch.yml must also be applied to the scaffold dispatch workflows. The per-org scaffold code.yml calls the reusable workflow with a secrets: block but does not pass SSH_SIGNING_KEY. Repos using per-org installation mode will not receive the signing key. The standalone reusable-code.yml also does not declare SSH_SIGNING_KEY in its secrets block.
    Remediation: Add SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }} to the secrets: block in the scaffold code.yml and reusable-code.yml.

  • [protected-path] .github/workflows/reusable-dispatch.yml — This PR modifies a file under .github/, which is a protected path requiring human approval. The PR links to issue Investigate using gitsign to produce GPG signatures on agent-generated commits #1685 and explains the rationale. Human review is required regardless of automated review outcome.

Low

  • [scope-misalignment] .github/workflows/reusable-dispatch.yml:74 — The linked issue Investigate using gitsign to produce GPG signatures on agent-generated commits #1685 investigated gitsign (keyless Sigstore signing), while this PR implements SSH key-based signing plumbing. These are different signing mechanisms. The PR correctly uses "related to" rather than "fixes", and this is opt-in plumbing that does not commit to a signing architecture. A brief note in the PR description explaining why SSH signing was chosen alongside or instead of gitsign would clarify the design direction.

Labels: PR modifies dispatch workflow to forward SSH signing secret to the code agent sandbox

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

AGENT_PREFIX: CODE_
CODE_TARGET_REPO_DIR: target-repo
CODE_ANTHROPIC_VERTEX_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }}
CODE_SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] consumer completeness

The diff forwards SSH_SIGNING_KEY into the code agent sandbox via CODE_SSH_SIGNING_KEY, and setup-agent-env.sh will correctly strip the CODE_ prefix. However, there is no consumer of this environment variable anywhere in the codebase. The sandbox image (images/code/Containerfile) is configured for gitsign (x509 signing), not SSH-based signing. The PR description references a gpg.ssh.program wrapper that does not exist yet. Without a consumer, this secret is forwarded into the sandbox environment but never used.

Suggested fix: Either land the gpg.ssh.program wrapper and git config changes that consume SSH_SIGNING_KEY in the same PR or a predecessor, or defer this plumbing PR until the consumer exists.

CODE_ANTHROPIC_VERTEX_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }}
CODE_SSH_SIGNING_KEY: ${{ secrets.SSH_SIGNING_KEY }}
CODE_CLOUD_ML_REGION: ${{ inputs.gcp_region }}
CODE_ISSUE_NUMBER: ${{ fromJSON(needs.route.outputs.event_payload).issue.number }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] secret-exposure

The SSH private key secret is passed as CODE_SSH_SIGNING_KEY and written to GITHUB_ENV by setup-agent-env.sh. GitHub Actions automatically masks values referenced via ${{ secrets.* }}, but once the secret is re-exported to GITHUB_ENV through the shell script (via printenv and heredoc), the re-exported value may not be covered by GitHub's masking. If any subsequent step logs the SSH_SIGNING_KEY env var, the private key material could appear in workflow logs.

Suggested fix: Add an explicit echo "::add-mask::${CODE_SSH_SIGNING_KEY}" before writing the secret to GITHUB_ENV, or add masking logic to setup-agent-env.sh for all secret-sourced values.

secret). Optional — unset leaves export disabled unless the
endpoint variable alone suffices.
required: false
SSH_SIGNING_KEY:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] scope-misalignment

The linked issue #1685 investigated gitsign (keyless Sigstore signing), while this PR implements SSH key-based signing plumbing. These are different signing mechanisms. The PR correctly uses 'related to' rather than 'fixes', and this is opt-in plumbing that does not commit to a signing architecture. A brief note in the PR description explaining why SSH signing was chosen alongside or instead of gitsign would clarify the design direction.

@fullsend-ai-review fullsend-ai-review Bot added component/dispatch Workflow dispatch and triggers agent/code Code agent security Security threat model and related concerns labels Jul 20, 2026
@rh-hemartin rh-hemartin self-assigned this Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent/code Code agent component/dispatch Workflow dispatch and triggers security Security threat model and related concerns

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants