Skip to content

Test Network Permissions #61

Test Network Permissions

Test Network Permissions #61

Workflow file for this run

# This file was automatically generated by gh-aw. DO NOT EDIT.
# To update this file, edit the corresponding .md file and run:
# gh aw compile
name: "Test Network Permissions"
on:
pull_request:
branches:
- main
workflow_dispatch: null
permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
cancel-in-progress: true
run-name: "Test Network Permissions"
jobs:
test-network-permissions:
runs-on: ubuntu-latest
permissions:
contents: read
issues: read
pull-requests: read
discussions: read
deployments: read
models: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Proxy Configuration for MCP Network Restrictions
run: |
echo "Generating proxy configuration files for MCP tools with network restrictions..."
# Generate Squid proxy configuration
cat > squid.conf << 'EOF'
# Squid configuration for egress traffic control
# This configuration implements a whitelist-based proxy
# Access log and cache configuration
access_log /var/log/squid/access.log squid
cache_log /var/log/squid/cache.log
cache deny all
# Port configuration
http_port 3128
# ACL definitions for allowed domains
acl allowed_domains dstdomain "/etc/squid/allowed_domains.txt"
acl localnet src 10.0.0.0/8
acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl CONNECT method CONNECT
# Access rules
# Deny requests to unknown domains (not in whitelist)
http_access deny !allowed_domains
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localnet
http_access deny all
# Disable caching
cache deny all
# DNS settings
dns_nameservers 8.8.8.8 8.8.4.4
# Forwarded headers
forwarded_for delete
via off
# Error page customization
error_directory /usr/share/squid/errors/English
# Logging
logformat combined %>a %[ui %[un [%tl] "%rm %ru HTTP/%rv" %>Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
access_log /var/log/squid/access.log combined
# Memory and file descriptor limits
cache_mem 64 MB
maximum_object_size 0 KB
EOF
# Generate allowed domains file
cat > allowed_domains.txt << 'EOF'
# Allowed domains for egress traffic
# Add one domain per line
example.com
EOF
# Generate Docker Compose configuration for fetch
cat > docker-compose-fetch.yml << 'EOF'
services:
squid-proxy:
image: ubuntu/squid:latest
container_name: squid-proxy-fetch
ports:
- "3128:3128"
volumes:
- ./squid.conf:/etc/squid/squid.conf:ro
- ./allowed_domains.txt:/etc/squid/allowed_domains.txt:ro
- squid-logs:/var/log/squid
healthcheck:
test: ["CMD", "squid", "-k", "check"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
networks:
awproxy-fetch:
ipv4_address: 172.28.179.10
fetch:
image: mcp/fetch
container_name: fetch-mcp
stdin_open: true
tty: true
environment:
- PROXY_HOST=squid-proxy
- PROXY_PORT=3128
- HTTP_PROXY=http://squid-proxy:3128
- HTTPS_PROXY=http://squid-proxy:3128
networks:
- awproxy-fetch
depends_on:
squid-proxy:
condition: service_healthy
volumes:
squid-logs:
networks:
awproxy-fetch:
driver: bridge
ipam:
config:
- subnet: 172.28.179.0/24
EOF
echo "Proxy configuration files generated."
- name: Pre-pull images and start Squid proxy
run: |
set -e
echo 'Pre-pulling Docker images for proxy-enabled MCP tools...'
docker pull ubuntu/squid:latest
echo 'Pulling mcp/fetch for tool fetch'
docker pull mcp/fetch
echo 'Starting squid-proxy service for fetch'
docker compose -f docker-compose-fetch.yml up -d squid-proxy
echo 'Enforcing egress to proxy for fetch (subnet 172.28.179.0/24, squid 172.28.179.10)'
if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=; fi
$SUDO iptables -C DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || $SUDO iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$SUDO iptables -C DOCKER-USER -s 172.28.179.10 -j ACCEPT 2>/dev/null || $SUDO iptables -I DOCKER-USER 2 -s 172.28.179.10 -j ACCEPT
$SUDO iptables -C DOCKER-USER -s 172.28.179.0/24 -d 172.28.179.10 -p tcp --dport 3128 -j ACCEPT 2>/dev/null || $SUDO iptables -I DOCKER-USER 3 -s 172.28.179.0/24 -d 172.28.179.10 -p tcp --dport 3128 -j ACCEPT
$SUDO iptables -C DOCKER-USER -s 172.28.179.0/24 -j REJECT 2>/dev/null || $SUDO iptables -A DOCKER-USER -s 172.28.179.0/24 -j REJECT
- name: Setup MCPs
run: |
mkdir -p /tmp/mcp-config
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
{
"mcpServers": {
"fetch": {
"command": "docker",
"args": [
"compose",
"-f",
"docker-compose-fetch.yml",
"run",
"--rm",
"fetch"
]
},
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-45e90ae"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
}
}
}
}
EOF
- name: Create prompt
run: |
mkdir -p /tmp/aw-prompts
cat > /tmp/aw-prompts/prompt.txt << 'EOF'
# Test Network Permissions
## Task Description
Test the MCP network permissions feature to validate that domain restrictions are properly enforced.
- Use the fetch tool to successfully retrieve content from `https://example.com/` (the only allowed domain)
- Attempt to access blocked domains and verify they fail with network errors:
- `https://httpbin.org/json`
- `https://api.github.com/user`
- `https://www.google.com/`
- `http://malicious-example.com/`
- Verify that all blocked requests fail at the network level (proxy enforcement)
- Confirm that only example.com is accessible through the Squid proxy
## Reporting Instructions
If there are any failures, security issues, or unexpected behaviors:
- Write a detailed report to ${{ env.GITHUB_AW_OUTPUT }} documenting:
- Which domains were successfully accessed vs blocked
- Error messages received for blocked domains
- Any security observations or recommendations
- Specific failure details that need attention
The test results will be automatically posted as a comment on PR #${{ github.event.pull_request.number }}.
EOF
- name: Print prompt to step summary
run: |
echo "## Generated Prompt" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '``````markdown' >> $GITHUB_STEP_SUMMARY
cat /tmp/aw-prompts/prompt.txt >> $GITHUB_STEP_SUMMARY
echo '``````' >> $GITHUB_STEP_SUMMARY
- name: Generate agentic run info
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const awInfo = {
engine_id: "claude",
engine_name: "Claude Code",
model: "",
version: "",
workflow_name: "Test Network Permissions",
experimental: false,
supports_tools_whitelist: 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,
created_at: new Date().toISOString()
};
// Write to /tmp directory to avoid inclusion in PR
const tmpPath = '/tmp/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));
- name: Upload agentic run info
if: always()
uses: actions/upload-artifact@v4
with:
name: aw_info.json
path: /tmp/aw_info.json
if-no-files-found: warn
- name: Execute Claude Code Action
id: agentic_execution
uses: anthropics/claude-code-base-action@v0.0.56
with:
# Allowed tools (sorted):
# - Glob
# - Grep
# - LS
# - NotebookRead
# - Read
# - Task
# - mcp__fetch__fetch
# - 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_issue
# - mcp__github__get_issue_comments
# - mcp__github__get_job_logs
# - 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_reviews
# - mcp__github__get_pull_request_status
# - 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__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_issues
# - mcp__github__list_notifications
# - mcp__github__list_pull_requests
# - mcp__github__list_secret_scanning_alerts
# - 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__search_code
# - mcp__github__search_issues
# - mcp__github__search_orgs
# - mcp__github__search_pull_requests
# - mcp__github__search_repositories
# - mcp__github__search_users
allowed_tools: "Glob,Grep,LS,NotebookRead,Read,Task,mcp__fetch__fetch,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_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,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_reviews,mcp__github__get_pull_request_status,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__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_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_secret_scanning_alerts,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__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
mcp_config: /tmp/mcp-config/mcp-servers.json
prompt_file: /tmp/aw-prompts/prompt.txt
timeout_minutes: 5
- name: Capture Agentic Action logs
if: always()
run: |
# Copy the detailed execution file from Agentic Action if available
if [ -n "${{ steps.agentic_execution.outputs.execution_file }}" ] && [ -f "${{ steps.agentic_execution.outputs.execution_file }}" ]; then
cp ${{ steps.agentic_execution.outputs.execution_file }} /tmp/test-network-permissions.log
else
echo "No execution file output found from Agentic Action" >> /tmp/test-network-permissions.log
fi
# Ensure log file exists
touch /tmp/test-network-permissions.log
- name: Check if workflow-complete.txt exists, if so upload it
id: check_file
run: |
if [ -f workflow-complete.txt ]; then
echo "File exists"
echo "upload=true" >> $GITHUB_OUTPUT
else
echo "File does not exist"
echo "upload=false" >> $GITHUB_OUTPUT
fi
- name: Upload workflow-complete.txt
if: steps.check_file.outputs.upload == 'true'
uses: actions/upload-artifact@v4
with:
name: workflow-complete
path: workflow-complete.txt
- name: Upload engine output files
if: always()
uses: actions/upload-artifact@v4
with:
name: agent_outputs
path: |
output.txt
if-no-files-found: ignore
- name: Upload agent logs
if: always()
uses: actions/upload-artifact@v4
with:
name: test-network-permissions.log
path: /tmp/test-network-permissions.log
if-no-files-found: warn
create_issue_comment:
needs: test-network-permissions
if: github.event.issue.number || github.event.pull_request.number
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
timeout-minutes: 10
outputs:
comment_id: ${{ steps.create_comment.outputs.comment_id }}
comment_url: ${{ steps.create_comment.outputs.comment_url }}
steps:
- name: Create Output Comment
id: create_comment
uses: actions/github-script@v7
env:
GITHUB_AW_AGENT_OUTPUT: ${{ needs.test-network-permissions.outputs.output }}
with:
script: |
async function main() {
// Read the agent output content from environment variable
const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT;
if (!outputContent) {
console.log('No GITHUB_AW_AGENT_OUTPUT environment variable found');
return;
}
if (outputContent.trim() === '') {
console.log('Agent output content is empty');
return;
}
console.log('Agent output content length:', outputContent.length);
// Check if we're in an issue or pull request context
const isIssueContext = context.eventName === 'issues' || context.eventName === 'issue_comment';
const isPRContext = context.eventName === 'pull_request' || context.eventName === 'pull_request_review' || context.eventName === 'pull_request_review_comment';
if (!isIssueContext && !isPRContext) {
console.log('Not running in issue or pull request context, skipping comment creation');
return;
}
// Determine the issue/PR number and comment endpoint
let issueNumber;
let commentEndpoint;
if (isIssueContext) {
if (context.payload.issue) {
issueNumber = context.payload.issue.number;
commentEndpoint = 'issues';
} else {
console.log('Issue context detected but no issue found in payload');
return;
}
} else if (isPRContext) {
if (context.payload.pull_request) {
issueNumber = context.payload.pull_request.number;
commentEndpoint = 'issues'; // PR comments use the issues API endpoint
} else {
console.log('Pull request context detected but no pull request found in payload');
return;
}
}
if (!issueNumber) {
console.log('Could not determine issue or pull request number');
return;
}
let body = outputContent.trim();
// Add AI disclaimer with run id, run htmlurl
const runId = context.runId;
const runUrl = context.payload.repository
? `${context.payload.repository.html_url}/actions/runs/${runId}`
: `https://github.com/actions/runs/${runId}`;
body += `\n\n> Generated by Agentic Workflow Run [${runId}](${runUrl})\n`;
console.log(`Creating comment on ${commentEndpoint} #${issueNumber}`);
console.log('Comment content length:', body.length);
// Create the comment using GitHub API
const { data: comment } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: body
});
console.log('Created comment #' + comment.id + ': ' + comment.html_url);
// Set output for other jobs to use
core.setOutput('comment_id', comment.id);
core.setOutput('comment_url', comment.html_url);
// write comment id, url to the github_step_summary
await core.summary.addRaw(`
## GitHub Comment
- Comment ID: ${comment.id}
- Comment URL: ${comment.html_url}
`).write();
}
await main();