Skip to content

feat: forward SSH_SIGNING_KEY secret to code agent sandbox - #5349

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

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
@rh-hemartin

Copy link
Copy Markdown
Member

We are coming with a new approach.

@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 22, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 9:50 AM UTC · Completed 10:15 AM UTC
Commit: a5ede36 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #5349 — Forward SSH_SIGNING_KEY secret to code agent sandbox

Outcome: Closed without merge after 2 days. Human maintainer closed with "We are coming with a new approach."

Review Quality: High

The fullsend review agent performed well on this external contributor PR. All 5 findings were valid with no false positives:

Finding Severity Valid? Notes
Consumer completeness — no code consumes SSH_SIGNING_KEY Medium Sandbox uses gitsign (x509), not SSH signing
Secret exposure — re-exported secrets may bypass GitHub masking Medium Real security concern
Dispatch workflow sync — missing scaffold updates Medium Per AGENTS.md rules
Protected path — .github/ needs human review Medium Low-value for human PRs but not wrong
Scope misalignment — issue #1685 is about gitsign, not SSH Low Validated by PR closure

Both Qodo and fullsend-review converged on scaffold sync and incomplete forwarding, with fullsend finding additional security and design concerns. The CHANGES_REQUESTED verdict was appropriate. Review completed in ~16 minutes.

Notable: Design-Level Review Capability

The [scope-misalignment] finding demonstrated design-level review — the agent cross-referenced the linked issue and identified an approach mismatch. The human's decision to close and pursue a different approach validated this concern as arguably the most important finding on the PR.

Evidence for Existing Issues

No New Proposals

All improvement opportunities identified are already covered by existing open issues. The review agent performed well — findings were accurate, no false positives, and the review likely contributed to the team's decision to pursue a different signing approach.

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