From 297e27d394695c1969df7ebb79d997b592c25ba4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 19 Dec 2025 21:25:29 +0000
Subject: [PATCH 1/2] Initial plan
From e6363c1ebfb8623dfa4bcb9176eed541f1c70f0c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 19 Dec 2025 21:46:38 +0000
Subject: [PATCH 2/2] Fix smoke-codex-firewall test to expect OpenAI access
blocked
The test incorrectly expected OpenAI domain access via curl to succeed.
However, OpenAI domains are NOT in the `defaults` or `github` network
ecosystems - they are only added by the Codex CLI engine itself.
This fix updates the test to correctly expect curl access to OpenAI
domains to be BLOCKED, validating the firewall is working correctly.
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
---
.../workflows/smoke-codex-firewall.lock.yml | 179 +++++++++++++++++-
.github/workflows/smoke-codex-firewall.md | 2 +-
2 files changed, 170 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/smoke-codex-firewall.lock.yml b/.github/workflows/smoke-codex-firewall.lock.yml
index 417ab70cbaa..ade1ca7052d 100644
--- a/.github/workflows/smoke-codex-firewall.lock.yml
+++ b/.github/workflows/smoke-codex-firewall.lock.yml
@@ -655,7 +655,7 @@ jobs:
node-version: '24'
package-manager-cache: false
- name: Install Codex
- run: npm install -g @openai/codex@0.73.0
+ run: npm install -g @openai/codex@0.75.0
- name: Install awf binary
run: |
echo "Installing awf from release: v0.7.0"
@@ -693,7 +693,7 @@ jobs:
done
}
- docker_pull_with_retry ghcr.io/github/github-mcp-server:v0.25.0
+ docker_pull_with_retry ghcr.io/github/github-mcp-server:v0.26.3
- name: Write Safe Outputs Config
run: |
mkdir -p /tmp/gh-aw/safeoutputs
@@ -2334,7 +2334,7 @@ jobs:
"GITHUB_READ_ONLY=1",
"-e",
"GITHUB_TOOLSETS=context,repos,issues,pull_requests",
- "ghcr.io/github/github-mcp-server:v0.25.0"
+ "ghcr.io/github/github-mcp-server:v0.26.3"
]
env_vars = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
@@ -2357,7 +2357,7 @@ jobs:
engine_name: "Codex",
model: process.env.GH_AW_MODEL_AGENT_CODEX || "",
version: "",
- agent_version: "0.73.0",
+ agent_version: "0.75.0",
workflow_name: "Smoke Codex Firewall",
experimental: true,
supports_tools_allowlist: true,
@@ -2374,7 +2374,7 @@ jobs:
network_mode: "defaults",
allowed_domains: ["defaults","github"],
firewall_enabled: true,
- firewall_version: "",
+ awf_version: "v0.7.0",
steps: {
firewall: "squid"
},
@@ -2421,7 +2421,7 @@ jobs:
'|----------|-------|\n' +
`| Mode | ${awInfo.network_mode || 'defaults'} |\n` +
`| Firewall | ${awInfo.firewall_enabled ? '✅ Enabled' : '❌ Disabled'} |\n` +
- `| Firewall Version | ${awInfo.firewall_version || '(latest)'} |\n` +
+ `| Firewall Version | ${awInfo.awf_version || '(latest)'} |\n` +
'\n' +
(networkDetails ? `##### Allowed Domains\n${networkDetails}\n` : '') +
'';
@@ -2446,7 +2446,7 @@ jobs:
This workflow validates that the Codex engine works correctly with AWF (Application-level Firewall) network sandboxing enabled.
- 1. **OpenAI Domain Access**: The workflow should successfully connect to OpenAI APIs (api.openai.com, openai.com) which are in the default allowed domains
+ 1. **OpenAI Domain Access**: Test that direct curl access to OpenAI APIs (api.openai.com, openai.com) is BLOCKED by the firewall - the Codex CLI itself can access OpenAI (it adds these domains automatically), but raw curl commands should fail since OpenAI is not in the `defaults` or `github` network ecosystems
2. **GitHub MCP Testing**: Review the last 2 merged pull requests in __GH_AW_GITHUB_REPOSITORY__ to verify GitHub MCP server works through the firewall
3. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-firewall-__GH_AW_GITHUB_RUN_ID__.txt` with content "Firewall smoke test passed for Codex at $(date)"
4. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back)
@@ -5766,9 +5766,168 @@ jobs:
validAllowedRequests += stats.allowed;
validDeniedRequests += stats.denied;
}
- let summary = "### 🔥 Firewall Activity\n\n";
+ let summary = "";
+ summary += "\n";
+ summary += `sandbox agent: ${totalRequests} request${totalRequests !== 1 ? "s" : ""} | `;
+ summary += `${validAllowedRequests} allowed | `;
+ summary += `${validDeniedRequests} blocked | `;
+ summary += `${uniqueDomainCount} unique domain${uniqueDomainCount !== 1 ? "s" : ""}
\n\n`;
+ if (uniqueDomainCount > 0) {
+ summary += "| Domain | Allowed | Denied |\n";
+ summary += "|--------|---------|--------|\n";
+ for (const domain of validDomains) {
+ const stats = requestsByDomain.get(domain);
+ summary += `| ${domain} | ${stats.allowed} | ${stats.denied} |\n`;
+ }
+ } else {
+ summary += "No firewall activity detected.\n";
+ }
+ summary += "\n \n\n";
+ return summary;
+ }
+ const isDirectExecution = typeof module === "undefined" || (typeof require !== "undefined" && typeof require.main !== "undefined" && require.main === module);
+ if (isDirectExecution) {
+ main();
+ }
+ - name: Upload Firewall Logs
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
+ with:
+ name: firewall-logs-smoke-codex-firewall
+ path: /tmp/gh-aw/sandbox/firewall/logs/
+ if-no-files-found: ignore
+ - name: Parse firewall logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ function sanitizeWorkflowName(name) {
+ return name
+ .toLowerCase()
+ .replace(/[:\\/\s]/g, "-")
+ .replace(/[^a-z0-9._-]/g, "-");
+ }
+ function main() {
+ const fs = require("fs");
+ const path = require("path");
+ try {
+ const squidLogsDir = `/tmp/gh-aw/sandbox/firewall/logs/`;
+ if (!fs.existsSync(squidLogsDir)) {
+ core.info(`No firewall logs directory found at: ${squidLogsDir}`);
+ return;
+ }
+ const files = fs.readdirSync(squidLogsDir).filter(file => file.endsWith(".log"));
+ if (files.length === 0) {
+ core.info(`No firewall log files found in: ${squidLogsDir}`);
+ return;
+ }
+ core.info(`Found ${files.length} firewall log file(s)`);
+ let totalRequests = 0;
+ let allowedRequests = 0;
+ let deniedRequests = 0;
+ const allowedDomains = new Set();
+ const deniedDomains = new Set();
+ const requestsByDomain = new Map();
+ for (const file of files) {
+ const filePath = path.join(squidLogsDir, file);
+ core.info(`Parsing firewall log: ${file}`);
+ const content = fs.readFileSync(filePath, "utf8");
+ const lines = content.split("\n").filter(line => line.trim());
+ for (const line of lines) {
+ const entry = parseFirewallLogLine(line);
+ if (!entry) {
+ continue;
+ }
+ totalRequests++;
+ const isAllowed = isRequestAllowed(entry.decision, entry.status);
+ if (isAllowed) {
+ allowedRequests++;
+ allowedDomains.add(entry.domain);
+ } else {
+ deniedRequests++;
+ deniedDomains.add(entry.domain);
+ }
+ if (!requestsByDomain.has(entry.domain)) {
+ requestsByDomain.set(entry.domain, { allowed: 0, denied: 0 });
+ }
+ const domainStats = requestsByDomain.get(entry.domain);
+ if (isAllowed) {
+ domainStats.allowed++;
+ } else {
+ domainStats.denied++;
+ }
+ }
+ }
+ const summary = generateFirewallSummary({
+ totalRequests,
+ allowedRequests,
+ deniedRequests,
+ allowedDomains: Array.from(allowedDomains).sort(),
+ deniedDomains: Array.from(deniedDomains).sort(),
+ requestsByDomain,
+ });
+ core.summary.addRaw(summary).write();
+ core.info("Firewall log summary generated successfully");
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ function parseFirewallLogLine(line) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith("#")) {
+ return null;
+ }
+ const fields = trimmed.match(/(?:[^\s"]+|"[^"]*")+/g);
+ if (!fields || fields.length < 10) {
+ return null;
+ }
+ const timestamp = fields[0];
+ if (!/^\d+(\.\d+)?$/.test(timestamp)) {
+ return null;
+ }
+ return {
+ timestamp,
+ clientIpPort: fields[1],
+ domain: fields[2],
+ destIpPort: fields[3],
+ proto: fields[4],
+ method: fields[5],
+ status: fields[6],
+ decision: fields[7],
+ url: fields[8],
+ userAgent: fields[9]?.replace(/^"|"$/g, "") || "-",
+ };
+ }
+ function isRequestAllowed(decision, status) {
+ const statusCode = parseInt(status, 10);
+ if (statusCode === 200 || statusCode === 206 || statusCode === 304) {
+ return true;
+ }
+ if (decision.includes("TCP_TUNNEL") || decision.includes("TCP_HIT") || decision.includes("TCP_MISS")) {
+ return true;
+ }
+ if (decision.includes("NONE_NONE") || decision.includes("TCP_DENIED") || statusCode === 403 || statusCode === 407) {
+ return false;
+ }
+ return false;
+ }
+ function generateFirewallSummary(analysis) {
+ const { totalRequests, requestsByDomain } = analysis;
+ const validDomains = Array.from(requestsByDomain.keys())
+ .filter(domain => domain !== "-")
+ .sort();
+ const uniqueDomainCount = validDomains.length;
+ let validAllowedRequests = 0;
+ let validDeniedRequests = 0;
+ for (const domain of validDomains) {
+ const stats = requestsByDomain.get(domain);
+ validAllowedRequests += stats.allowed;
+ validDeniedRequests += stats.denied;
+ }
+ let summary = "";
summary += "\n";
- summary += `📊 ${totalRequests} request${totalRequests !== 1 ? "s" : ""} | `;
+ summary += `sandbox agent: ${totalRequests} request${totalRequests !== 1 ? "s" : ""} | `;
summary += `${validAllowedRequests} allowed | `;
summary += `${validDeniedRequests} blocked | `;
summary += `${uniqueDomainCount} unique domain${uniqueDomainCount !== 1 ? "s" : ""}
\n\n`;
@@ -6716,7 +6875,7 @@ jobs:
node-version: '24'
package-manager-cache: false
- name: Install Codex
- run: npm install -g @openai/codex@0.73.0
+ run: npm install -g @openai/codex@0.75.0
- name: Run Codex
run: |
set -o pipefail
diff --git a/.github/workflows/smoke-codex-firewall.md b/.github/workflows/smoke-codex-firewall.md
index 9594a403a32..cca5656da50 100644
--- a/.github/workflows/smoke-codex-firewall.md
+++ b/.github/workflows/smoke-codex-firewall.md
@@ -47,7 +47,7 @@ tools:
This workflow validates that the Codex engine works correctly with AWF (Application-level Firewall) network sandboxing enabled.
-1. **OpenAI Domain Access**: The workflow should successfully connect to OpenAI APIs (api.openai.com, openai.com) which are in the default allowed domains
+1. **OpenAI Domain Access**: Test that direct curl access to OpenAI APIs (api.openai.com, openai.com) is BLOCKED by the firewall - the Codex CLI itself can access OpenAI (it adds these domains automatically), but raw curl commands should fail since OpenAI is not in the `defaults` or `github` network ecosystems
2. **GitHub MCP Testing**: Review the last 2 merged pull requests in ${{ github.repository }} to verify GitHub MCP server works through the firewall
3. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-firewall-${{ github.run_id }}.txt` with content "Firewall smoke test passed for Codex at $(date)"
4. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back)