diff --git a/.github/workflows/security-alert-burndown.campaign.lock.yml b/.github/workflows/security-alert-burndown.campaign.lock.yml index 891055e70d..0abccd72ba 100644 --- a/.github/workflows/security-alert-burndown.campaign.lock.yml +++ b/.github/workflows/security-alert-burndown.campaign.lock.yml @@ -112,12 +112,11 @@ jobs: run: mkdir -p ./.gh-aw - env: GH_AW_CAMPAIGN_ID: security-alert-burndown - GH_AW_CURSOR_PATH: /tmp/gh-aw/repo-memory/campaigns/security-alert-burndown/cursor.json - GH_AW_DISCOVERY_REPOS: githubnext/gh-aw + GH_AW_CURSOR_PATH: "" GH_AW_MAX_DISCOVERY_ITEMS: "100" GH_AW_MAX_DISCOVERY_PAGES: "5" GH_AW_PROJECT_URL: https://github.com/orgs/githubnext/projects/134 - GH_AW_TRACKER_LABEL: z_campaign_security-alert-burndown + GH_AW_TRACKER_LABEL: "" GH_AW_WORKFLOWS: code-scanning-fixer,security-fix-pr,dependabot-bundler,secret-scanning-triage id: discovery name: Run campaign discovery precomputation @@ -898,9 +897,6 @@ jobs: This workflow orchestrates the 'Security Alert Burndown' campaign. - Associated workflows: code-scanning-fixer, security-fix-pr, dependabot-bundler, secret-scanning-triage - - Memory paths: memory/campaigns/security-alert-burndown/** - - Metrics glob: `memory/campaigns/security-alert-burndown/metrics/*.json` - - Cursor glob: `memory/campaigns/security-alert-burndown/cursor.json` - Project URL: https://github.com/orgs/githubnext/projects/134 - Governance: max new items per run: 3 - Governance: max discovery items per run: 100 @@ -1224,35 +1220,9 @@ jobs: - On throttling (HTTP 429 / rate-limit 403), do not retry aggressively; back off and end the run after reporting what remains. - **Cursor file (repo-memory)**: `memory/campaigns/security-alert-burndown/cursor.json` - **File system path**: `/tmp/gh-aw/repo-memory/campaigns/security-alert-burndown/cursor.json` - - If it exists: read first and continue from its boundary. - - If it does not exist: create it by end of run. - - Always write the updated cursor back to the same path. - **Metrics snapshots (repo-memory)**: `memory/campaigns/security-alert-burndown/metrics/*.json` - **File system path**: `/tmp/gh-aw/repo-memory/campaigns/security-alert-burndown/metrics/*.json` - - Persist one append-only JSON metrics snapshot per run (new file per run; do not rewrite history). - - Use UTC date (`YYYY-MM-DD`) in the filename (example: `metrics/2025-12-22.json`). - - Each snapshot MUST include ALL required fields (even if zero): - - `campaign_id` (string): The campaign identifier - - `date` (string): UTC date in YYYY-MM-DD format - - `tasks_total` (number): Total number of tasks (>= 0, even if 0) - - `tasks_completed` (number): Completed task count (>= 0, even if 0) - - Optional fields (include only if available): `tasks_in_progress`, `tasks_blocked`, `velocity_per_day`, `estimated_completion` - - Example minimum valid snapshot: - ```json - { - "campaign_id": "security-alert-burndown", - "date": "2025-12-22", - "tasks_total": 0, - "tasks_completed": 0 - } - ``` - - **Read budget**: max discovery items per run: 100 @@ -1381,8 +1351,6 @@ jobs: 1) Read the precomputed discovery manifest: `./.gh-aw/campaign.discovery.json` - This manifest contains all discovered worker outputs with normalized metadata - PROMPT_EOF - cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT" - Schema version: v1 - Fields: campaign_id, generated_at, discovery (total_items, cursor info), summary (counts), items (array of normalized items) @@ -1413,6 +1381,8 @@ jobs: - `start_date`: format `created_at` as `YYYY-MM-DD` - `end_date`: - if closed/merged → format `closed_at`/`merged_at` as `YYYY-MM-DD` + PROMPT_EOF + cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT" - if open → **today's date** formatted `YYYY-MM-DD` (required for roadmap view) **Why use today for open items?** - GitHub Projects requires end_date for roadmap views. Using today's date shows the item is actively tracked and updates automatically each run until completion. diff --git a/pkg/workflow/mcp_utilities_test.go b/pkg/workflow/mcp_utilities_test.go index fad3f768ae..1f15c610c2 100644 --- a/pkg/workflow/mcp_utilities_test.go +++ b/pkg/workflow/mcp_utilities_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestShellQuote(t *testing.T) { @@ -67,6 +68,21 @@ func TestShellQuote(t *testing.T) { input: "echo 'hello' \"world\" $VAR `cmd`", expected: "'echo '\\''hello'\\'' \"world\" $VAR `cmd`'", }, + { + name: "command injection attempt with semicolon", + input: "file; rm -rf /", + expected: "'file; rm -rf /'", + }, + { + name: "command injection attempt with pipe", + input: "file | cat /etc/passwd", + expected: "'file | cat /etc/passwd'", + }, + { + name: "multiple single quotes", + input: "it's a test's file", + expected: "'it'\\''s a test'\\''s file'", + }, } for _, tt := range tests { @@ -128,6 +144,16 @@ func TestBuildDockerCommandWithExpandableVars(t *testing.T) { input: "", expected: "", }, + { + name: "injection attempt in GITHUB_WORKSPACE context", + input: "${GITHUB_WORKSPACE}; rm -rf /", + expected: "''\"${GITHUB_WORKSPACE}\"'; rm -rf /'", + }, + { + name: "multiple variables mixed with GITHUB_WORKSPACE", + input: "${GITHUB_WORKSPACE}/src ${OTHER_VAR}/dst", + expected: "''\"${GITHUB_WORKSPACE}\"'/src ${OTHER_VAR}/dst'", + }, } for _, tt := range tests { @@ -143,9 +169,21 @@ func TestBuildDockerCommandWithExpandableVars_PreservesVariableExpansion(t *test input := "docker run -v ${GITHUB_WORKSPACE}:/workspace" result := buildDockerCommandWithExpandableVars(input) - // Verify the result contains the variable in a form that can be expanded - assert.Contains(t, result, "${GITHUB_WORKSPACE}", "Result should preserve GITHUB_WORKSPACE variable for expansion") + // Use require.* for the first critical check (fail-fast) + require.Contains(t, result, "${GITHUB_WORKSPACE}", "Result should preserve GITHUB_WORKSPACE variable for expansion") - // Verify the variable is properly quoted to prevent injection + // Use assert.* for the second check (still runs if we get here) assert.Contains(t, result, "\"${GITHUB_WORKSPACE}\"", "GITHUB_WORKSPACE should be in double quotes for safe expansion") } + +func TestBuildDockerCommandWithExpandableVars_UnbracedVariable(t *testing.T) { + // Test that $GITHUB_WORKSPACE (without braces) is handled + // The current implementation only handles ${GITHUB_WORKSPACE} (with braces) + // and treats $GITHUB_WORKSPACE as a regular shell character that gets quoted + input := "docker run -v $GITHUB_WORKSPACE:/workspace" + result := buildDockerCommandWithExpandableVars(input) + + // Document current behavior: unbraced $GITHUB_WORKSPACE is quoted normally + assert.Equal(t, "'docker run -v $GITHUB_WORKSPACE:/workspace'", result, + "Unbraced $GITHUB_WORKSPACE should be quoted normally (not preserved for expansion)") +}