From d01dbb38b26bb766fafe201e61ec6b6174858846 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:43:54 +0000 Subject: [PATCH 1/3] Initialize pull request for Daily Compiler Threat Spec Optimizer From 38e0ea9c2437143d1e6b4ae41f5bb9453be76ff1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:53:28 +0000 Subject: [PATCH 2/3] Initialize pull request for Daily Compiler Threat Spec Optimizer From 3c9d8d1b507fc7a3671ce21b8a3c1060cdccb2bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:53:29 +0000 Subject: [PATCH 3/3] Add CTR-027 pre-created PR branch confusion to threat detection spec Documents the already-implemented validate-pre-created-pull-request activation-job step (#55481) that re-validates the pre-created steer PR's branch and repository identity before privileged agent/safe-outputs checkout, and the branch-prefix static/valid-git-ref-prefix validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- specs/compiler-threat-detection-spec.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/specs/compiler-threat-detection-spec.md b/specs/compiler-threat-detection-spec.md index 2b19f2d06f1..479aa924ec5 100644 --- a/specs/compiler-threat-detection-spec.md +++ b/specs/compiler-threat-detection-spec.md @@ -7,7 +7,7 @@ sidebar: # GitHub Actions Compiler Threat Detection Specification -**Version**: 1.0.26 +**Version**: 1.0.27 **Status**: Candidate Recommendation **Latest Version**: https://github.com/github/gh-aw/blob/main/specs/compiler-threat-detection-spec.md **Editors**: GitHub Next (GitHub, Inc.) @@ -78,6 +78,7 @@ This section anchors the specification version to the minimum gh-aw binary versi | Spec version | Minimum gh-aw binary version | Lock-file compatibility notes | |--------------|------------------------------|-------------------------------| +| `1.0.27` | `v0.87.5` (or newer) | Adds CTR-027 Pre-Created Pull Request Branch Confusion, documenting the already-implemented `validate-pre-created-pull-request` activation-job step (`addActivationPreCreatePullRequestStep` in `pkg/workflow/compiler_precreate_pull_request.go`, `validate_pre_created_pull_request.cjs`) that re-validates the pre-created pull request's branch and repository identity before privileged `agent`/`safe-outputs` checkout in `safe-outputs.create-pull-request.steer: true` workflows, plus static/valid-git-ref-prefix validation for `branch-prefix`. `.lock.yml` files for steer-enabled workflows now include an additional `validate-pre-created-pull-request` step in the `activation` job and reference `steps.validate-pre-created-pull-request.outputs.branch` (instead of the raw `pre-create-pull-request` output) in downstream checkout `ref:` values; recompilation of affected workflows changes these lines but introduces no new secrets or action-reference manifest impact (no CTR-016 impact). | | `1.0.26` | `v0.87.4` (or newer) | Adds CTR-026 Generated Job Timeout Expression Injection, documenting the already-implemented rejection of expression/non-positive-integer `jobs.agent.timeout-minutes` and `jobs.detection.timeout-minutes` values (`extractCustomJobTimeoutMinutes` in `pkg/workflow/compiler_custom_job_properties.go`). `.lock.yml` files now always emit a literal integer job-level `timeout-minutes` for the generated `agent` and `detection` jobs instead of a `${{ vars.GH_AW_DEFAULT_*_TIMEOUT_MINUTES || '' }}` expression; recompilation of existing workflows changes these two lines but does not alter secrets or action-reference manifests (no CTR-016 impact). | | `1.0.25` | `v0.87.1` (or newer) | No new CTR rules; the CTR-025 mapping is re-confirmed against the merged `fix-threat-detection-system-block-false-positive` changeset (`stripFrameworkSystemBlock`/`SYSTEM_BLOCK_REMOVED_MARKER` in `actions/setup/js/setup_threat_detection.cjs`), which matches the existing Section 7.1 mapping with no drift. No `.lock.yml` schema changes. | | `1.0.24` | `v0.87.1` (or newer) | No new CTR rules; extends existing rule mappings (CTR-005, CTR-006/CTR-009, CTR-007, CTR-012) to cover recently-hardened implementation sites reviewed in this cycle (safe-output field allowlisting, agent-import-path shell escaping, URL-authority userinfo bypass in the markdown/content sanitizer, and generalized wildcard-target validation). No `.lock.yml` schema changes. | @@ -90,7 +91,7 @@ This section anchors the specification version to the minimum gh-aw binary versi Compact changelog: `1.0.8` introduced CTR-016 and CTR-018; `1.0.10`–`1.0.13` added CTR-019; `1.0.14` added CTR-020; `1.0.15` added CTR-021; `1.0.20` added CTR-022 and CTR-023; `1.0.21` corrects the Deprecation Policy subsection numbering; `1.0.23` -adds CTR-025; and `1.0.26` adds CTR-026. Versions with no distinct lock-file impact are grouped above. +adds CTR-025; `1.0.26` adds CTR-026; and `1.0.27` adds CTR-027. Versions with no distinct lock-file impact are grouped above. When this specification version changes, maintainers MUST update this table in the same pull request as any lock-file compatibility changes. @@ -158,6 +159,7 @@ A conforming implementation MUST include detection coverage for at least the fol - **CTR-023 Bash Command Allowlist Illusion**: Detect when workflow frontmatter declares an explicit `tools.bash` restriction (`bash: false`, `bash: []`, or a non-wildcard command list such as `bash: ["git", "npm"]`) for a coding agent engine that does not enforce a bash command allowlist. Some engines silently ignore restricted `tools.bash` configurations at runtime, creating the dangerous illusion that bash execution is restricted when in fact all commands remain permitted. Reject compilation with an error identifying the unsupported engine and the ignored restriction; unrestricted (`bash: true` or absent) and wildcard (`bash: ["*"]`, `bash: [":*"]`) configurations are unaffected because they do not depend on engine-side enforcement. - **CTR-025 Framework Self-Prompt Misattribution**: Detect and neutralize threat-detection false positives caused by gh-aw framework-generated prompt scaffolding by stripping only a leading `...` block from the analyzed prompt artifact before threat analysis. The removal MUST be position-bound to the first block only; ``-lookalike markup that appears later in prompt content MUST remain intact so attacker-supplied injections are still visible to analysis. - **CTR-026 Generated Job Timeout Expression Injection**: Detect and reject non-positive-integer or expression values (including `${{ ... }}` GitHub Actions expressions, zero, negative numbers, non-integral floats, and out-of-range values) for `jobs.agent.timeout-minutes` and `jobs.detection.timeout-minutes`. Because these generated jobs' step budgets (including setup and teardown) are computed at compile time from the resolved literal, an expression or non-positive value would defer resolution to workflow-run time or produce an unbounded/invalid job budget, undermining the job-level timeout guarantee that prevents a hung or malicious setup/teardown step from consuming runner capacity indefinitely. Reject compilation identifying the field and the required positive-integer-literal form in all modes; this is a hard compile-time boundary, not a strict-mode-only enhancement. +- **CTR-027 Pre-Created Pull Request Branch Confusion**: Detect and neutralize branch-confusion attacks against `safe-outputs.create-pull-request.steer: true` workflows, where a pre-created pull request's `branch` output (later checked out by privileged `agent` and `safe-outputs` jobs) is not independently re-validated after the activation job's `pre-create-pull-request` step returns it. Because the branch name and pull request number both originate from the same activation-job output before privileged checkout, a compromised or racing actor able to influence that output (e.g., via a colliding branch name or a substituted pull request number) could redirect privileged checkout to an attacker-controlled branch, ref, or fork. The compiler MUST emit a `validate-pre-created-pull-request` step that: (1) compares the returned `branch` output against a compile-time-derived expected branch ref built from `${{ github.run_id }}-${{ github.run_attempt }}` and the configured (or default `gh-aw/pre-created/`) branch prefix, rejecting on any mismatch before the pull request number is used; (2) validates the pull request number is a positive-integer string; (3) fetches the pull request via the GitHub API and confirms `head.ref` matches the expected branch and both `head.repo.full_name` and `base.repo.full_name` match the current trusted repository (case-insensitive), rejecting cross-repository or cross-fork redirection. The compiler MUST also reject a configured `branch-prefix` that is a GitHub Actions expression (non-static) or that does not form a valid git branch ref, since a dynamic or malformed prefix would undermine the expected-branch comparison. Downstream `ref:` checkout steps MUST consume the validated output (`steps.validate-pre-created-pull-request.outputs.branch`), not the raw activation-job output. ### 5.2 Compiler Response Requirements @@ -312,6 +314,7 @@ Implementations MUST maintain a clear mapping from each active `CTR-*` rule to c | CTR-023 Bash Command Allowlist Illusion | `pkg/workflow/agent_validation.go` (`validateBashCommandAllowlistSupport`, `hasBashExplicitRestriction`), `pkg/workflow/agentic_engine.go` (`EngineCapabilities.BashCommandAllowlist`), per-engine capability declarations in `pkg/workflow/*_engine.go` | `pkg/workflow/bash_command_allowlist_validation_test.go` (`TestValidateBashCommandAllowlistSupport`, `TestEngineBashCommandAllowlistCapability`) | | CTR-025 Framework Self-Prompt Misattribution | `actions/setup/js/setup_threat_detection.cjs` (`stripFrameworkSystemBlock`, `SYSTEM_BLOCK_REMOVED_MARKER`) | `actions/setup/js/setup_threat_detection.test.cjs` (`removes the leading framework system block from the analyzed prompt file`, `removes only the first system block and preserves later lookalikes`) | | CTR-026 Generated Job Timeout Expression Injection | `pkg/workflow/compiler_custom_job_properties.go` (`extractCustomJobTimeoutMinutes`, rejects expressions/non-positive values for the `agent` and `detection` generated jobs), `pkg/workflow/job_timeouts.go` (`resolveAgentJobTimeoutValue`, `resolveDetectionJobTimeoutValue`, `builtinJobTimeoutOverride`) | `pkg/workflow/compiler_custom_jobs_test.go` (`TestApplyBuiltinJobAugmentations_RejectsTimeoutMinutesExpression`, `TestExtractCustomJobTimeoutMinutes`, `TestBuildCustomJob_InvalidTimeoutMinutesError`), `pkg/workflow/job_timeouts_test.go` (`TestResolveAgentJobTimeoutValue`, `TestResolveDetectionJobTimeoutValue`, `TestGeneratedAgentJobTimeoutMinutes`) | +| CTR-027 Pre-Created Pull Request Branch Confusion | `pkg/workflow/compiler_precreate_pull_request.go` (`preCreatedPullRequestBranchPrefix`, `preCreatedPullRequestBranchRef`, `addActivationPreCreatePullRequestStep` emitting the `validate-pre-created-pull-request` step), `pkg/workflow/create_pull_request.go` (branch-prefix static-expression and valid-git-ref validation), `actions/setup/js/validate_pre_created_pull_request.cjs` (expected-branch comparison, pull request number format check, cross-repo/fork `head.repo`/`base.repo` confirmation) | `pkg/workflow/compiler_precreate_pull_request_test.go` (`TestPreCreatePullRequestCheckoutOverride`, `TestPreCreatePullRequestCheckoutOverrideUsesConfiguredBranchPrefix`, `TestValidatePreCreatePullRequest`), `pkg/workflow/compiler_precreate_pull_request_integration_test.go`, `actions/setup/js/validate_pre_created_pull_request.test.cjs` | The mappings above are pattern-based references and MUST be validated against concrete file paths whenever this specification is updated. @@ -382,6 +385,7 @@ The following test IDs map one-to-one to the CTR rules in Section 5.1. Each test | **T-CTR-023** | CTR-023 Bash Command Allowlist Illusion | A workflow frontmatter declares `tools.bash: false`, `tools.bash: []`, or a non-wildcard command list (e.g., `tools.bash: ["git", "npm"]`) while the configured engine's `EngineCapabilities.BashCommandAllowlist` is `false` (e.g., `codex`) | Compilation failure with error identifying the engine, stating the restriction is silently ignored at runtime, and suggesting `bash: ["*"]`, removing the entry, or switching to an engine that supports allowlist enforcement (copilot, claude, gemini) | `CTR-023` | | **T-CTR-039** | CTR-025 Framework Self-Prompt Misattribution | The analyzed prompt artifact starts with a framework-generated `...` block and also contains a later ``-lookalike block in user content | Threat-detection setup strips only the leading framework block and emits the marker while preserving later ``-lookalike content for analysis | `CTR-025` | | **T-CTR-041** | CTR-026 Generated Job Timeout Expression Injection | A workflow frontmatter sets `jobs.agent.timeout-minutes` or `jobs.detection.timeout-minutes` to a GitHub Actions expression (e.g., `${{ inputs.agent-timeout }}`), zero, a negative integer, or a non-integral float | Compilation failure with error identifying the job name and field and stating that `timeout-minutes` must be a positive integer literal | `CTR-026` | +| **T-CTR-042** | CTR-027 Pre-Created Pull Request Branch Confusion | A `safe-outputs.create-pull-request.steer: true` workflow's pre-created pull request returns a `branch` output that does not match the expected `-` ref, or the fetched pull request's `head.repo`/`base.repo` does not match the current repository, or `branch-prefix` is configured as a GitHub Actions expression or an invalid git ref prefix | The `validate-pre-created-pull-request` step rejects the mismatch before privileged checkout; compilation rejects a non-static or invalid `branch-prefix` with an error identifying the required static valid-git-ref-prefix form | `CTR-027` | ### 8.2 Optimizer Protocol Test ID Catalog @@ -426,6 +430,14 @@ These optimizer-protocol IDs cover Section 6 norms; they do not add or replace t ## 10. Change Log +### 1.0.27 (2026-08-25) + +- Daily optimizer review cycle. Reviewed compiler and parser changes merged since the 1.0.26 audit, focusing on security-sensitive diffs in `pkg/workflow/`, `pkg/parser/`, and `actions/setup/js/`: `Validate pre-created PR branch before privileged checkout` (#55481) adds a `validate-pre-created-pull-request` activation-job step (`addActivationPreCreatePullRequestStep` in `pkg/workflow/compiler_precreate_pull_request.go`, `validate_pre_created_pull_request.cjs`) that re-derives the expected pre-created branch ref from the configured (or default `gh-aw/pre-created/`) branch prefix and `${{ github.run_id }}-${{ github.run_attempt }}`, rejects any mismatch against the raw `pre-create-pull-request` output before use, validates the pull request number format, and fetches the pull request via the GitHub API to confirm `head.ref`, `head.repo.full_name`, and `base.repo.full_name` all match the trusted current repository and expected branch — closing a branch-confusion/cross-fork redirection gap where privileged `agent`/`safe-outputs` checkout previously trusted the activation-job branch output without independent re-validation. `create_pull_request.go` also now rejects a `branch-prefix` that is a non-static GitHub Actions expression or does not form a valid git branch ref. This was implemented (not merely already covered) during the review window, so it is added as new rule **CTR-027 Pre-Created Pull Request Branch Confusion** with test ID **T-CTR-042**, mapped to `pkg/workflow/compiler_precreate_pull_request_test.go` (`TestPreCreatePullRequestCheckoutOverride`, `TestPreCreatePullRequestCheckoutOverrideUsesConfiguredBranchPrefix`, `TestValidatePreCreatePullRequest`), `pkg/workflow/compiler_precreate_pull_request_integration_test.go`, and `actions/setup/js/validate_pre_created_pull_request.test.cjs`. +- Other reviewed changesets in the window were evaluated and found to be outside compiler-detectable threat-class scope or already covered: `Pipe threat-detection kill-switch frontmatter into compiled threat-detect invocation` (#55532, adds optional `engine-timeout`/`max-turns`/`retries` cost/time-governance flags to the `threat-detect` invocation via `safe-outputs.threat-detection` frontmatter with defensive multi-type parsing and schema validation for negative/malformed values — an operational governance control for the detection tooling itself, not a new detectable compiler threat class; no existing CTR rule covers detector invocation cost controls and none is warranted, as invalid values are rejected by ordinary schema/type validation rather than a security boundary); `Fix conclusion job issues permission derivation` (#55529, corrects `issues: write` permission derivation in the conclusion job to key off `safe-outputs.missing-tool.create-issue` instead of the removed `detection-runs` reporting path — a permissions-derivation correctness fix within existing CTR-001 scope, not a new threat class); `[test-parallel]` and `[dead-code]` batches (#55558, #55537, #55506, #55472, #55450, #55402) (test-only parallelization and dead-code removal, no validation logic changed); `Split pkg/workflow/awf_config.go into types / schema / build / policy files` (#55496) and `Split safe outputs handler registry by category` (#55482) (internal file-organization refactors preserving existing CTR-mapped validation entry points, no behavior change); `Guard allocation size calculations flagged by CodeQL` (#55480, defensive integer-overflow guard on an internal allocation size computation, not attacker-reachable through workflow frontmatter, no new CTR rule); `Deduplicate token-usage and integrity-level types flagged by Typist` (#55518) and `Improve test coverage and assertions in pkg/cli/logs_firewall_parse_test.go` (#55522) (type deduplication and test-assertion hardening, no detection-logic change). +- No `threat-detection-suppress` annotations were found in any live (non-fixture, non-documentation-example) workflow source in this review window, so no SLA-breach or expiration findings apply. +- Extended Section 5.1 with CTR-027, Section 7.1 baseline rule mapping with the CTR-027 row, and Section 8.1 with T-CTR-042. +- Updated Section 2 spec-to-implementation sync table with version 1.0.27 mapped to minimum binary `v0.87.5`, noting the additional `validate-pre-created-pull-request` activation-job step and the downstream checkout `ref:` output rename for steer-enabled `.lock.yml` files (no secrets/action-manifest impact). + ### 1.0.26 (2026-08-23) - Daily optimizer review cycle. Reviewed compiler and parser changes merged since the 1.0.25 audit (`d2432aca6`..`HEAD` at `11a1dcf84`): `Enforce positive-integer jobs.*.timeout-minutes in compiled lock files` (#54884) rejects GitHub Actions expressions, zero, negative, and non-integral values for `jobs.agent.timeout-minutes` and `jobs.detection.timeout-minutes` in `extractCustomJobTimeoutMinutes` (`pkg/workflow/compiler_custom_job_properties.go`), and removes the previous `${{ vars.GH_AW_DEFAULT_*_TIMEOUT_MINUTES || '' }}` runtime-variable indirection in favor of a compile-time-resolved literal integer (`resolveAgentJobTimeoutValue`/`resolveDetectionJobTimeoutValue` in `pkg/workflow/job_timeouts.go`). This closes a gap where an author- or import-controlled expression could defer a generated job's timeout budget to workflow-run time (or an invalid non-positive value could produce an unbounded/undefined job budget), undermining the job-level timeout guarantee that bounds setup, agentic execution, and teardown steps against a hung or malicious step consuming runner capacity indefinitely. This was implemented (not merely already covered) during the review window, so it is added as new rule **CTR-026 Generated Job Timeout Expression Injection** with test ID **T-CTR-041**, mapped to `TestApplyBuiltinJobAugmentations_RejectsTimeoutMinutesExpression`, `TestExtractCustomJobTimeoutMinutes`, `TestBuildCustomJob_InvalidTimeoutMinutesError` (`pkg/workflow/compiler_custom_jobs_test.go`) and `TestResolveAgentJobTimeoutValue`, `TestResolveDetectionJobTimeoutValue`, `TestGeneratedAgentJobTimeoutMinutes` (`pkg/workflow/job_timeouts_test.go`).