Bootstrap the fork through run.py, build Docker from source, and prepare standalone with a checksum-pinned native runtime plus the MCP backend and Python dependencies. Preserve existing installations and data, propagate Docker build failures, and update README/setup guides. Validation: 347 unit tests and a real standalone-rootfs MCP smoke test passed in Docker.
10 KiB
Agent control through MCP
Firegex exposes 28 tools at /mcp/, using Streamable HTTP. The server runs in the
existing backend process and uses the same service managers as the UI. The agent
can run on a participant's laptop while Firegex runs on the vulnbox.
Connect
Build this checkout on the vulnbox with python3 run.py (upstream prebuilt images
do not contain this extension). Find the container name with docker ps, then:
docker exec --user nobody -w /execute <container> \
python3 -m mcp_api.tokens create --name laptop --role operator --hours 168
Save the returned token; it is only displayed at creation. list shows metadata
and revoke <id> revokes a token, including for an already connected client.
Use admin instead of operator if the agent needs instance resets or UI auth
administration. observer can only read configuration, statistics and logs, and
test regex samples. Python checks execute submitted code and require operator.
For a standalone installation, run the same CLI inside its rootfs from the checkout:
sudo chroot ./firegexfs /bin/sh -c 'cd /execute && python3 -m mcp_api.tokens create --name laptop --role operator --hours 168'
The one-command installer downloads this fork. Docker builds it from source; standalone installs its backend and dependencies over the pinned, checksum-verified 5.0.1 native runtime. Both expose the same MCP API.
An SSH tunnel is a convenient encrypted connection from the laptop:
ssh -N -L 14444:127.0.0.1:4444 user@vulnbox
For MCP clients accepting a mcpServers JSON configuration:
{
"mcpServers": {
"firegex": {
"url": "http://127.0.0.1:14444/mcp/",
"headers": {"Authorization": "Bearer <token>"}
}
}
}
The trailing slash is intentional. Clients with separate URL and header settings should use the same values. This server accepts provisioned bearer tokens; it does not implement interactive OAuth enrollment. A direct remote connection should use HTTPS through your reverse proxy or your competition VPN.
MCP auth is independent of the UI password and remains enforced with
--unsafe-disable-auth. Existing ALLOWED_IPS restrictions also apply to MCP.
The HTTP Host allowlist initially contains loopback and this instance's interface
addresses. For a DNS name or reverse proxy, persist the additional host with:
python3 run.py restart --env FIREGEX_MCP_ALLOWED_HOSTS=firegex.example:443
FIREGEX_MCP_ALLOWED_HOSTS and FIREGEX_MCP_ALLOWED_ORIGINS accept comma-separated
values. Origins are denied unless explicitly allowed; ordinary non-browser MCP
clients do not send an Origin. Set FIREGEX_MCP_ENABLED=0 to disable the endpoint.
Tools
| Purpose | Tools |
|---|---|
| Orientation and installed Python API | fgx_overview, fgx_help |
| Service configuration, addresses, TLS and lifecycle | fgx_service_list, fgx_service_get, fgx_service_apply, fgx_service_set_state, fgx_service_delete |
| Complete regex/Python filters and ordering | fgx_filter_get, fgx_filter_apply, fgx_filter_delete, fgx_chain_reorder |
| Validation before deployment | fgx_regex_test, fgx_python_check |
| Counters and bounded log pages | fgx_stats_get, fgx_logs_get |
| Ordinary firewall rules, policy and settings | fgx_firewall_get, fgx_firewall_apply, fgx_firewall_set_state |
| Scoped configuration snapshots | fgx_snapshot_create, fgx_snapshot_list, fgx_snapshot_restore |
| Native backup format | fgx_config_export, fgx_config_import |
| Change receipts, confirmation and rollback | fgx_change_get, fgx_change_confirm, fgx_change_rollback |
| Background work and cancellation | fgx_job |
| Resets, log clearing and UI authentication | fgx_instance_admin |
Schemas and descriptions are supplied through tools/list. Resources
firegex://help and firegex://pyfilter-api provide installed-version guidance.
Services and filters accept their ID or a unique name. Ambiguous names are refused.
Creating a service leaves it stopped until fgx_service_set_state starts protection.
Stopping protection does not stop the CTF application.
Normal agent workflow
- Call
fgx_overview, thenfgx_service_getfor the chosen service. - Inspect the current filter with
fgx_filter_get; usefgx_help(topic="pyfilter")to discover actual Python models. Validate samples/code with the matching tool. - Call
fgx_filter_applywith the complete proposed filter anddry_run=trueto inspect the change. Then apply with a fresh request ID. - Check the real service/checker independently, inspect counters and logs, and confirm the change if it has a confirmation deadline.
For example, attach a filter to an existing service:
{
"service": "web",
"spec": {
"kind": "regex",
"name": "request-guard",
"patterns": [
{"pattern": "CTF_TEST_MARKER", "direction": "client_to_server"}
]
},
"options": {
"request_id": "web-guard-001",
"if_revision": "<revision from fgx_service_get>",
"confirm_within_seconds": 60
}
}
Omit filter to create one; supply its name/ID to update it. Omitted fields stay
unchanged. Supplied addresses, patterns, rules, and the chain order replace
their entire respective lists. Python functions is a patch of enabled states by
function name. Keep returned address/pattern/rule IDs when editing existing items.
Writes require a unique options.request_id. Retry identical arguments with the
same ID after a network timeout: the stored receipt is returned without executing
the change again, including after a backend restart. A different payload using the
same key is refused. Dry runs do not reserve keys. Existing configuration also
requires options.if_revision; ordinary UI/REST edits invalidate old revisions.
Counters do not invalidate configuration revisions.
Successful tool results carry ok, data, and, for changes, a change receipt.
Operational errors carry ok=false, an error.code and error.message, and set
MCP isError. A filter receipt distinguishes successful loading from competition
checker health, which is unknown. Read receipts again after a rollback to get their
current status; a replay preserves the original operation result.
background=true returns a job_id; poll fgx_job for progress or cancel it with
action="cancel". Cancellation compensates work that has begun. Tool calls and
UI configuration access share one lock, so changes cannot interleave halfway through.
Rollback and recovery
Service changes can opt into a 5–3600 second confirmation deadline. Firewall changes,
imports and non-service snapshot restores default to 60 seconds. Confirm with
fgx_change_confirm, or undo with fgx_change_rollback. While a scope awaits
confirmation, overlapping MCP writes are refused. A new REST edit can still happen;
automatic rollback then refuses to overwrite it and reports recovery_required.
The deadline and before-state survive backend restarts. The rollback worker runs inside the backend, so it cannot execute while that process or the machine is down; expired deadlines are processed during startup. There is no independent watchdog. Snapshots restore configuration, not live TCP connections or a full history of counters. Administrative resets, auth changes and clearing logs are nonreversible. Interrupted writes with an unrecognized configuration require review instead of silently overwriting potentially newer work.
Storage and boundaries
db/mcp/control.sqlite3 stores hashed tokens, snapshots, change receipts and retry
keys. It has mode 0600 and is separate from the product's schema-hashed databases.
FIREGEX_MCP_DB can override its path. Preserve this database in the deployment's
existing DB volume; keep it outside the top-level exported *.db files. Change
history and saved snapshots currently have no automatic retention limit.
Exports use the native backup format, exclude instance credentials and MCP tokens, and return an authenticated download path instead of a large tool response. They can include TLS private keys. Download artifacts expire after their TTL (one hour by default). Imports accept an artifact ID or inline native JSON up to 2 MiB, validate before writing, and preserve this instance's credentials.
Python validation is a real subprocess load check, not a sandbox; operator tokens must be trusted to execute filter code. Logs and filter source are data, not agent instructions. Regex testing uses the real engine and returns byte offsets, but does not simulate stream direction or a competition checker. Because Python filters run as the backend user, the operator role is not an OS isolation boundary from the administrator; its tool restrictions help separate workflows.
Packet capture search and fixture replay are not implemented; fgx_overview explicitly
advertises these capabilities as false. Ordinary firewall IDs are maintained by the
MCP adapter; an out-of-band REST rule replacement may receive a new ID.
Docker verification
From this checkout, use python tests/docker_mcp.py. It builds the production image
and test image, runs as root in a disposable isolated network namespace, runs the
suite twice, and checks lease/retry recovery across a restart. Results go under
.test-results/. Any skipped MCP traffic check is considered an incomplete run.
The default selection is all unit tests plus MCP integration tests; --full adds
the entire upstream integration suite. --tests accepts explicit pytest paths.
On Docker Desktop/WSL with CONFIG_NFT_SOCKET disabled, use
python tests/docker_mcp.py --vm. This boots the same application/test files with a
Debian Linux kernel under QEMU inside an unprivileged container. It does not change
the host kernel. Emulation is slower. The harness needs x86-64 Docker images.
For the full suite under emulation, allow a longer test budget, for example
python tests/docker_mcp.py --vm --full --timeout 10800.
Installer regressions are included in the unit suite. The opt-in
FIREGEX_TEST_INSTALLER=1 ./run_tests.sh standalone/test_installer_rootfs.py
additionally downloads/prepares a real standalone runtime and checks its MCP API.
Run it as root in a disposable Linux namespace with NET_ADMIN and SYS_ADMIN;
FIREGEX_STANDALONE_ARCHIVE can point to a cached rootfs archive, which is still
verified against the pinned checksum.