diff --git a/.github/workflows/build-test-rust.lock.yml b/.github/workflows/build-test-rust.lock.yml
index 496ad312..5dc3b74d 100644
--- a/.github/workflows/build-test-rust.lock.yml
+++ b/.github/workflows/build-test-rust.lock.yml
@@ -13,7 +13,7 @@
# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
-# This file was automatically generated by gh-aw (v0.42.17). DO NOT EDIT.
+# This file was automatically generated by gh-aw (v0.43.13). DO NOT EDIT.
#
# To update this file, edit the corresponding .md file and run:
# gh aw compile
@@ -21,7 +21,7 @@
#
# Build Test Rust
#
-# frontmatter-hash: ce653278c404e9224b068baacf3bdd06b6735c2759288f0faf0121c7381f3b53
+# frontmatter-hash: abe6a719756fbac074aec027963912d0b0ac8782c539c40df8bff1803473182b
name: "Build Test Rust"
"on":
@@ -54,11 +54,11 @@ jobs:
comment_repo: ""
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@7a970851c1090295e55a16e549c61ba1ce227f16 # v0.42.17
+ uses: github/gh-aw/actions/setup@v0.43.13
with:
destination: /opt/gh-aw/actions
- name: Check workflow file timestamps
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_WORKFLOW_FILE: "build-test-rust.lock.yml"
with:
@@ -93,12 +93,13 @@ jobs:
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@7a970851c1090295e55a16e549c61ba1ce227f16 # v0.42.17
+ uses: github/gh-aw/actions/setup@v0.43.13
with:
destination: /opt/gh-aw/actions
- name: Checkout .github and .agents folders
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
+ fetch-depth: 1
persist-credentials: false
- name: Create gh-aw temp directory
run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
@@ -117,7 +118,7 @@ jobs:
id: checkout-pr
if: |
github.event.pull_request
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
@@ -127,13 +128,58 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
await main();
+ - name: Generate agentic run info
+ id: generate_aw_info
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const fs = require('fs');
+
+ const awInfo = {
+ engine_id: "copilot",
+ engine_name: "GitHub Copilot CLI",
+ model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
+ version: "",
+ agent_version: "0.0.407",
+ cli_version: "v0.43.13",
+ workflow_name: "Build Test Rust",
+ experimental: false,
+ supports_tools_allowlist: true,
+ supports_http_transport: true,
+ run_id: context.runId,
+ run_number: context.runNumber,
+ run_attempt: process.env.GITHUB_RUN_ATTEMPT,
+ repository: context.repo.owner + '/' + context.repo.repo,
+ ref: context.ref,
+ sha: context.sha,
+ actor: context.actor,
+ event_name: context.eventName,
+ staged: false,
+ allowed_domains: ["defaults","github","rust","crates.io"],
+ firewall_enabled: true,
+ awf_version: "v0.16.0",
+ awmg_version: "",
+ steps: {
+ firewall: "squid"
+ },
+ created_at: new Date().toISOString()
+ };
+
+ // Write to /tmp/gh-aw directory to avoid inclusion in PR
+ const tmpPath = '/tmp/gh-aw/aw_info.json';
+ fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
+ console.log('Generated aw_info.json at:', tmpPath);
+ console.log(JSON.stringify(awInfo, null, 2));
+
+ // Set model as output for reuse in other steps/jobs
+ core.setOutput('model', awInfo.model);
- name: Validate COPILOT_GITHUB_TOKEN secret
id: validate-secret
run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Install GitHub Copilot CLI
- run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.407
- name: Install awf dependencies
run: npm ci
- name: Build awf
@@ -170,16 +216,16 @@ jobs:
const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
await determineAutomaticLockdown(github, context, core);
- name: Download container images
- run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.113 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.16.0 ghcr.io/github/gh-aw-firewall/squid:0.16.0 ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine
- name: Write Safe Outputs Config
run: |
mkdir -p /opt/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > /opt/gh-aw/safeoutputs/config.json << 'EOF'
+ cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
{"add_comment":{"max":1},"add_labels":{"allowed":["build-test-rust"],"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
- EOF
- cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF'
+ GH_AW_SAFE_OUTPUTS_CONFIG_EOF
+ cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF'
[
{
"description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 1 comment(s) can be added.",
@@ -293,8 +339,8 @@ jobs:
"name": "missing_data"
}
]
- EOF
- cat > /opt/gh-aw/safeoutputs/validation.json << 'EOF'
+ GH_AW_SAFE_OUTPUTS_TOOLS_EOF
+ cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
{
"add_comment": {
"defaultMax": 1,
@@ -358,18 +404,17 @@ jobs:
}
}
}
- EOF
+ GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
- name: Generate Safe Outputs MCP Server Config
id: safe-outputs-config
run: |
# Generate a secure random API key (360 bits of entropy, 40+ chars)
- API_KEY=""
+ # Mask immediately to prevent timing vulnerabilities
API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- PORT=3001
-
- # Register API key as secret to mask it from logs
echo "::add-mask::${API_KEY}"
+ PORT=3001
+
# Set outputs for next steps
{
echo "safe_outputs_api_key=${API_KEY}"
@@ -413,20 +458,18 @@ jobs:
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="80"
export MCP_GATEWAY_DOMAIN="host.docker.internal"
- MCP_GATEWAY_API_KEY=""
MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
export MCP_GATEWAY_API_KEY
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export DEBUG="*"
- # Register API key as secret to mask it from logs
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
export GH_AW_ENGINE="copilot"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.0.113'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.4'
mkdir -p /home/runner/.copilot
- cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -454,54 +497,9 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- MCPCONFIG_EOF
- - name: Generate agentic run info
- id: generate_aw_info
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
- with:
- script: |
- const fs = require('fs');
-
- const awInfo = {
- engine_id: "copilot",
- engine_name: "GitHub Copilot CLI",
- model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
- version: "",
- agent_version: "0.0.405",
- cli_version: "v0.42.17",
- workflow_name: "Build Test Rust",
- experimental: false,
- supports_tools_allowlist: true,
- supports_http_transport: true,
- run_id: context.runId,
- run_number: context.runNumber,
- run_attempt: process.env.GITHUB_RUN_ATTEMPT,
- repository: context.repo.owner + '/' + context.repo.repo,
- ref: context.ref,
- sha: context.sha,
- actor: context.actor,
- event_name: context.eventName,
- staged: false,
- allowed_domains: ["defaults","github","rust"],
- firewall_enabled: true,
- awf_version: "v0.13.12",
- awmg_version: "v0.0.113",
- steps: {
- firewall: "squid"
- },
- created_at: new Date().toISOString()
- };
-
- // Write to /tmp/gh-aw directory to avoid inclusion in PR
- const tmpPath = '/tmp/gh-aw/aw_info.json';
- fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
- console.log('Generated aw_info.json at:', tmpPath);
- console.log(JSON.stringify(awInfo, null, 2));
-
- // Set model as output for reuse in other steps/jobs
- core.setOutput('model', awInfo.model);
+ GH_AW_MCP_CONFIG_EOF
- name: Generate workflow overview
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
@@ -520,12 +518,12 @@ jobs:
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
run: |
bash /opt/gh-aw/actions/create_prompt_first.sh
- cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
+ cat << 'GH_AW_PROMPT_EOF' > "$GH_AW_PROMPT"
- PROMPT_EOF
+ GH_AW_PROMPT_EOF
cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
GitHub API Access Instructions
@@ -569,15 +567,15 @@ jobs:
{{/if}}
- PROMPT_EOF
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
- PROMPT_EOF
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
{{#runtime-import .github/workflows/build-test-rust.md}}
- PROMPT_EOF
+ GH_AW_PROMPT_EOF
- name: Substitute placeholders
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
@@ -607,7 +605,7 @@ jobs:
}
});
- name: Interpolate variables and render templates
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
with:
@@ -624,6 +622,8 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ - name: Clean git credentials
+ run: bash /opt/gh-aw/actions/clean_git_credentials.sh
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
@@ -645,6 +645,17 @@ jobs:
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
GITHUB_WORKSPACE: ${{ github.workspace }}
XDG_CONFIG_HOME: /home/runner
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
- name: Copy Copilot session state files to logs
if: always()
continue-on-error: true
@@ -673,7 +684,7 @@ jobs:
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
- name: Redact secrets in logs
if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@@ -695,7 +706,7 @@ jobs:
if-no-files-found: warn
- name: Ingest agent output
id: collect_output
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crates.io,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,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,index.crates.io,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,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,sh.rustup.rs,static.crates.io,static.rust-lang.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
@@ -724,7 +735,7 @@ jobs:
if-no-files-found: ignore
- name: Parse agent logs for step summary
if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
with:
@@ -735,7 +746,7 @@ jobs:
await main();
- name: Parse MCP gateway logs for step summary
if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@@ -786,20 +797,9 @@ jobs:
total_count: ${{ steps.missing_tool.outputs.total_count }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@7a970851c1090295e55a16e549c61ba1ce227f16 # v0.42.17
+ uses: github/gh-aw/actions/setup@v0.43.13
with:
destination: /opt/gh-aw/actions
- - name: Debug job inputs
- env:
- COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
- AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
- AGENT_CONCLUSION: ${{ needs.agent.result }}
- run: |
- echo "Comment ID: $COMMENT_ID"
- echo "Comment Repo: $COMMENT_REPO"
- echo "Agent Output Types: $AGENT_OUTPUT_TYPES"
- echo "Agent Conclusion: $AGENT_CONCLUSION"
- name: Download agent output artifact
continue-on-error: true
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
@@ -813,7 +813,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process No-Op Messages
id: noop
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_NOOP_MAX: 1
@@ -827,7 +827,7 @@ jobs:
await main();
- name: Record Missing Tool
id: missing_tool
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Build Test Rust"
@@ -840,12 +840,13 @@ jobs:
await main();
- name: Handle Agent Failure
id: handle_agent_failure
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Build Test Rust"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_WORKFLOW_ID: "build-test-rust"
GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runFailure\":\"**Build Test Failed** [{workflow_name}]({run_url}) - See logs for details\"}"
@@ -858,7 +859,7 @@ jobs:
await main();
- name: Handle No-Op Message
id: handle_noop_message
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Build Test Rust"
@@ -875,7 +876,7 @@ jobs:
await main();
- name: Update reaction comment with completion status
id: conclusion
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
@@ -903,7 +904,7 @@ jobs:
success: ${{ steps.parse_results.outputs.success }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@7a970851c1090295e55a16e549c61ba1ce227f16 # v0.42.17
+ uses: github/gh-aw/actions/setup@v0.43.13
with:
destination: /opt/gh-aw/actions
- name: Download agent artifacts
@@ -924,7 +925,7 @@ jobs:
run: |
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
- name: Setup threat detection
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
WORKFLOW_NAME: "Build Test Rust"
WORKFLOW_DESCRIPTION: "Build Test Rust"
@@ -945,7 +946,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Install GitHub Copilot CLI
- run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.407
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
@@ -977,7 +978,7 @@ jobs:
XDG_CONFIG_HOME: /home/runner
- name: Parse threat detection results
id: parse_results
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@@ -1016,7 +1017,7 @@ jobs:
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@7a970851c1090295e55a16e549c61ba1ce227f16 # v0.42.17
+ uses: github/gh-aw/actions/setup@v0.43.13
with:
destination: /opt/gh-aw/actions
- name: Download agent output artifact
@@ -1032,7 +1033,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process Safe Outputs
id: process_safe_outputs
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"build-test-rust\"]},\"missing_data\":{},\"missing_tool\":{}}"
diff --git a/.github/workflows/build-test-rust.md b/.github/workflows/build-test-rust.md
index 758ea98b..5a5f8895 100644
--- a/.github/workflows/build-test-rust.md
+++ b/.github/workflows/build-test-rust.md
@@ -19,6 +19,7 @@ network:
- defaults
- github
- rust
+ - crates.io
tools:
bash:
- "*"
diff --git a/.github/workflows/test-chroot.yml b/.github/workflows/test-chroot.yml
index 2f5457f5..b3ea6e4d 100644
--- a/.github/workflows/test-chroot.yml
+++ b/.github/workflows/test-chroot.yml
@@ -157,6 +157,8 @@ jobs:
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: stable
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4
@@ -186,6 +188,12 @@ jobs:
echo "Captured CARGO_HOME: ${CARGO_HOME}"
fi
+ # Rust: RUSTUP_HOME is needed so rustc can find the toolchain
+ if [ -n "$RUSTUP_HOME" ]; then
+ echo "RUSTUP_HOME=${RUSTUP_HOME}" >> $GITHUB_ENV
+ echo "Captured RUSTUP_HOME: ${RUSTUP_HOME}"
+ fi
+
# Java: JAVA_HOME is needed so entrypoint can add $JAVA_HOME/bin to PATH
# The setup-java action sets JAVA_HOME but sudo may not preserve it
if [ -n "$JAVA_HOME" ]; then
@@ -210,11 +218,14 @@ jobs:
echo "GOROOT: $GOROOT"
echo "Ruby: $(ruby --version)"
echo "Gem: $(gem --version)"
+ echo "Bundler: $(bundle --version 2>&1 || echo 'Not installed')"
echo "Rust: $(rustc --version)"
echo "Cargo: $(cargo --version)"
echo "CARGO_HOME: $CARGO_HOME"
+ echo "RUSTUP_HOME: $RUSTUP_HOME"
echo "Java: $(java --version 2>&1 | head -1)"
echo "JAVA_HOME: $JAVA_HOME"
+ echo "Maven: $(mvn --version 2>&1 | head -1 || echo 'Not installed')"
echo "dotnet: $(dotnet --version 2>&1)"
echo "DOTNET_ROOT: $DOTNET_ROOT"
diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile
index 6a74a155..65ba8cc5 100644
--- a/containers/agent/Dockerfile
+++ b/containers/agent/Dockerfile
@@ -4,7 +4,24 @@
# - ghcr.io/catthehacker/ubuntu:runner-22.04: Closer to GitHub Actions runner (~2-5GB)
# - ghcr.io/catthehacker/ubuntu:full-22.04: Near-identical to GitHub Actions runner (~20GB compressed)
# Use --build-arg BASE_IMAGE= to customize
+# NOTE: ARG declared before first FROM is global and available in all FROM statements
ARG BASE_IMAGE=ubuntu:22.04
+
+# Multi-stage build: Use official Rust image to build one-shot-token library
+# SECURITY: Using official rust:1.77-slim image prevents executing unverified
+# scripts from the internet during build time (supply chain attack mitigation)
+# NOTE: Rust 1.77+ required for C string literal syntax (c"...") used in src/lib.rs
+FROM rust:1.77-slim AS rust-builder
+
+# Copy one-shot-token source files
+COPY one-shot-token/Cargo.toml /tmp/one-shot-token/Cargo.toml
+COPY one-shot-token/src/ /tmp/one-shot-token/src/
+
+# Build the one-shot-token library
+WORKDIR /tmp/one-shot-token
+RUN cargo build --release
+
+# Main stage
FROM ${BASE_IMAGE}
# Install required packages and Node.js 22
@@ -66,20 +83,11 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY pid-logger.sh /usr/local/bin/pid-logger.sh
RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh
-# Build one-shot-token LD_PRELOAD library for single-use token access
+# Copy pre-built one-shot-token library from rust-builder stage
# This prevents tokens from being read multiple times (e.g., by malicious code)
-COPY one-shot-token/one-shot-token.c /tmp/one-shot-token.c
-RUN set -eux; \
- BUILD_PKGS="gcc libc6-dev"; \
- apt-get update && \
- ( apt-get install -y --no-install-recommends $BUILD_PKGS || \
- (rm -rf /var/lib/apt/lists/* && apt-get update && \
- apt-get install -y --no-install-recommends $BUILD_PKGS) ) && \
- gcc -shared -fPIC -O2 -Wall -o /usr/local/lib/one-shot-token.so /tmp/one-shot-token.c -ldl -lpthread && \
- rm /tmp/one-shot-token.c && \
- apt-get remove -y $BUILD_PKGS && \
- apt-get autoremove -y && \
- rm -rf /var/lib/apt/lists/*
+# SECURITY: Using multi-stage build with official Rust image avoids executing
+# unverified scripts from the internet during build time
+COPY --from=rust-builder /tmp/one-shot-token/target/release/libone_shot_token.so /usr/local/lib/one-shot-token.so
# Install Docker stub script that shows helpful error message
# Docker-in-Docker support was removed in v0.9.1
diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh
index 9a3ab69f..eced7d04 100644
--- a/containers/agent/entrypoint.sh
+++ b/containers/agent/entrypoint.sh
@@ -292,6 +292,16 @@ AWFEOF
echo "[entrypoint] Adding CARGO_HOME/bin to PATH: ${AWF_CARGO_HOME}/bin"
echo "export PATH=\"${AWF_CARGO_HOME}/bin:\$PATH\"" >> "/host${SCRIPT_FILE}"
echo "export CARGO_HOME=\"${AWF_CARGO_HOME}\"" >> "/host${SCRIPT_FILE}"
+ # Also set RUSTUP_HOME if provided (needed for rustc to find toolchain)
+ if [ -n "${AWF_RUSTUP_HOME}" ]; then
+ echo "[entrypoint] Setting RUSTUP_HOME: ${AWF_RUSTUP_HOME}"
+ echo "export RUSTUP_HOME=\"${AWF_RUSTUP_HOME}\"" >> "/host${SCRIPT_FILE}"
+ fi
+ else
+ # Fallback: detect Cargo from default location if CARGO_HOME not provided
+ # This ensures Rust binaries work even when CARGO_HOME env var is not set
+ echo "# Add Cargo bin for Rust if it exists (fallback when CARGO_HOME not provided)" >> "/host${SCRIPT_FILE}"
+ echo "[ -d \"\$HOME/.cargo/bin\" ] && export PATH=\"\$HOME/.cargo/bin:\$PATH\"" >> "/host${SCRIPT_FILE}"
fi
# Add JAVA_HOME/bin to PATH if provided (for Java on GitHub Actions)
# Also set LD_LIBRARY_PATH to include Java's lib directory for libjli.so
diff --git a/containers/agent/one-shot-token/.gitignore b/containers/agent/one-shot-token/.gitignore
index 140f8cf8..f3dee79b 100644
--- a/containers/agent/one-shot-token/.gitignore
+++ b/containers/agent/one-shot-token/.gitignore
@@ -1 +1,9 @@
+# Build output
*.so
+
+# Rust build artifacts
+target/
+Cargo.lock
+
+# C build artifacts (legacy)
+*.o
diff --git a/containers/agent/one-shot-token/Cargo.toml b/containers/agent/one-shot-token/Cargo.toml
new file mode 100644
index 00000000..9d8093bc
--- /dev/null
+++ b/containers/agent/one-shot-token/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "one-shot-token"
+version = "0.1.0"
+edition = "2021"
+description = "LD_PRELOAD library for one-shot access to sensitive environment variables"
+license = "MIT"
+
+[lib]
+name = "one_shot_token"
+crate-type = ["cdylib"]
+
+[dependencies]
+libc = "0.2"
+once_cell = "1.19"
+
+[profile.release]
+opt-level = 2
+lto = true
+strip = true
diff --git a/containers/agent/one-shot-token/README.md b/containers/agent/one-shot-token/README.md
index db2d21c0..eb9cb642 100644
--- a/containers/agent/one-shot-token/README.md
+++ b/containers/agent/one-shot-token/README.md
@@ -158,22 +158,22 @@ In chroot mode, the library must be accessible from within the chroot (host file
### In Docker (automatic)
-The Dockerfile compiles the library during image build:
+The Dockerfile compiles the Rust library during image build:
```dockerfile
-RUN gcc -shared -fPIC -O2 -Wall \
- -o /usr/local/lib/one-shot-token.so \
- /tmp/one-shot-token.c \
- -ldl -lpthread
+RUN cargo build --release && \
+ cp target/release/libone_shot_token.so /usr/local/lib/one-shot-token.so
```
### Locally (for testing)
+Requires Rust toolchain (install via [rustup](https://rustup.rs/)):
+
```bash
./build.sh
```
-This produces `one-shot-token.so` in the current directory.
+This builds `target/release/libone_shot_token.so` and creates a symlink `one-shot-token.so` for backwards compatibility.
## Testing
@@ -274,6 +274,20 @@ Note: The `AWF_ONE_SHOT_TOKENS` variable must be exported before running `awf` s
- **In-process getenv() calls**: Since values are cached, any code in the same process can still call `getenv()` and get the cached token
- **Static linking**: Programs statically linked with libc bypass LD_PRELOAD
- **Direct syscalls**: Code that reads `/proc/self/environ` directly (without getenv) bypasses this protection
+- **Task-level /proc exposure**: `/proc/PID/task/TID/environ` may still expose tokens even after `unsetenv()`. The library checks and logs warnings about this exposure.
+
+### Environment Verification
+
+After calling `unsetenv()` to clear tokens, the library automatically verifies whether the token was successfully removed by directly checking the process's environment pointer. This works correctly in both regular and chroot modes.
+
+**Log messages:**
+- `INFO: Token cleared from process environment` - Token successfully cleared (✓ secure)
+- `WARNING: Token still exposed in process environment` - Token still visible (⚠ security concern)
+- `INFO: Token cleared (environ is null)` - Environment pointer is null
+
+This verification runs automatically after `unsetenv()` on first access to each sensitive token and helps identify potential security issues with environment exposure.
+
+**Note on chroot mode:** The verification uses the process's `environ` pointer directly rather than reading from `/proc/self/environ`. This is necessary because in chroot mode, `/proc` may be bind-mounted from the host and show stale environment data.
### Defense in Depth
@@ -285,12 +299,13 @@ This library is one layer in AWF's security model:
## Limitations
-- **x86_64 Linux only**: The library is compiled for x86_64 Ubuntu
+- **Linux only**: The library is compiled for Linux (x86_64 and potentially other architectures via Rust cross-compilation)
- **glibc programs only**: Programs using musl libc or statically linked programs are not affected
- **Single process**: Child processes inherit the LD_PRELOAD but have their own token state and cache (each starts fresh)
## Files
-- `one-shot-token.c` - Library source code
+- `src/lib.rs` - Library source code (Rust)
+- `Cargo.toml` - Rust package configuration
- `build.sh` - Local build script
- `README.md` - This documentation
diff --git a/containers/agent/one-shot-token/build.sh b/containers/agent/one-shot-token/build.sh
index 79227f8f..6996725c 100644
--- a/containers/agent/one-shot-token/build.sh
+++ b/containers/agent/one-shot-token/build.sh
@@ -1,34 +1,39 @@
#!/bin/bash
# Build the one-shot-token LD_PRELOAD library
-# This script compiles the shared library for x86_64 Ubuntu
+# This script compiles the Rust shared library
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-SOURCE_FILE="${SCRIPT_DIR}/one-shot-token.c"
-OUTPUT_FILE="${SCRIPT_DIR}/one-shot-token.so"
-
-echo "[build] Compiling one-shot-token.so..."
-
-# Compile as a shared library with position-independent code
-# -shared: create a shared library
-# -fPIC: position-independent code (required for shared libs)
-# -ldl: link with libdl for dlsym
-# -lpthread: link with pthread for mutex
-# -O2: optimize for performance
-# -Wall -Wextra: enable warnings
-gcc -shared -fPIC \
- -O2 -Wall -Wextra \
- -o "${OUTPUT_FILE}" \
- "${SOURCE_FILE}" \
- -ldl -lpthread
-
-echo "[build] Successfully built: ${OUTPUT_FILE}"
+LINK_FILE="${SCRIPT_DIR}/one-shot-token.so"
+
+echo "[build] Building one-shot-token with Cargo..."
+
+cd "${SCRIPT_DIR}"
+
+# Build the release version
+cargo build --release
+
+# Determine the output file based on platform
+if [[ "$(uname)" == "Darwin" ]]; then
+ OUTPUT_FILE="${SCRIPT_DIR}/target/release/libone_shot_token.dylib"
+ echo "[build] Successfully built: ${OUTPUT_FILE} (macOS)"
+else
+ OUTPUT_FILE="${SCRIPT_DIR}/target/release/libone_shot_token.so"
+ echo "[build] Successfully built: ${OUTPUT_FILE}"
+
+ # Create symlink for backwards compatibility (Linux only)
+ if [[ -L "${LINK_FILE}" ]]; then
+ rm "${LINK_FILE}"
+ fi
+ ln -sf "target/release/libone_shot_token.so" "${LINK_FILE}"
+ echo "[build] Created symlink: ${LINK_FILE} -> target/release/libone_shot_token.so"
+fi
# Verify it's a valid shared library
-if file "${OUTPUT_FILE}" | grep -q "shared object"; then
- echo "[build] Verified: valid shared object"
+if file "${OUTPUT_FILE}" | grep -qE "shared object|dynamically linked"; then
+ echo "[build] Verified: valid shared library"
else
- echo "[build] ERROR: Output is not a valid shared object"
+ echo "[build] ERROR: Output is not a valid shared library"
exit 1
fi
diff --git a/containers/agent/one-shot-token/src/lib.rs b/containers/agent/one-shot-token/src/lib.rs
new file mode 100644
index 00000000..1472c5fb
--- /dev/null
+++ b/containers/agent/one-shot-token/src/lib.rs
@@ -0,0 +1,403 @@
+//! One-Shot Token LD_PRELOAD Library
+//!
+//! Intercepts getenv() calls for sensitive token environment variables.
+//! On first access, caches the value in memory and unsets from environment.
+//! Subsequent calls return the cached value, so the process can read tokens
+//! multiple times while /proc/self/environ no longer exposes them.
+//!
+//! Configuration:
+//! AWF_ONE_SHOT_TOKENS - Comma-separated list of token names to protect
+//! If not set, uses built-in defaults
+//!
+//! Compile: cargo build --release
+//! Usage: LD_PRELOAD=/path/to/libone_shot_token.so ./your-program
+
+use libc::{c_char, c_void};
+use once_cell::sync::Lazy;
+use std::collections::HashMap;
+use std::ffi::{CStr, CString};
+use std::ptr;
+use std::sync::Mutex;
+
+// External declaration of the environ pointer
+// This is a POSIX standard global that points to the process's environment
+extern "C" {
+ static mut environ: *mut *mut c_char;
+}
+
+/// Maximum number of tokens we can track
+const MAX_TOKENS: usize = 100;
+
+/// Default sensitive token environment variable names
+const DEFAULT_SENSITIVE_TOKENS: &[&str] = &[
+ // GitHub tokens
+ "COPILOT_GITHUB_TOKEN",
+ "GITHUB_TOKEN",
+ "GH_TOKEN",
+ "GITHUB_API_TOKEN",
+ "GITHUB_PAT",
+ "GH_ACCESS_TOKEN",
+ // OpenAI tokens
+ "OPENAI_API_KEY",
+ "OPENAI_KEY",
+ // Anthropic/Claude tokens
+ "ANTHROPIC_API_KEY",
+ "CLAUDE_API_KEY",
+ // Codex tokens
+ "CODEX_API_KEY",
+];
+
+/// State for tracking tokens and their cached values
+struct TokenState {
+ /// List of sensitive token names to protect
+ tokens: Vec,
+ /// Cached token values - stored on first access so subsequent reads succeed
+ /// even after the variable is unset from the environment. This allows
+ /// /proc/self/environ to be cleaned while the process can still read tokens.
+ /// Maps token name to cached C string pointer (or null if token was not set).
+ cache: HashMap,
+ /// Whether initialization has completed
+ initialized: bool,
+}
+
+// SAFETY: TokenState is only accessed through a Mutex, ensuring thread safety
+unsafe impl Send for TokenState {}
+unsafe impl Sync for TokenState {}
+
+impl TokenState {
+ fn new() -> Self {
+ Self {
+ tokens: Vec::new(),
+ cache: HashMap::new(),
+ initialized: false,
+ }
+ }
+}
+
+/// Global state protected by a mutex
+static STATE: Lazy> = Lazy::new(|| Mutex::new(TokenState::new()));
+
+/// Type alias for the real getenv function
+type GetenvFn = unsafe extern "C" fn(*const c_char) -> *mut c_char;
+
+/// Cached pointer to the real getenv function
+static REAL_GETENV: Lazy = Lazy::new(|| {
+ // SAFETY: We're looking up a standard C library function
+ unsafe {
+ let symbol = libc::dlsym(libc::RTLD_NEXT, c"getenv".as_ptr());
+ if symbol.is_null() {
+ eprintln!("[one-shot-token] FATAL: Could not find real getenv");
+ std::process::abort();
+ }
+ std::mem::transmute::<*mut c_void, GetenvFn>(symbol)
+ }
+});
+
+/// Cached pointer to the real secure_getenv function (may be null if unavailable)
+static REAL_SECURE_GETENV: Lazy