Skip to content

Commit dac0da3

Browse files
Copilotlpcoxgithub-actions[bot]claudeCopilot
authored
Add byok-copilot feature flag for Copilot offline BYOK mode wiring and bump default firewall to v0.25.21 (#26544)
* Initial plan * feat: add byok-copilot feature flag support for copilot workflows Agent-Logs-Url: https://github.com/github/gh-aw/sessions/31aedde0-6b77-46bd-81d2-65765f154fc1 Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * docs(adr): add draft ADR-26544 for byok-copilot feature flag composition Generated by Design Decision Gate workflow. Captures the decision to introduce byok-copilot as a composing feature flag that bundles cli-proxy enablement, dummy API key injection, and latest-version install for Copilot offline BYOK mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump default firewall to v0.25.21 Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ea79c99-08ff-4adf-b710-b7eb8a71e584 Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: address review feedback for byok-copilot feature flag - Move COPILOT_API_KEY injection after engine/agent env merges so legacy manual wiring in engine.env or agent.env cannot overwrite the dummy sentinel value that triggers AWF BYOK detection - Always set GH_AW_FEATURES env var in feature tests (including empty string) to prevent flakiness from inherited outer process environment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Landon Cox <landon.cox@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent dae7d5f commit dac0da3

File tree

14 files changed

+252
-14
lines changed

14 files changed

+252
-14
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# ADR-26544: BYOK-Copilot Feature Flag as a Composing Abstraction for Offline Mode Wiring
2+
3+
**Date**: 2026-04-16
4+
**Status**: Draft
5+
**Deciders**: lpcox, Copilot (PR author)
6+
7+
---
8+
9+
## Part 1 — Narrative (Human-Friendly)
10+
11+
### Context
12+
13+
Copilot offline BYOK (Bring Your Own Key) mode requires three separate pieces to work correctly together: injecting a well-known dummy API key (`COPILOT_API_KEY`) into the execution environment to trigger AWF's runtime BYOK-detection path, enabling the `cli-proxy` feature so that `gh` CLI calls are routed through the authenticated DIFC proxy sidecar, and forcing the Copilot CLI installer to use `latest` instead of a pinned version. Previously, workflow authors had to compose all three behaviors manually in their workflow frontmatter, which was error-prone and created a high documentation burden. The system needed a first-class abstraction that bundled the required wiring behind a single, discoverable switch.
14+
15+
### Decision
16+
17+
We will introduce a `byok-copilot` feature flag that, when enabled on a `copilot`-engine workflow, automatically composes the three required behaviors: it injects `COPILOT_API_KEY: dummy-byok-key-for-offline-mode` into the execution environment, implicitly enables the `cli-proxy` feature (for Copilot engine only), and overrides the engine version to `latest` during installation. The implicit `cli-proxy` enablement is implemented inside `isFeatureEnabled` as a special case: when the `cli-proxy` flag is queried, the function first checks whether `byok-copilot` is active on a Copilot engine workflow and returns `true` if so. The dummy API key is a well-known sentinel value; the real credential never leaves the AWF API proxy sidecar.
18+
19+
### Alternatives Considered
20+
21+
#### Alternative 1: Require explicit frontmatter composition
22+
23+
Workflow authors could be required to set `cli-proxy`, `COPILOT_API_KEY`, and the version field individually. This avoids any hidden behavior in the feature flag system, and every enabled behavior is visible at the frontmatter layer. It was rejected because the combination is mandatory for BYOK to work—there is no valid use case for enabling one without the others—and requiring authors to assemble all three manually introduces a category of silent misconfiguration bugs that are hard to diagnose at runtime.
24+
25+
#### Alternative 2: A "preset" system separate from feature flags
26+
27+
A named preset (e.g., `preset: byok-copilot`) could expand to a validated set of options at compile time, distinct from the existing feature flag mechanism. This would avoid adding implicit logic to `isFeatureEnabled` and could be extended to other preset bundles in the future. It was not chosen because it would require introducing a new schema concept (`preset`) alongside the existing `features` map, increasing the surface area of the workflow YAML language without clear benefit over the simpler feature flag approach for a single-flag use case.
28+
29+
#### Alternative 3: Inject all BYOK wiring unconditionally for all Copilot workflows
30+
31+
The AWF runtime could detect BYOK mode automatically (e.g., by the absence of a real API key) and apply all necessary wiring without any explicit flag. This removes the flag entirely and avoids any frontmatter requirement. It was rejected because it would silently alter the execution environment for all Copilot workflows, making it harder to understand why certain environment variables or proxy flags appear, and it couples the BYOK decision to runtime inference rather than explicit author intent.
32+
33+
### Consequences
34+
35+
#### Positive
36+
- Workflow authors enable BYOK mode with a single line (`byok-copilot: true`) instead of assembling three separate, order-sensitive options.
37+
- Misconfiguration is structurally eliminated: all three required behaviors are either all present or all absent.
38+
- The dummy key constant (`CopilotBYOKDummyAPIKey`) is centralised in `pkg/constants`, making the sentinel value discoverable and easy to update if the AWF BYOK detection signal changes.
39+
40+
#### Negative
41+
- `isFeatureEnabled` now contains an implicit cross-flag dependency (`cli-proxy``byok-copilot`). Readers of the feature evaluation code must know this special case exists; it is not visible from the frontmatter alone.
42+
- The implicit `cli-proxy` enablement is engine-scoped (Copilot only), adding a conditional that future maintainers must understand when adding new engines or new flag interactions.
43+
- Forcing `latest` on BYOK install overrides any pinned version the author may have set, which may cause unexpected behavior if a regression is introduced in the latest Copilot CLI release.
44+
45+
#### Neutral
46+
- The glossary entry for `features` has been updated to document `byok-copilot` semantics.
47+
- The dummy key value (`dummy-byok-key-for-offline-mode`) is opaque to the AWF container; the real credential path is entirely in the AWF API proxy sidecar and unchanged by this PR.
48+
- Tests for the new flag are co-located with the existing engine tests, following the project's existing test organization pattern.
49+
50+
---
51+
52+
## Part 2 — Normative Specification (RFC 2119)
53+
54+
> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
55+
56+
### Feature Flag Behavior
57+
58+
1. Implementations **MUST** treat `byok-copilot` as a valid `FeatureFlag` constant defined in `pkg/constants/feature_constants.go`.
59+
2. When `byok-copilot` is enabled on a workflow with `engine: copilot`, implementations **MUST** inject the value of `CopilotBYOKDummyAPIKey` as the `COPILOT_API_KEY` environment variable into the Copilot execution step.
60+
3. When `byok-copilot` is enabled on a workflow with `engine: copilot`, implementations **MUST** treat the `cli-proxy` feature flag as enabled, regardless of whether it is explicitly set in the workflow frontmatter.
61+
4. The implicit `cli-proxy` enablement described above **MUST NOT** apply to workflows using engines other than `copilot`.
62+
5. When `byok-copilot` is enabled, implementations **MUST** override the Copilot CLI install version to `latest`, ignoring any `engine.version` value set in the workflow frontmatter.
63+
64+
### Dummy Key Constant
65+
66+
1. The placeholder API key used for BYOK detection **MUST** be sourced from the `CopilotBYOKDummyAPIKey` constant in `pkg/constants/engine_constants.go`.
67+
2. Implementations **MUST NOT** embed the dummy key value as a string literal outside of the constants package.
68+
3. The dummy key **MUST NOT** be treated as a real credential; the real AWF API proxy credential **SHALL** remain isolated in the sidecar and **MUST NOT** be injected into the workflow container environment.
69+
70+
### Feature Composition Logic
71+
72+
1. Implicit feature enablement rules (such as `byok-copilot` enabling `cli-proxy`) **MUST** be implemented inside the `isFeatureEnabled` function in `pkg/workflow/features.go`.
73+
2. Each implicit enablement rule **MUST** be scoped to the specific engine or context for which it applies and **MUST NOT** affect unrelated engines or feature flags.
74+
3. Implementations **SHOULD** log a message when an implicit feature enablement rule is triggered, to aid in runtime diagnostics.
75+
76+
### Conformance
77+
78+
An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance.
79+
80+
---
81+
82+
*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24490726928) workflow. The PR author must review, complete, and finalize this document before the PR can merge.*

docs/src/content/docs/reference/glossary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ See [Engines Reference](/gh-aw/reference/engines/).
291291

292292
### Feature Flags (`features:`)
293293

294-
A frontmatter section that enables experimental or optional compiler and runtime behaviors as key-value pairs. Feature flags provide controlled access to new capabilities before they become defaults or are fully stabilized. Common flags include `action-mode` (controls how custom action references are compiled), `copilot-requests` (enables GitHub Actions token authentication for Copilot; currently in **private preview** — will not work unless your account has been onboarded), `mcp-gateway` (enables the MCP gateway proxy), `integrity-reactions` (enables reaction-based integrity promotion and demotion), and `cli-proxy` (enables CLI proxy mode for integrity enforcement at the network boundary). See [Frontmatter Reference](/gh-aw/reference/frontmatter/#feature-flags-features).
294+
A frontmatter section that enables experimental or optional compiler and runtime behaviors as key-value pairs. Feature flags provide controlled access to new capabilities before they become defaults or are fully stabilized. Common flags include `action-mode` (controls how custom action references are compiled), `copilot-requests` (enables GitHub Actions token authentication for Copilot; currently in **private preview** — will not work unless your account has been onboarded), `byok-copilot` (enables Copilot offline BYOK mode with dummy `COPILOT_API_KEY`, API proxy sidecar, implicit `cli-proxy`, and latest Copilot CLI install), `mcp-gateway` (enables the MCP gateway proxy), `integrity-reactions` (enables reaction-based integrity promotion and demotion), and `cli-proxy` (enables CLI proxy mode for integrity enforcement at the network boundary). See [Frontmatter Reference](/gh-aw/reference/frontmatter/#feature-flags-features).
295295

296296
### Fuzzy Scheduling
297297

pkg/constants/engine_constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ const (
190190
// CopilotCLIIntegrationIDValue is the value of the integration ID for agentic workflows.
191191
CopilotCLIIntegrationIDValue = "agentic-workflows"
192192

193+
// CopilotBYOKDummyAPIKey is the placeholder API key used to trigger AWF's
194+
// runtime BYOK detection for Copilot offline mode. The real credential remains
195+
// isolated in the AWF API proxy sidecar.
196+
CopilotBYOKDummyAPIKey = "dummy-byok-key-for-offline-mode"
197+
193198
// ClaudeCLIModelEnvVar is the native environment variable name supported by the Claude Code CLI
194199
// for selecting the model. Setting this env var is equivalent to passing --model to the CLI.
195200
ClaudeCLIModelEnvVar = "ANTHROPIC_MODEL"

pkg/constants/feature_constants.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ const (
4949
// features:
5050
// copilot-integration-id: true
5151
CopilotIntegrationIDFeatureFlag FeatureFlag = "copilot-integration-id"
52+
// ByokCopilotFeatureFlag enables Copilot CLI offline BYOK mode.
53+
// When enabled with engine: copilot, the compiler:
54+
// - injects a dummy COPILOT_API_KEY into the agent env to trigger AWF BYOK runtime behavior
55+
// - implicitly enables the cli-proxy feature
56+
// - installs the latest Copilot CLI version (un-pinned)
57+
//
58+
// Workflow frontmatter usage:
59+
//
60+
// features:
61+
// byok-copilot: true
62+
ByokCopilotFeatureFlag FeatureFlag = "byok-copilot"
5263
// IntegrityReactionsFeatureFlag enables reaction-based integrity promotion/demotion
5364
// in the MCPG allow-only policy. When enabled, the compiler injects
5465
// endorsement-reactions and disapproval-reactions fields into the allow-only policy.

pkg/constants/version_constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const DefaultGeminiVersion Version = "0.37.2"
5151
const DefaultGitHubMCPServerVersion Version = "v0.32.0"
5252

5353
// DefaultFirewallVersion is the default version of the gh-aw-firewall (AWF) binary
54-
const DefaultFirewallVersion Version = "v0.25.20"
54+
const DefaultFirewallVersion Version = "v0.25.21"
5555

5656
// AWFExcludeEnvMinVersion is the minimum AWF version that supports the --exclude-env flag.
5757
// Workflows pinning an older AWF version must not emit --exclude-env flags or the run will fail.

pkg/workflow/awf_helpers_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77
"testing"
88

9+
"github.com/github/gh-aw/pkg/constants"
910
"github.com/stretchr/testify/assert"
1011
)
1112

@@ -790,6 +791,31 @@ func TestBuildAWFArgsCliProxy(t *testing.T) {
790791
assert.NotContains(t, argsStr, "--cli-proxy-policy", "Should not include deprecated --cli-proxy-policy")
791792
})
792793

794+
t.Run("includes cli-proxy flags when byok-copilot is enabled", func(t *testing.T) {
795+
config := AWFCommandConfig{
796+
EngineName: "copilot",
797+
WorkflowData: &WorkflowData{
798+
Name: "test-workflow",
799+
EngineConfig: &EngineConfig{
800+
ID: "copilot",
801+
},
802+
NetworkPermissions: &NetworkPermissions{
803+
Firewall: &FirewallConfig{Enabled: true, Version: "v0.26.0"},
804+
},
805+
Features: map[string]any{
806+
string(constants.ByokCopilotFeatureFlag): true,
807+
},
808+
},
809+
AllowedDomains: "github.com",
810+
}
811+
812+
args := BuildAWFArgs(config)
813+
argsStr := strings.Join(args, " ")
814+
815+
assert.Contains(t, argsStr, "--difc-proxy-host", "Should include --difc-proxy-host when byok-copilot is enabled")
816+
assert.Contains(t, argsStr, "--difc-proxy-ca-cert", "Should include --difc-proxy-ca-cert when byok-copilot is enabled")
817+
})
818+
793819
t.Run("does not include deprecated flags even with guard policy configured", func(t *testing.T) {
794820
config := AWFCommandConfig{
795821
EngineName: "copilot",

pkg/workflow/copilot_engine_execution.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,13 @@ touch %s
412412
copilotExecLog.Printf("Added %d custom env vars from agent config", len(agentConfig.Env))
413413
}
414414

415+
// Inject the dummy COPILOT_API_KEY AFTER all env merges so that legacy/manual
416+
// wiring in engine.env or agent.env cannot accidentally overwrite the sentinel
417+
// value that triggers AWF's runtime BYOK detection path.
418+
if isFeatureEnabled(constants.ByokCopilotFeatureFlag, workflowData) {
419+
env["COPILOT_API_KEY"] = constants.CopilotBYOKDummyAPIKey
420+
}
421+
415422
// Add HTTP MCP header secrets to env for passthrough
416423
headerSecrets := collectHTTPMCPHeaderSecrets(workflowData.Tools)
417424
for varName, secretExpr := range headerSecrets {

pkg/workflow/copilot_engine_installation.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ func (e *CopilotEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHu
6262
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Version != "" {
6363
copilotVersion = workflowData.EngineConfig.Version
6464
}
65+
if isFeatureEnabled(constants.ByokCopilotFeatureFlag, workflowData) {
66+
copilotVersion = "latest"
67+
copilotInstallLog.Print("byok-copilot enabled: forcing Copilot CLI install version to latest")
68+
}
6569

6670
// Use the installer script for global installation
6771
copilotInstallLog.Print("Using new installer script for Copilot installation")

pkg/workflow/copilot_engine_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,30 @@ func TestCopilotEngineEnvOverridesTokenExpression(t *testing.T) {
14991499
})
15001500
}
15011501

1502+
func TestCopilotEngineByokFeatureSetsDummyAPIKey(t *testing.T) {
1503+
engine := NewCopilotEngine()
1504+
workflowData := &WorkflowData{
1505+
Name: "test-workflow",
1506+
EngineConfig: &EngineConfig{
1507+
ID: "copilot",
1508+
},
1509+
Features: map[string]any{
1510+
string(constants.ByokCopilotFeatureFlag): true,
1511+
},
1512+
}
1513+
1514+
steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/test.log")
1515+
if len(steps) != 1 {
1516+
t.Fatalf("Expected 1 step, got %d", len(steps))
1517+
}
1518+
1519+
stepContent := strings.Join([]string(steps[0]), "\n")
1520+
expected := "COPILOT_API_KEY: " + constants.CopilotBYOKDummyAPIKey
1521+
if !strings.Contains(stepContent, expected) {
1522+
t.Errorf("Expected byok-copilot to inject dummy COPILOT_API_KEY, got:\n%s", stepContent)
1523+
}
1524+
}
1525+
15021526
func TestCopilotEngineDriverScript(t *testing.T) {
15031527
engine := NewCopilotEngine()
15041528

pkg/workflow/copilot_installer_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,38 @@ func TestCopilotInstallerExpressionVersion_ViaEngineConfig(t *testing.T) {
246246
t.Errorf("Expression should NOT be embedded directly in shell command, got:\n%s", installStep)
247247
}
248248
}
249+
250+
func TestCopilotInstallerByokFeatureUsesLatestVersion(t *testing.T) {
251+
engine := NewCopilotEngine()
252+
workflowData := &WorkflowData{
253+
Name: "test-workflow",
254+
EngineConfig: &EngineConfig{
255+
Version: "1.0.0",
256+
},
257+
Features: map[string]any{
258+
string(constants.ByokCopilotFeatureFlag): true,
259+
},
260+
}
261+
262+
steps := engine.GetInstallationSteps(workflowData)
263+
264+
var installStep string
265+
for _, step := range steps {
266+
stepContent := strings.Join(step, "\n")
267+
if strings.Contains(stepContent, "install_copilot_cli.sh") {
268+
installStep = stepContent
269+
break
270+
}
271+
}
272+
273+
if installStep == "" {
274+
t.Fatal("Could not find install step with install_copilot_cli.sh")
275+
}
276+
277+
if !strings.Contains(installStep, `install_copilot_cli.sh" latest`) {
278+
t.Errorf("Expected byok-copilot to force latest Copilot CLI install, got:\n%s", installStep)
279+
}
280+
if strings.Contains(installStep, `install_copilot_cli.sh" 1.0.0`) {
281+
t.Errorf("Expected byok-copilot to ignore pinned version, got:\n%s", installStep)
282+
}
283+
}

0 commit comments

Comments
 (0)