Harden workflow GitHub context handling#750
Conversation
Pass attacker-controllable GitHub context and workflow values through environment variables before shell use. Co-authored-by: openhands <openhands@all-hands.dev>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
all-hands-bot
left a comment
There was a problem hiding this comment.
🟡 Acceptable - Solid security hardening work with one inconsistency to address
[IMPROVEMENT OPPORTUNITIES]
The security improvements are well-executed overall, but there's an inconsistency in .github/workflows/ci.yml that should be addressed for completeness:
In the "Fetch PR head directly" step, PR_HEAD_REPO is properly moved through the env: block, but steps.pr_context.outputs.head_sha is still directly interpolated in three places. This is inconsistent with the PR's stated goal of moving step outputs through env vars and could leave a potential injection vector.
[RISK ASSESSMENT]
This PR improves workflow security by hardening against GitHub context injection attacks. The changes correctly follow the principle of moving attacker-influenced data through environment variables in most cases. However, the remaining direct interpolation of head_sha in ci.yml is inconsistent with the security improvement and should be addressed for completeness. The fix is trivial and will close the remaining gap.
VERDICT:
✅ Worth merging after addressing the inconsistency noted above
KEY INSIGHT:
This PR successfully implements the principle of defense-in-depth for workflow security by routing GitHub context through environment variables, but should complete the pattern consistently across all step outputs.
Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/agent-canvas/actions/runs/26371144793
📸 Snapshot Test ReportWarning Snapshot comparison step crashed (timeout, OOM, or runner error) — diff results below may be incomplete or absent. ❌ 24 snapshots differ from the main branch baselines. Add the
🔴 Changed snapshots (24)
|
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backends-extended — 10 snapshots
backend-add-blank-disabled
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-add-cloud-advanced-open
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-add-cloud-no-key-disabled
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-add-form-partially-filled
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-add-local-ready
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-add-two-column-layout
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-add-whitespace-host-disabled
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-dropdown-two-backends
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-edit-prefilled
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-manage-two-listed
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backends — 3 snapshots
backend-add-modal
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-manage-modal
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
backend-selector-open
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
collapsible-thinking
think-action-collapsed
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
mcp-page
mcp-slack-install-2-modal
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
settings-page — 2 snapshots
add-backend-modal
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
home-screen
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
sidebar — 2 snapshots
sidebar-collapsed
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
sidebar-filter-menu
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
skills-page — 4 snapshots
skills-loaded
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
skills-no-match
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
skills-search-filtered
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
skills-type-filter
| Expected (main) | Actual (PR) | Diff |
|---|---|---|
![]() |
![]() |
![]() |
✅ Unchanged snapshots (49)
archived-conversation
- conversation-panel-with-archived-badges
- conversation-view-archived
- conversation-view-sandbox-error
automations
- automations-delete-modal
- automations-list-active-inactive
- automations-search-no-results
backends-extended
- backend-add-cloud-with-key-enabled
- backend-add-invalid-url-disabled
- backend-add-name-only-disabled
- backend-after-switch
- backend-cancel-nothing-saved
- backend-manage-after-removal
- backend-remove-cancelled
- backend-remove-confirmation
- backend-switch-overlay
changes-tab
- changes-deleted-file
- changes-diff-viewer
- changes-empty
collapsible-thinking
- reasoning-content-collapsed
- reasoning-content-expanded
- think-action-expanded
mcp-page
- mcp-custom-server-1-editor-open
- mcp-custom-server-2-url-filled
- mcp-custom-server-3-all-filled
- mcp-custom-server-4-installed
- mcp-custom-server-editor
- mcp-empty-installed
- mcp-search-filtered
- mcp-slack-install-1-marketplace
- mcp-slack-install-3-filled
- mcp-slack-install-4-installed
onboarding
- onboarding-step-0-choose-agent
- onboarding-step-1-check-backend
- onboarding-step-2-setup-llm
- onboarding-step-3-say-hello
projects-workspace-browser
- projects-workspace-browser
settings-page
- analytics-consent-modal
- settings-app-page
- settings-page
settings-secrets
- secrets-add-form-filled
- secrets-add-form
- secrets-after-save
- secrets-delete-confirm
- secrets-list
settings-verification
- condenser-settings
- verification-settings-off
- verification-settings-on
sidebar
- sidebar-conversation-panel
skills-page
- skills-empty
Generated by the Snapshot Tests workflow. This comment was created by an AI agent (OpenHands) on behalf of the repo maintainers.








































































Why
Part of the cross-repo fix for OpenHands/software-agent-sdk#3371. Workflow
run:blocks should not interpolate attacker-influenced GitHub context, workflow inputs, or derived step outputs directly into shell scripts.Summary
env:before shell use.${{ ... }}interpolation.Issue Number
Fixes OpenHands/software-agent-sdk#3371
How to Test
python+PyYAMLvalidation over all changed workflow/action YAML files across the audited repositories:validated changed yaml files: 33remaining suspicious run blocks: 0git diff --checkacross all audited repositoriesVideo/Screenshots
Not applicable; workflow hardening only.
Type
Notes
This PR was created by an AI agent (OpenHands) on behalf of the user.
@enyst can click here to continue refining the PR
🐳 Docker images for this PR
• GHCR package: https://github.com/OpenHands/agent-canvas/pkgs/container/agent-canvas
ghcr.io/openhands/agent-canvasghcr.io/openhands/agent-server:1.23.0-pythonopenhands-automation==1.0.0a338dd249897797440a0d0755f2d43e2e3d0b7a8a6Pull (multi-arch manifest)
# Multi-arch manifest — Docker automatically pulls the correct architecture docker pull ghcr.io/openhands/agent-canvas:sha-38dd249Run
All tags pushed for this build
About Multi-Architecture Support
sha-38dd249) is a multi-arch manifest supporting both amd64 and arm64sha-38dd249-amd64) are also available if needed