diff --git a/.github/workflows/security-guard.lock.yml b/.github/workflows/security-guard.lock.yml index c1d20aae..7a80ba95 100644 --- a/.github/workflows/security-guard.lock.yml +++ b/.github/workflows/security-guard.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"bed0fd101ce3aa5a218b0fe507dd4a06c0295829c81331eb98c396576be6239c","compiler_version":"v0.68.7","strict":true,"agent_id":"claude"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"4263b1635713ab2fc3e7ee65d2ea7dab800f4cca2e3e590afc537441647b180e","compiler_version":"v0.68.7","strict":true,"agent_id":"claude"} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"53b83947a5a98c8d113130e565377fae1a50d02f","version":"v6.3.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"f52802884d655622f0a2dfd6d6a2250983c95523","version":"v0.68.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.23"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.23"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.22"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0","digest":"sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28","pinned_image":"ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -309,7 +309,12 @@ jobs: retention-days: 1 agent: - needs: activation + needs: + - activation + - check_security_relevance + if: > + (needs.check_security_relevance.outputs.security_files_changed != '0') && (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.id == github.repository_id) runs-on: ubuntu-latest permissions: contents: read @@ -371,7 +376,7 @@ jobs: id: security-relevance if: github.event.pull_request.number name: Check security relevance - run: "SECURITY_RE=\"host-iptables|setup-iptables|squid-config|docker-manager|seccomp-profile|domain-patterns|entrypoint\\.sh|Dockerfile|containers/\"\nCOUNT=$(gh api \"repos/${GH_REPO}/pulls/${PR_NUMBER}/files\" \\\n --paginate --jq '.[].filename' \\\n | grep -cE \"$SECURITY_RE\" || true)\necho \"security_files_changed=$COUNT\" >> \"$GITHUB_OUTPUT\"\n" + run: "SECURITY_RE=\"host-iptables|setup-iptables|squid-config|docker-manager|seccomp-profile|domain-patterns|entrypoint\\.sh|Dockerfile|(^|/)containers/\"\nCOUNT=$(gh api \"repos/${GH_REPO}/pulls/${PR_NUMBER}/files\" \\\n --paginate --jq '.[].filename' \\\n | grep -cE \"$SECURITY_RE\" || true)\necho \"security_files_changed=$COUNT\" >> \"$GITHUB_OUTPUT\"\n" - name: Configure Git credentials env: @@ -758,7 +763,7 @@ jobs: (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --tty --env-all --exclude-env ANTHROPIC_API_KEY --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,anthropic.com,api.anthropic.com,api.github.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,ghcr.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,pypi.org,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,sentry.io,statsig.anthropic.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --enable-host-access --build-local --enable-api-proxy --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && claude --print --no-chrome --max-turns 10 --mcp-config "${{ runner.temp }}/gh-aw/mcp-config/mcp-servers.json" --allowed-tools Bash,BashOutput,Edit,ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,NotebookEdit,NotebookRead,Read,Task,TodoWrite,Write,mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users --debug-file /tmp/gh-aw/agent-stdio.log --verbose --permission-mode bypassPermissions --output-format stream-json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_CLAUDE:+ --model "$GH_AW_MODEL_AGENT_CLAUDE"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && claude --print --no-chrome --max-turns 6 --mcp-config "${{ runner.temp }}/gh-aw/mcp-config/mcp-servers.json" --allowed-tools Bash,BashOutput,Edit,ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,NotebookEdit,NotebookRead,Read,Task,TodoWrite,Write,mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users --debug-file /tmp/gh-aw/agent-stdio.log --verbose --permission-mode bypassPermissions --output-format stream-json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_CLAUDE:+ --model "$GH_AW_MODEL_AGENT_CLAUDE"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} BASH_DEFAULT_TIMEOUT_MS: 60000 @@ -766,7 +771,7 @@ jobs: DISABLE_BUG_COMMAND: 1 DISABLE_ERROR_REPORTING: 1 DISABLE_TELEMETRY: 1 - GH_AW_MAX_TURNS: 10 + GH_AW_MAX_TURNS: 6 GH_AW_MCP_CONFIG: ${{ runner.temp }}/gh-aw/mcp-config/mcp-servers.json GH_AW_MODEL_AGENT_CLAUDE: ${{ vars.GH_AW_MODEL_AGENT_CLAUDE || '' }} GH_AW_PHASE: agent @@ -920,10 +925,37 @@ jobs: /tmp/gh-aw/sandbox/firewall/audit/ if-no-files-found: ignore + check_security_relevance: + needs: activation + runs-on: ubuntu-latest + permissions: + pull-requests: read + + outputs: + security_files_changed: ${{ steps.check.outputs.count }} + steps: + - name: Check security relevance + id: check + run: | + if [ -z "${PR_NUMBER}" ]; then + echo "count=1" >> "$GITHUB_OUTPUT" + exit 0 + fi + SECURITY_RE="host-iptables|setup-iptables|squid-config|docker-manager|seccomp-profile|domain-patterns|entrypoint\.sh|Dockerfile|(^|/)containers/" + COUNT=$(gh api "repos/${GH_REPO}/pulls/${PR_NUMBER}/files" \ + --paginate --jq '.[].filename' \ + | grep -cE "$SECURITY_RE" || true) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + conclusion: needs: - activation - agent + - check_security_relevance - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || diff --git a/.github/workflows/security-guard.md b/.github/workflows/security-guard.md index 3edeee68..a1d36321 100644 --- a/.github/workflows/security-guard.md +++ b/.github/workflows/security-guard.md @@ -11,7 +11,7 @@ permissions: issues: read engine: id: claude - max-turns: 10 + max-turns: 6 features: cli-proxy: true tools: @@ -20,6 +20,31 @@ tools: network: allowed: - github +if: needs.check_security_relevance.outputs.security_files_changed != '0' +jobs: + check_security_relevance: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + security_files_changed: ${{ steps.check.outputs.count }} + steps: + - name: Check security relevance + id: check + run: | + if [ -z "${PR_NUMBER}" ]; then + echo "count=1" >> "$GITHUB_OUTPUT" + exit 0 + fi + SECURITY_RE="host-iptables|setup-iptables|squid-config|docker-manager|seccomp-profile|domain-patterns|entrypoint\.sh|Dockerfile|(^|/)containers/" + COUNT=$(gh api "repos/${GH_REPO}/pulls/${PR_NUMBER}/files" \ + --paginate --jq '.[].filename' \ + | grep -cE "$SECURITY_RE" || true) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + GH_REPO: ${{ github.repository }} safe-outputs: threat-detection: enabled: false @@ -57,7 +82,7 @@ steps: id: security-relevance if: github.event.pull_request.number run: | - SECURITY_RE="host-iptables|setup-iptables|squid-config|docker-manager|seccomp-profile|domain-patterns|entrypoint\.sh|Dockerfile|containers/" + SECURITY_RE="host-iptables|setup-iptables|squid-config|docker-manager|seccomp-profile|domain-patterns|entrypoint\.sh|Dockerfile|(^|/)containers/" COUNT=$(gh api "repos/${GH_REPO}/pulls/${PR_NUMBER}/files" \ --paginate --jq '.[].filename' \ | grep -cE "$SECURITY_RE" || true) @@ -66,6 +91,7 @@ steps: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} GH_REPO: ${{ github.repository }} + --- # Security Guard @@ -74,7 +100,7 @@ steps: **Security-critical files changed in this PR:** ${{ steps.security-relevance.outputs.security_files_changed }} -> If this value is `0`, no security-critical files were modified. Use `noop` immediately without further analysis — this PR does not require a security review. +> If this value is `0`, the workflow skips the agent job. ## Repository Context @@ -119,40 +145,7 @@ Analyze PR #${{ github.event.pull_request.number }} in repository ${{ github.rep ## Security Checks -Look for these types of security-weakening changes: - -### iptables and Network Filtering -- Changes that add new ACCEPT rules without proper justification -- Removal or weakening of DROP/REJECT rules -- Changes to the firewall chain structure (FW_WRAPPER, DOCKER-USER) -- DNS exfiltration prevention bypasses (allowing arbitrary DNS servers) -- IPv6 filtering gaps that could allow bypasses - -### Squid Proxy Configuration -- Changes to ACL rule ordering that could allow blocked traffic -- Removal of domain blocking functionality -- Addition of overly permissive domain patterns (e.g., `*.*`) -- Changes that allow non-standard ports (only 80/443 should be allowed) -- Timeout changes that could enable connection-based attacks - -### Container Security -- Removal or weakening of capability dropping (cap_drop) -- Addition of dangerous capabilities (SYS_ADMIN, NET_RAW readdition) -- Changes to seccomp profile that allow dangerous syscalls -- Removal of resource limits -- Changes that run as root instead of unprivileged user - -### Domain Pattern Security -- Removal of wildcard pattern validation -- Allowing overly broad patterns like `*` or `*.*` -- Changes to protocol handling that could bypass restrictions - -### General Security -- Hardcoded credentials or secrets -- Removal of input validation -- Introduction of command injection vulnerabilities -- Changes that disable security features via environment variables -- Dependency updates that introduce known vulnerabilities +Check for these security-weakening changes: new/expanded ACCEPT rules, weakened DROP/REJECT, firewall chain rewiring, DNS or IPv6 bypasses, Squid ACL/order regressions, non-80/443 egress allowances, wildcard/domain validation bypasses, capability additions (`SYS_ADMIN`, `NET_RAW`), seccomp relaxations, removal of resource/user hardening, input validation removal, command injection risk, hardcoded secrets, security-disabling env var changes, or risky dependency updates. ## Output Format diff --git a/CLAUDE.md b/CLAUDE.md index 9e4c7a88..f71339e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -77,86 +77,6 @@ This downloads artifacts to `./artifacts-run-$RUN_ID` for local examination. Req **Example:** The "Pool overlaps" Docker network error was reproduced locally, traced to orphaned networks from `timeout`-killed processes, fixed by adding pre-test cleanup in scripts, then verified before updating workflows. -## Development Commands - -### Build and Testing -```bash -# Build TypeScript to dist/ -npm run build - -# Watch mode (rebuilds on changes) -npm run dev - -# Run tests -npm test - -# Run tests in watch mode -npm test:watch - -# Lint TypeScript files -npm run lint - -# Clean build artifacts -npm run clean -``` - -### Workflow Compilation - -**IMPORTANT:** When modifying smoke or build-test workflow `.md` files, you MUST run the post-processing script after compiling. The compiled `.lock.yml` files need post-processing to replace GHCR image references with local builds, remove sparse-checkout, and install awf from source. - -```bash -# 1. Compile the workflow(s) -gh-aw compile .github/workflows/smoke-claude.md - -# 2. Post-process ALL lock files (always run this after any compile) -npx tsx scripts/ci/postprocess-smoke-workflows.ts -``` - -The post-processing script (`scripts/ci/postprocess-smoke-workflows.ts`) applies these transformations to lock files: -- Replaces the "Install awf binary" step with local `npm ci && npm run build` steps -- Removes sparse-checkout blocks (full repo needed for npm build) -- Removes shallow depth settings -- Replaces `--image-tag --skip-pull` with `--build-local` - -### Local Installation - -**For regular use:** -```bash -# Link locally for testing -npm link - -# Use the CLI -awf --allow-domains github.com 'curl https://api.github.com' -``` - -**For sudo usage (required for iptables manipulation):** - -Since `npm link` creates symlinks in the user's npm directory which isn't in root's PATH, you need to create a wrapper script in `/usr/local/bin/`: - -```bash -# Build the project -npm run build - -# Create sudo wrapper script -# Update the paths below to match your system: -# - NODE_PATH: Find with `which node` (example shows nvm installation) -# - PROJECT_PATH: Your cloned repository location -sudo tee /usr/local/bin/awf > /dev/null <<'EOF' -#!/bin/bash -NODE_PATH="$HOME/.nvm/versions/node/v22.13.0/bin/node" -PROJECT_PATH="$HOME/developer/gh-aw-firewall" - -exec "$NODE_PATH" "$PROJECT_PATH/dist/cli.js" "$@" -EOF - -sudo chmod +x /usr/local/bin/awf - -# Verify it works -sudo awf --help -``` - -**Note:** After each `npm run build`, the wrapper automatically uses the latest compiled code. Update the paths in the wrapper script to match your node installation and project directory. - ## Container Image Strategy The firewall uses three Docker containers: Squid proxy, agent execution environment, and an optional API proxy sidecar. By default, the CLI pulls pre-built images from GitHub Container Registry (GHCR) for faster startup and easier distribution. @@ -302,44 +222,6 @@ The wrapper propagates the exit code from the agent container: 3. Wrapper inspects container: `docker inspect --format={{.State.ExitCode}}` 4. Wrapper exits with same code -## Cleanup Lifecycle - -The system uses a defense-in-depth cleanup strategy across four stages to prevent Docker resource leaks: - -### 1. Pre-Test Cleanup (CI/CD Scripts) -**Location:** `scripts/ci/test-agent-*.sh` (start of each script) -**What:** Runs `cleanup.sh` to remove orphaned resources from previous failed runs -**Why:** Prevents Docker network subnet pool exhaustion and container name conflicts -**Critical:** Without this, `timeout` commands that kill the wrapper mid-cleanup leave networks/containers behind - -### 2. Normal Exit Cleanup (Built-in) -**Location:** `src/cli.ts:117-118` (`performCleanup()`) -**What:** -- `stopContainers()` → `docker compose down -v` (stops containers, removes volumes) -- `cleanup()` → Deletes workDir (`/tmp/awf-`) -**Trigger:** Successful command completion - -### 3. Signal/Error Cleanup (Built-in) -**Location:** `src/cli.ts:95-103, 122-126` (SIGINT/SIGTERM handlers, catch blocks) -**What:** Same as normal exit cleanup -**Trigger:** User interruption (Ctrl+C), timeout signals, or errors -**Limitation:** Cannot catch SIGKILL (9) from `timeout` after grace period - -### 4. CI/CD Always Cleanup -**Location:** `.github/workflows/test-agent-*.yml` (`if: always()`) -**What:** Runs `cleanup.sh` regardless of job status -**Why:** Safety net for SIGKILL, job cancellation, and unexpected failures - -### Cleanup Script (`scripts/ci/cleanup.sh`) -Removes all awf resources: -- Containers by name (`awf-squid`, `awf-agent`) -- All docker-compose services from work directories -- Unused containers (`docker container prune -f`) -- Unused networks (`docker network prune -f`) - **critical for subnet pool management** -- Temporary directories (`/tmp/awf-*`) - -**Note:** Test scripts use `timeout 60s` which can kill the wrapper before Stage 2/3 cleanup completes. Stage 1 (pre-test) and Stage 4 (always) prevent accumulation across test runs. - ## Configuration Files All temporary files are created in `workDir` (default: `/tmp/awf-`): @@ -350,102 +232,6 @@ All temporary files are created in `workDir` (default: `/tmp/awf-`): Use `--keep-containers` to preserve containers and files after execution for debugging. -## Log Streaming and Persistence - -### Real-Time Log Streaming - -The wrapper streams container logs in real-time using `docker logs -f`, allowing you to see output as commands execute rather than waiting until completion. This is implemented in `src/docker-manager.ts:runAgentCommand()` which runs `docker logs -f` concurrently with `docker wait`. - -**Note:** The container is configured with `tty: false` (line 202 in `src/docker-manager.ts`) to prevent ANSI escape sequences from appearing in log output. This provides cleaner, more readable streaming logs. - -### Agent Logs Preservation - -Agent logs (including GitHub Copilot CLI logs) are automatically preserved for debugging: - -**Directory Structure:** -- Container writes logs to: `~/.copilot/logs/` (GitHub Copilot CLI's default location) -- Volume mount maps to: `${workDir}/agent-logs/` -- After cleanup: Logs moved to `/tmp/awf-agent-logs-` (if they exist) - -**Automatic Preservation:** -- If agent creates logs, they're automatically moved to `/tmp/awf-agent-logs-/` before workDir cleanup -- Empty log directories are not preserved (avoids cluttering /tmp) -- You'll see: `[INFO] Agent logs preserved at: /tmp/awf-agent-logs-` when logs exist - -**With `--keep-containers`:** -- Logs remain at: `${workDir}/agent-logs/` -- All config files and containers are preserved -- You'll see: `[INFO] Agent logs available at: /tmp/awf-/agent-logs/` - -**Usage Examples:** -```bash -# Logs automatically preserved (if created) -awf --allow-domains github.com \ - "npx @github/copilot@0.0.347 -p 'your prompt' --log-level debug --allow-all-tools" -# Output: [INFO] Agent logs preserved at: /tmp/awf-agent-logs-1761073250147 - -# Increase log verbosity for debugging -awf --allow-domains github.com \ - "npx @github/copilot@0.0.347 -p 'your prompt' --log-level all --allow-all-tools" - -# Keep everything for detailed inspection -awf --allow-domains github.com --keep-containers \ - "npx @github/copilot@0.0.347 -p 'your prompt' --log-level debug" -``` - -**Implementation Details:** -- Volume mount added in `src/docker-manager.ts:172` -- Log directory creation in `src/docker-manager.ts:247-252` -- Preservation logic in `src/docker-manager.ts:540-550` (cleanup function) - -### Squid Logs Preservation - -Squid proxy logs are automatically preserved for debugging network traffic: - -**Directory Structure:** -- Container writes logs to: `/var/log/squid/` (Squid's default location) -- Volume mount maps to: `${workDir}/squid-logs/` -- After cleanup: Logs moved to `/tmp/squid-logs-` (if they exist) - -**Automatic Preservation:** -- If Squid creates logs, they're automatically moved to `/tmp/squid-logs-/` before workDir cleanup -- Empty log directories are not preserved (avoids cluttering /tmp) -- You'll see: `[INFO] Squid logs preserved at: /tmp/squid-logs-` when logs exist - -**With `--keep-containers`:** -- Logs remain at: `${workDir}/squid-logs/` -- All config files and containers are preserved -- You'll see: `[INFO] Squid logs available at: /tmp/awf-/squid-logs/` - -**Log Files:** -- `access.log`: All HTTP/HTTPS traffic with custom format showing domains, IPs, and allow/deny decisions -- `cache.log`: Squid internal diagnostic messages - -**Viewing Logs:** -```bash -# Logs are owned by the 'proxy' user (from container), requires sudo on host -sudo cat /tmp/squid-logs-/access.log - -# Example log entries: -# Allowed: TCP_TUNNEL:HIER_DIRECT with status 200 -# Denied: TCP_DENIED:HIER_NONE with status 403 -``` - -**Usage Examples:** -```bash -# Check which domains were blocked -sudo grep "TCP_DENIED" /tmp/squid-logs-/access.log - -# View all traffic -sudo cat /tmp/squid-logs-/access.log -``` - -**Implementation Details:** -- Volume mount in `src/docker-manager.ts:135` -- Log directory creation in `src/docker-manager.ts:254-261` -- Entrypoint script fixes permissions: `containers/squid/entrypoint.sh` -- Preservation logic in `src/docker-manager.ts:552-562` (cleanup function) - ## Key Dependencies - `commander`: CLI argument parsing