mirror of
https://github.com/Av0idD4rk/firegex.git
synced 2026-10-23 14:31:00 +00:00
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.
207 lines
10 KiB
Markdown
207 lines
10 KiB
Markdown
# 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:
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
sudo chroot ./firegexfs /bin/sh -c 'cd /execute && python3 -m mcp_api.tokens create --name laptop --role operator --hours 168'
|
||
```
|
||
|
||
The [one-command installer](../README.md#docker-mode-recommended) 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:
|
||
|
||
```sh
|
||
ssh -N -L 14444:127.0.0.1:4444 user@vulnbox
|
||
```
|
||
|
||
For MCP clients accepting a `mcpServers` JSON configuration:
|
||
|
||
```json
|
||
{
|
||
"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:
|
||
|
||
```sh
|
||
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
|
||
|
||
1. Call `fgx_overview`, then `fgx_service_get` for the chosen service.
|
||
2. Inspect the current filter with `fgx_filter_get`; use `fgx_help(topic="pyfilter")`
|
||
to discover actual Python models. Validate samples/code with the matching tool.
|
||
3. Call `fgx_filter_apply` with the complete proposed filter and `dry_run=true` to
|
||
inspect the change. Then apply with a fresh request ID.
|
||
4. 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:
|
||
|
||
```json
|
||
{
|
||
"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.
|