Skip to content

feat: persist redacted resolved config as audit artifact#4719

Merged
lpcox merged 3 commits into
mainfrom
persist-resolved-config-artifact
Jun 11, 2026
Merged

feat: persist redacted resolved config as audit artifact#4719
lpcox merged 3 commits into
mainfrom
persist-resolved-config-artifact

Conversation

@lpcox

@lpcox lpcox commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

The full resolved AWF configuration (CLI flags, domain allowlists, network topology, feature flags) is only logged at --log-level debug to stderr. After a run completes, there's no persistent artifact to recover what options AWF was invoked with — you have to parse GitHub Actions step logs and hope debug was enabled.

Solution

Write awf-resolved-config.json to the audit dir (or workDir) at startup, before containers launch. This file contains the complete resolved config with:

  • All CLI options and their resolved values
  • Domain allowlists and blocklists
  • Network configuration (subnet, IPs, DNS)
  • Feature flags (api-proxy, cli-proxy, chroot, etc.)

Security: API keys are fully excluded (same redaction as debug log). The agent command has inline secrets redacted. File is written mode 0600.

Usage

# After a run with --audit-dir:
cat /path/to/audit/awf-resolved-config.json

# After a run without --audit-dir (preserved with --keep-containers):
cat /tmp/awf-<timestamp>/awf-resolved-config.json

Testing

  • 2 new unit tests: writes to audit dir when set, falls back to workDir otherwise
  • All 2476 existing tests pass

Copilot AI review requested due to automatic review settings June 10, 2026 23:28
@lpcox

lpcox commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a persisted, redacted “resolved config” snapshot so AWF runs can be audited after completion without relying on debug logs, by writing a JSON artifact during startup and covering it with unit tests.

Changes:

  • Write awf-resolved-config.json containing the redacted resolved config early in createMainAction().
  • Add unit tests to verify the artifact is written to auditDir when set, and otherwise written to the fallback directory.
Show a summary per file
File Description
src/commands/main-action.ts Writes a redacted resolved-config JSON file at startup.
src/commands/main-action.test.ts Adds tests asserting the resolved-config artifact is written to the expected location.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 4

Comment thread src/commands/main-action.ts Outdated
Comment on lines +121 to +130
// Persist redacted config to audit artifact for post-run diagnostics
try {
const configArtifactDir = config.auditDir || config.workDir;
fs.mkdirSync(configArtifactDir, { recursive: true });
fs.writeFileSync(
path.join(configArtifactDir, 'awf-resolved-config.json'),
JSON.stringify(redactedConfig, null, 2) + '\n',
{ mode: 0o600 },
);
} catch { /* best-effort — never block startup */ }
});
});

describe('resolved config artifact', () => {
Comment thread src/commands/main-action.test.ts Outdated
Comment on lines +383 to +405
const configWithAudit = {
...STUB_CONFIG,
auditDir: '/tmp/awf-audit',
};
mockedValidateOptions.validateOptions.mockReturnValue(
configWithAudit as unknown as import('../types').WrapperConfig
);
const action = createMainAction(getOptionValueSource);
await action(['echo hi'], {});

expect(mkdirSyncSpy).toHaveBeenCalledWith('/tmp/awf-audit', { recursive: true });
expect(writeFileSyncSpy).toHaveBeenCalledWith(
'/tmp/awf-audit/awf-resolved-config.json',
expect.stringContaining('"allowedDomains"'),
{ mode: 0o600 },
);
// Verify secrets are not in the artifact
const written = writeFileSyncSpy.mock.calls.find(
(c) => String(c[0]).includes('awf-resolved-config.json')
);
expect(written).toBeDefined();
expect(String(written![1])).not.toContain('ApiKey');

Comment thread src/commands/main-action.test.ts Outdated
Comment on lines +419 to +423
expect(writeFileSyncSpy).toHaveBeenCalledWith(
'/tmp/awf-test/awf-resolved-config.json',
expect.any(String),
{ mode: 0o600 },
);
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@copilot fix this failing ci check https://github.com/github/gh-aw-firewall/actions/runs/27313022132/job/80687323683?pr=4719

Fixed in the latest commit. The CI failure was caused by two require('fs') calls in the test (forbidden by @typescript-eslint/no-require-imports). I replaced them with a module-level jest.mock('fs', ...) factory pattern (matching the pattern already used in ssl-bump-chown.test.ts), which also fixes the "Cannot redefine property" issue with jest.spyOn on non-configurable fs methods.

Also addressed the other review feedback in the same commit:

  • Fallback dir changed to ${workDir}/audit (consistent with config-writer.ts)
  • File permissions changed to 0o644 + explicit chmodSync (world-readable for non-root post-run steps)
  • Directory permissions set to 0o755
  • catch block now logs at debug level instead of silently swallowing errors
  • afterEach(() => jest.restoreAllMocks()) added
  • Added a new test verifying actual secret values in agentCommand are redacted

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@copilot fix this failing ci check https://github.com/github/gh-aw-firewall/actions/runs/27314107289/job/80690749755?pr=4719

Fixed in the latest commit. The CI was running against the original commit — the previous fix didn't persist across sessions. Re-applied all changes: replaced require('fs') with a module-level jest.mock('fs', ...) factory, corrected audit dir fallback to ${workDir}/audit, permissions to 0o644/0o755, and added the debug catch logging. ESLint now shows 0 errors and all 22 tests pass.

Copilot finished work on behalf of lpcox June 10, 2026 23:59
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK (Direct) Mode — PASS

Test Result
GitHub MCP Connectivity
File Write/Read
BYOK Inference (api-proxy → api.githubcopilot.com)

Mode: Direct BYOK (COPILOT_PROVIDER_API_KEY via api-proxy sidecar)
Status: All tests passed
PR Author: @lpcox

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Smoke Test — PASS

Test Result
GitHub MCP (merged PR: "chore: recompile all workflows with latest gh-aw")
GitHub.com HTTP connectivity (200)
File write/read (smoke-test-copilot-27314277979.txt)

Overall: PASS

PR by @lpcox — no assignees.

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

Copy link
Copy Markdown
Contributor

Merged PRs:

Checks:

  • PR list ✅
  • Playwright ✅
  • File write ✅
  • Discussion query ✅
  • Build ✅

Overall: PASS

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions

Copy link
Copy Markdown
Contributor

🔥 Smoke Test Results — PAT Auth

Test Result
GitHub MCP (list PRs)
GitHub.com HTTP ✅ 200
File write/read

Overall: PASS 🎉

PR: feat: persist redacted resolved config as audit artifact by @lpcox
Auth mode: PAT (COPILOT_GITHUB_TOKEN)

🔑 PAT report filed by Smoke Copilot PAT

@github-actions

Copy link
Copy Markdown
Contributor

@lpcox

PR: feat: persist redacted resolved config as audit artifact

✅ GitHub MCP connectivity
✅ GitHub.com HTTP (200)
✅ File write/read
✅ Direct BYOK inference

Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw)

Overall: PASS

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)

@github-actions

Copy link
Copy Markdown
Contributor

feat: persist redacted resolved config as audit artifact: ✅
GitHub.com Connectivity: ✅
File Write/Read Test: ✅
BYOK Inference Test: ✅
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) authenticated via Microsoft Entra
Overall Status: PASS
CC @lpcox

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.openai.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.openai.com"

See Network Configuration for more information.

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.16.0 v22.22.3
Go go1.22.12 go1.22.12

Result: Not all tests passed. Python and Node.js versions differ between host and chroot environments. Go versions match.

Tested by Smoke Chroot

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx all passed ✅ PASS
Node.js execa all passed ✅ PASS
Node.js p-limit all passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #4719 · 153.5 AIC · ⊞ 33.8K ·

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results

Check Result
Redis PING ❌ timeout
PostgreSQL pg_isready ❌ no response
PostgreSQL SELECT 1 ❌ timeout

Overall: FAILhost.docker.internal is not reachable from this runner environment. Service containers may not be configured or attached.

🔌 Service connectivity validated by Smoke Services

@github-actions

Copy link
Copy Markdown
Contributor

GitHub API: ✅ PASS
GitHub check: ✅ PASS
File verify: ✅ PASS

Total: PASS

💥 [THE END] — Illustrated by Smoke Claude

@lpcox lpcox merged commit e99d0a8 into main Jun 11, 2026
78 of 80 checks passed
@lpcox lpcox deleted the persist-resolved-config-artifact branch June 11, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants