Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/weekly-blog-post-writer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions actions/setup/sh/start_difc_proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,17 @@ for i in $(seq 1 30); do
fi
if curl -sf "https://localhost:18443/api/v3/health" -o /dev/null 2>/dev/null; then
echo "DIFC proxy ready on port 18443"
# Route gh CLI calls through the proxy.
echo "GH_HOST=localhost:18443" >> "$GITHUB_ENV"
git remote add proxy "https://localhost:18443/${GITHUB_REPOSITORY}.git" || true
# Route actions/github-script Octokit calls through the proxy.
# Save the originals so stop_difc_proxy.sh can restore them.
echo "GH_AW_ORIGINAL_GITHUB_API_URL=${GITHUB_API_URL:-}" >> "$GITHUB_ENV"
echo "GH_AW_ORIGINAL_GITHUB_GRAPHQL_URL=${GITHUB_GRAPHQL_URL:-}" >> "$GITHUB_ENV"
echo "GITHUB_API_URL=https://localhost:18443/api/v3" >> "$GITHUB_ENV"
echo "GITHUB_GRAPHQL_URL=https://localhost:18443/api/graphql" >> "$GITHUB_ENV"
# Trust the proxy TLS certificate from Node.js (used by actions/github-script).
echo "NODE_EXTRA_CA_CERTS=$PROXY_LOG_DIR/proxy-tls/ca.crt" >> "$GITHUB_ENV"
Comment on lines +69 to +79
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start_difc_proxy.sh now overrides GH_HOST and NODE_EXTRA_CA_CERTS but only saves/restores the original values for GITHUB_API_URL/GITHUB_GRAPHQL_URL. If the runner/job already had GH_HOST (e.g., GitHub Enterprise) or NODE_EXTRA_CA_CERTS set, stopping the proxy will currently clear them rather than restoring the prior values. Consider saving the originals (e.g., to GH_AW_ORIGINAL_GH_HOST and GH_AW_ORIGINAL_NODE_EXTRA_CA_CERTS) alongside the existing GH_AW_ORIGINAL_* vars so stop_difc_proxy.sh can restore them.

Copilot uses AI. Check for mistakes.
PROXY_READY=true
break
fi
Expand Down
12 changes: 12 additions & 0 deletions actions/setup/sh/stop_difc_proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,16 @@ fi
if [ "${GH_HOST:-}" = "localhost:18443" ]; then
echo "GH_HOST=" >> "$GITHUB_ENV"
fi

# Restore GITHUB_API_URL and GITHUB_GRAPHQL_URL to their original values.
if [ "${GITHUB_API_URL:-}" = "https://localhost:18443/api/v3" ]; then
echo "GITHUB_API_URL=${GH_AW_ORIGINAL_GITHUB_API_URL:-}" >> "$GITHUB_ENV"
fi
if [ "${GITHUB_GRAPHQL_URL:-}" = "https://localhost:18443/api/graphql" ]; then
echo "GITHUB_GRAPHQL_URL=${GH_AW_ORIGINAL_GITHUB_GRAPHQL_URL:-}" >> "$GITHUB_ENV"
fi

# Clear the Node.js CA certs override set by start_difc_proxy.sh.
echo "NODE_EXTRA_CA_CERTS=" >> "$GITHUB_ENV"
Comment on lines +39 to +40
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stop_difc_proxy.sh always clears NODE_EXTRA_CA_CERTS. Since start_difc_proxy.sh sets NODE_EXTRA_CA_CERTS globally for subsequent steps, this can unintentionally remove a pre-existing NODE_EXTRA_CA_CERTS value in the job. Prefer restoring NODE_EXTRA_CA_CERTS to its original value (saved by start_difc_proxy.sh) instead of unconditionally clearing it.

Suggested change
# Clear the Node.js CA certs override set by start_difc_proxy.sh.
echo "NODE_EXTRA_CA_CERTS=" >> "$GITHUB_ENV"
# Restore the Node.js CA certs override only if it was set to the DIFC proxy CA
# certificate. This preserves any pre-existing NODE_EXTRA_CA_CERTS value in the job.
if [ "${NODE_EXTRA_CA_CERTS:-}" = "$DIFC_PROXY_CA_CERT" ]; then
echo "NODE_EXTRA_CA_CERTS=${GH_AW_ORIGINAL_NODE_EXTRA_CA_CERTS:-}" >> "$GITHUB_ENV"
fi

Copilot uses AI. Check for mistakes.

echo "DIFC proxy stopped"
19 changes: 14 additions & 5 deletions pkg/workflow/compiler_difc_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,33 @@ package workflow
// is running. Only gh CLI calls that honour GH_HOST are actually filtered.
//
// Note: qmd indexing GitHub API calls are made via actions/github-script (@actions/github
// Octokit), which uses GITHUB_API_URL / GITHUB_GRAPHQL_URL rather than GH_HOST. The proxy
// does not intercept those calls, so no proxy wrapping is injected into the qmd indexing job.
// Octokit). The proxy sets GITHUB_API_URL, GITHUB_GRAPHQL_URL, and NODE_EXTRA_CA_CERTS in
// addition to GH_HOST, so it intercepts Octokit calls as well. Proxy wrapping is therefore
// also injected around qmd indexing steps when DIFC guards are configured.
//
// The proxy uses the same container image as the MCP gateway (gh-aw-mcpg)
// but runs in "proxy" mode with --guards-mode filter (graceful degradation)
// and --tls (required by the gh CLI HTTPS-only constraint).
//
// Injection conditions:
//
// Main job: GitHub tool has explicit guard policies (min-integrity set) AND
// custom steps set GH_TOKEN
// Main job: GitHub tool has explicit guard policies (min-integrity set) AND
// custom steps set GH_TOKEN
// Indexing job: GitHub tool has explicit guard policies (min-integrity set)
//
// Proxy lifecycle within the main job:
// 1. Start proxy — after "Configure gh CLI" step, before custom steps
// 2. Custom steps run with GH_HOST=localhost:18443 (set via $GITHUB_ENV)
// 2. Custom steps run with GH_HOST=localhost:18443, GITHUB_API_URL, GITHUB_GRAPHQL_URL,
// and NODE_EXTRA_CA_CERTS set (via $GITHUB_ENV)
// 3. Stop proxy — before MCP gateway starts (generateMCPSetup); always runs
// even if earlier steps failed (if: always(), continue-on-error: true)
//
// Proxy lifecycle within the indexing job:
// 1. Start proxy — before qmd index-building steps
// 2. qmd steps run with all proxy env vars set (GH_HOST, GITHUB_API_URL, GITHUB_GRAPHQL_URL,
// NODE_EXTRA_CA_CERTS); Octokit calls in actions/github-script are intercepted
// 3. Stop proxy — after qmd steps; always runs (if: always(), continue-on-error: true)
//
// Guard policy note:
//
// The proxy policy uses only the static fields from the workflow's frontmatter
Expand Down
53 changes: 53 additions & 0 deletions pkg/workflow/compiler_difc_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,4 +579,57 @@ func TestDIFCProxyInjectedInIndexingJob(t *testing.T) {
assert.Contains(t, stopStep, "Stop DIFC proxy")
assert.Contains(t, stopStep, "stop_difc_proxy.sh")
})

t.Run("buildQmdIndexingJob wraps steps with proxy when guard policy configured", func(t *testing.T) {
data := &WorkflowData{
Tools: map[string]any{
"github": map[string]any{"min-integrity": "approved"},
},
QmdConfig: &QmdToolConfig{
Searches: []*QmdSearchEntry{{Query: "repo:owner/repo language:Markdown"}},
CacheKey: "qmd-test",
},
SandboxConfig: &SandboxConfig{},
}
ensureDefaultMCPGatewayConfig(data)

job, err := c.buildQmdIndexingJob(data)
require.NoError(t, err, "buildQmdIndexingJob should succeed")
require.NotNil(t, job, "job should not be nil")

allSteps := strings.Join(job.Steps, "\n")
require.Contains(t, allSteps, "Start DIFC proxy for pre-agent gh calls",
"indexing job should include proxy start step when guard policy is configured")
require.Contains(t, allSteps, "Stop DIFC proxy",
"indexing job should include proxy stop step when guard policy is configured")

// Proxy start must come before the qmd index step and proxy stop must come after.
startIdx := strings.Index(allSteps, "Start DIFC proxy for pre-agent gh calls")
stopIdx := strings.Index(allSteps, "Stop DIFC proxy")
assert.Less(t, startIdx, stopIdx, "Start proxy must come before Stop proxy in indexing job")
})

t.Run("buildQmdIndexingJob has no proxy steps without guard policy", func(t *testing.T) {
data := &WorkflowData{
Tools: map[string]any{
"github": map[string]any{"toolsets": []string{"default"}},
},
QmdConfig: &QmdToolConfig{
Searches: []*QmdSearchEntry{{Query: "repo:owner/repo language:Markdown"}},
CacheKey: "qmd-test",
},
SandboxConfig: &SandboxConfig{},
}
ensureDefaultMCPGatewayConfig(data)

job, err := c.buildQmdIndexingJob(data)
require.NoError(t, err, "buildQmdIndexingJob should succeed")
require.NotNil(t, job, "job should not be nil")

allSteps := strings.Join(job.Steps, "\n")
assert.NotContains(t, allSteps, "Start DIFC proxy",
"indexing job should NOT include proxy start step without guard policy")
assert.NotContains(t, allSteps, "Stop DIFC proxy",
"indexing job should NOT include proxy stop step without guard policy")
})
}
21 changes: 15 additions & 6 deletions pkg/workflow/qmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,14 +591,23 @@ func (c *Compiler) buildQmdIndexingJob(data *WorkflowData) (*Job, error) {
// Generate all qmd index-building steps (cache restore/save, Node.js, SDK install, github-script).
qmdSteps := generateQmdIndexSteps(data.QmdConfig)

// Note: qmd indexing GitHub API calls are made via actions/github-script (@actions/github
// Octokit), which uses GITHUB_API_URL / GITHUB_GRAPHQL_URL rather than GH_HOST.
// The DIFC proxy started by buildStartDIFCProxyStepYAML() only sets GH_HOST, so it does
// not intercept qmd's traffic. We therefore do not wrap qmd indexing steps with the proxy.
// Wrap qmd indexing steps with the DIFC proxy when guard policies are configured.
// The proxy now sets GITHUB_API_URL, GITHUB_GRAPHQL_URL, and NODE_EXTRA_CA_CERTS in
// addition to GH_HOST, so it intercepts Octokit calls made by actions/github-script
// during qmd indexing.
if hasDIFCGuardsConfigured(data) {
qmdLog.Print("DIFC guards configured; qmd indexing steps are not wrapped with GH_HOST-based DIFC proxy")
qmdLog.Print("DIFC guards configured; wrapping qmd indexing steps with DIFC proxy")
startStep := c.buildStartDIFCProxyStepYAML(data)
if startStep != "" {
steps = append(steps, startStep)
steps = append(steps, qmdSteps...)
steps = append(steps, buildStopDIFCProxyStepYAML())
} else {
steps = append(steps, qmdSteps...)
}
} else {
steps = append(steps, qmdSteps...)
}
steps = append(steps, qmdSteps...)

// The indexing job runs after the activation job to inherit the artifact prefix output.
needs := []string{string(constants.ActivationJobName)}
Expand Down
Loading