Network Egress Check - #153
Conversation
JoeStech
commented
Aug 21, 2026
- Added runtime egress validation for AMD64 and Arm64 release images.
- Added blocked DNS/TCP/UDP/HTTP(S) negative controls and retained evidence.
- Made validation failures block production publication.
- Disabled FastMCP’s unnecessary PyPI update check.
- Moved contributor/build/release documentation into CONTRIBUTING.md.
There was a problem hiding this comment.
Pull request overview
Adds runtime egress validation for AMD64 and Arm64 release images, disables unnecessary update checks, and consolidates contributor documentation.
Changes:
- Adds traced network validation and negative controls.
- Gates production publication on validation.
- Moves build and release guidance into
CONTRIBUTING.md.
Critical blockers remain: published images are not digest-bound to validated images, and container-controlled evidence files can be tampered with.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Summary |
|---|---|
README.md |
Links to contributor documentation. |
mcp-local/tests/test_build_inputs.py |
Adds release-gate assertions. |
mcp-local/scripts/validate-runtime-egress.py |
Implements runtime egress validation. |
mcp-local/Dockerfile |
Disables FastMCP update checks. |
CONTRIBUTING.md |
Consolidates development and release guidance. |
.github/workflows/build-mcp-image.yml |
Runs validation before publication. |
Suppressed comments (4)
.github/workflows/build-mcp-image.yml:241
- This
push: truepublishes the semantic-versioned architecture tags before the runtime gate runs. If either validator fails,publish-releaseis skipped but the failed image remains publicly pullable as${VERSION}-amd64or${VERSION}-arm64, allowing distribution of an image that did not pass the release checks. Push to a quarantine/temporary reference or defer public publication until validation succeeds, while retaining a digest-addressable image for the check.
push: ${{ needs.validate-release.outputs.publish == 'true' }}
load: ${{ needs.validate-release.outputs.publish != 'true' }}
tags: ${{ env.IMAGE }}:${{ needs.validate-release.outputs.version }}-${{ matrix.tag }}
mcp-local/scripts/validate-runtime-egress.py:143
LD_LIBRARY_PATHis set on the wholedocker run, so it is inherited bystraceand every tracee._prepare_tracercopies the runner'slibc.so.6and other libraries into/validation/lib; consequently the MCP Python process can load the runner's glibc instead of the image's pinned runtime libraries. This can make validation fail for ABI differences or, worse, validate behavior that the released image does not have. Scope the tracer library path to the tracer process and clear it for the tracee (or use an image-compatible/static tracer).
"LD_LIBRARY_PATH=/validation/lib",
mcp-local/scripts/validate-runtime-egress.py:420
- Checking only the set
{9, 53, 80, 443}does not prove all five controls were observed: TCP/9 and UDP/9 collapse to one port, so this condition would still pass if one of those trace entries is missing. Compare(protocol, port)(and ideally the fixed destination) against all five expected controls so the “present in trace” claim is enforced per protocol.
and {9, 53, 80, 443}.issubset(detected_ports)
mcp-local/scripts/validate-runtime-egress.py:23
- The address regex does not match the normal
stracerendering of an IPv6 sockaddr, which includes a trailing argument such asinet_pton(AF_INET6, "2001:db8::1", &sin6_addr). Those lines are therefore skipped at the address-match check, so an external IPv6connect/sendtocan leaveruntime_attemptsempty and bypass the advertised IPv4/IPv6 egress gate. Allow the optional trailing arguments (or parse the sockaddr structure) here.
r'(?:inet_addr\("([^"]+)"\)|inet_pton\([^,]+, "([^"]+)"\))'
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
brikin01
left a comment
There was a problem hiding this comment.
Overall, the approach looks good to me. @JoeStech clarified that this PR is scoped to paths expected to operate offline. Intentional remote-repository and container-scanning requests are outside this gate’s scope and will be revisited, along with the documentation, once the corresponding carve-out language is finalized.
I think once the Copilot findings are addressed and the merge conflicts resolved, should be g2g