Secure Execution Gateway - API-first service for executing pre-defined system commands
Project description
FExec - Secure Execution Gateway
FExec is an HTTP API service that executes pre-defined system commands or Docker container tasks on demand. It acts as a security boundary between untrusted callers and the host execution environment: only explicitly configured command aliases can be triggered, and every parameter is validated before use.
Objectives
Containers that provide functionality used only occasionally still consume memory and CPU while they sit idle, waiting to be needed. If usage follows a regular schedule, a cron job can start the container when needed and stop it afterwards. If usage is purely interactive, a person can start it by hand. But when the functionality is needed on demand — triggered by an event such as a step in an automated workflow — neither option fits: nothing is watching for the event, so the container either runs continuously to be ready (wasting resources) or isn't there when the event happens.
The obvious workaround, for local automation, is to give the calling process access to the Docker socket so it can start containers itself. That solves the on-demand problem, but at a cost: anything with socket access can run arbitrary containers with no restriction on image, command, or resources — an unauthenticated, unaudited path to full control of the Docker daemon.
FExec exists to close that gap. It replaces direct Docker socket access with a narrow, authenticated HTTP API: callers can only trigger commands or containers an operator has explicitly pre-defined as aliases, with every parameter validated before use. Functionality that's needed only occasionally can be started exactly when it's needed — without leaving it running all the time, and without handing callers unrestricted control of the host.
Background
FExec grew out of a concrete need: running browser-based web automation (via Playwright) as part of a larger workflow. Playwright containers are memory-heavy, and running them permanently — especially alongside other, similar occasional-use workloads competing for the same memory — wasn't a good trade-off.
The automation itself was orchestrated by an internal n8n instance, but the intent was to expose the ability to trigger it more broadly, including to callers outside the local network, while tightly restricting which containers could run and what they were allowed to do. That requirement put potentially dangerous operations — starting arbitrary containers or processes — behind three layers of protection: authentication of every call to FExec (calls to the Docker socket itself are never authenticated), a strict, operator-defined whitelist of what can be run at all (no arbitrary commands, ever), and granular authorization that determines, per caller, which of those pre-defined aliases they're actually allowed to invoke.
Execution Modes and Types
Every action FExec's API can perform is defined as an alias — a named, operator-authored configuration that combines what to run, how input and output are mapped, how it's logged, and other execution details. Each alias is configured along two independent dimensions: type (what runs) and mode (how its lifecycle and invocation are handled).
Each alias is configured as one of two execution types:
- Container — FExec starts a Docker container, runs it to completion, and returns the result. Input and output are exchanged with the containerized process through whichever combination of environment variables, extra command-line arguments, streams, and files the alias maps to the API's parameters.
- Process — FExec starts a normal host process instead of a container, using the same range of input/output mapping options (environment variables, command-line arguments, streams, files).
Both types share the same authentication, authorization, and parameter validation — the only difference is what actually runs: a container or a host process.
Every alias also declares a mode, mandatory, which currently must be inline — the only mode supported today. In Inline mode (for both Container and Process types), the API call blocks until the container/process runs to completion, and its output/exit code is returned inline with the response.
How It Works
Clients call FExec over HTTP, authenticating with a JWT or API key. FExec looks up the requested alias, validates all parameters against the alias's regex rules, executes the command (on the host or in a Docker container), and returns the result. No arbitrary commands can be run — only aliases defined by the operator.
Installation
pip install .
Or build a wheel first:
python -m build
pip install dist/fexec-*.whl
Quick Start
- Create
/etc/fexec/fexec.conf(or use defaults — the file is optional; a warning is printed if it is absent):
ListenPort = 8080
LogLevel = info
AppLog = /var/log/fexec/app.log info json
AuditLog = /var/log/fexec/audit.log
-
Add at least one authentication file to
/etc/fexec/auth.d/and at least one alias to/etc/fexec/alias.d/. See CONFIGURATION.md for format details. -
Start the service:
fexecd
# or override specific settings via CLI flags:
fexecd --config /path/to/fexec.conf --listen-port 9000
# or via environment variables:
FEXEC_LISTEN_PORT=9000 FEXEC_LOG_LEVEL=info fexecd
# run as a background daemon (detach from terminal):
fexecd --daemon
# print usage and all accepted flags, then exit:
fexecd --help
# print version string and exit:
fexecd --version
API
Authentication
Every request must include one of:
| Header | Value |
|---|---|
Authorization |
Bearer <JWT> |
X-API-Key |
<key> |
Endpoint
POST /v1/{alias_name}
Request Body
Parameters can be sent in any of the following formats:
JSON (Content-Type: application/json):
{ "param1": "value1", "param2": "value2" }
Form-encoded (Content-Type: application/x-www-form-urlencoded):
param1=value1¶m2=value2
Multipart form (Content-Type: multipart/form-data):
-F param1=value1 -F param2=value2
A request with no body (absent or zero Content-Length) is always treated as having no parameters, regardless of the Content-Type header. A request with a non-zero body and an unrecognised Content-Type is rejected with 400.
Response Codes
| Code | Meaning |
|---|---|
200 |
Success — body contains the alias output. Also returned when the command exited non-zero but the alias's exitCodeMap explicitly maps that exit code to 200 |
400 |
Bad request — missing/invalid/extra parameters |
401 |
Missing or invalid credentials |
403 |
Credentials valid but alias not permitted for this user |
404 |
Alias does not exist |
408 |
Execution timed out and the process/container was killed by FExec |
422 |
Command completed but exited with a non-zero code (default; overridable per alias via exitCodeMap — see Exit Code Status Mapping) |
500 |
Command executed but failed internally (infrastructure-level failure, e.g. Docker unreachable) |
503 |
Service is shutting down |
200 from FExec means both FExec and the invoked process/container
succeeded — not merely that FExec was able to run it — except when
exitCodeMap has explicitly reassigned that exit code's status.
Example
curl -X POST https://localhost:443/v1/generate-report \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <jwt>" \
-d '{"type": "pdf", "date": "2024-01-15"}'
Command Line Reference
Run fexecd --help (or fexecd -h) to see the full usage summary and exit without starting the service.
| Flag | Metavar | Config key | Environment variable |
|---|---|---|---|
--help, -h |
— | — | — |
--version |
— | — | — |
--config |
PATH |
— | — |
--daemon |
— | — | — |
--pid-file |
PATH |
PidFile |
FEXEC_PID_FILE |
--auth-dir |
DIR |
AuthDir |
FEXEC_AUTH_DIR |
--alias-dir |
DIR |
AliasDir |
FEXEC_ALIAS_DIR |
--shared-dir |
DIR |
SharedDir |
FEXEC_SHARED_DIR |
--external-dir |
DIR |
ExternalDir |
FEXEC_EXTERNAL_DIR |
--listen-address |
ADDR |
ListenAddress |
FEXEC_LISTEN_ADDRESS |
--listen-port |
PORT |
ListenPort |
FEXEC_LISTEN_PORT |
--ssl-enabled |
true|false|1|0 |
SslEnabled |
FEXEC_SSL_ENABLED |
--ssl-cert |
PATH |
SslCert |
FEXEC_SSL_CERT |
--ssl-key |
PATH |
SslKey |
FEXEC_SSL_KEY |
--log-level |
LEVEL |
LogLevel |
FEXEC_LOG_LEVEL |
--log-dir |
DIR |
LogDir |
FEXEC_LOG_DIR |
--audit-log |
PATH |
AuditLog |
FEXEC_AUDIT_LOG |
--app-log |
SPEC |
AppLog |
FEXEC_APP_LOG |
--auth-reload |
SECONDS |
AuthReload |
FEXEC_AUTH_RELOAD |
--alias-reload |
SECONDS |
AliasReload |
FEXEC_ALIAS_RELOAD |
--container |
0|1 |
Container |
FEXEC_CONTAINER |
--max-timeout |
SECONDS |
MaxTimeout |
FEXEC_MAX_TIMEOUT |
--refresh-image |
always|on-start|no |
RefreshImage |
FEXEC_REFRESH_IMAGE |
--prefetch-image |
on-start|on-config-change|no |
PrefetchImage |
FEXEC_PREFETCH_IMAGE |
Precedence (highest first): CLI flag → environment variable → fexec.conf → built-in default.
Configuration
FExec is configured through four layers (highest precedence first):
- CLI flags — e.g.
--listen-port 9000 - Environment variables —
FEXEC_<PARAM>e.g.FEXEC_LISTEN_PORT=9000 /etc/fexec/fexec.conf— main service settings (port, SSL, log file, directories, reload intervals)/etc/fexec/auth.d/*.yaml— users with API keys and permitted aliases; JWT public keys/etc/fexec/alias.d/*.yaml— command aliases (what to run, parameters, output format)
All configuration is loaded at startup and validated. Any error causes FExec to refuse to start and print all errors to the console and log. FExec also refuses to start if no aliases or no authentication (users or JWT keys) are configured, or if the SSL certificate is expired or mismatched with its key. At runtime, auth and alias configuration is reloaded automatically when files change (configurable interval) and on SIGHUP. The main fexec.conf requires a restart to re-apply.
See CONFIGURATION.md for a complete reference including all parameters, formats, and examples.
Process Management
| Signal | Effect |
|---|---|
SIGHUP |
Reload auth and alias configuration (non-disruptive) |
SIGTERM |
Graceful shutdown — stops accepting new requests, waits for in-flight ones to finish |
SIGINT (Ctrl-C) |
Same graceful shutdown as SIGTERM |
SIGKILL |
Immediate termination |
A PID file is written to /run/fexec.pid (configurable via PidFile).
Security Model
- Whitelist execution: only pre-configured aliases run; no arbitrary commands
- Per-parameter validation: every input validated against a regex defined in the alias
- Container isolation: Docker aliases run with
--network none,--read-only,--cap-drop ALL - No state leakage: per-execution temp directories are deleted after each run
- Opaque errors: internal failures return
500without stack traces; details go to the log - Audit log: every call is recorded with user identity, alias name, parameters, and exit status
Logs
FExec writes two independent log streams:
| Stream | Default path | Content |
|---|---|---|
| AuditLog | /var/log/fexec/audit.log |
One nginx-style line per request: timestamp, source IP, user, alias, duration, HTTP status, ApplicationCode, exit code, PID (process aliases) or container/image ID (container aliases), correlation ID |
| AppLog | /var/log/fexec/app.log |
Application diagnostic messages (startup, errors, warnings). JSON format by default. Level-filtered — default level is warn (set LogLevel = info to see startup messages) |
Both targets are configurable in fexec.conf and accept /dev/stdout or /dev/stderr as the path. A relative path for either is resolved against LogDir (default /var/log/fexec). When running interactively (stdout is a TTY) and neither log targets stdout/stderr, AppLog entries are also echoed to stdout in plain-text format at the configured LogLevel.
Every failed-request entry in AppLog includes the same correlation ID as the corresponding AuditLog line, allowing the two to be cross-referenced.
Volume Preserve
Container-type aliases can mark a volume with preserve: flatten or preserve: yes (in its YAML definition) to have that volume's content copied to LogDir after the container finishes, surviving the run directory's cleanup independent of the alias's output. See CONFIGURATION.md for the full semantics (applicability, naming, and collision handling).
Stream Preserve
Any alias (process or container) can set preserveStreams: true, or list individual stdout/stderr entries, to have that execution's stdout/stderr copied to LogDir after it finishes, independent of the alias's output. See CONFIGURATION.md for the full semantics.
Exit Code Status Mapping
By default a zero exit code returns HTTP 200 and any other exit code returns HTTP 422 — the response body is always built from the alias's output config either way. Any alias can override this via exitCodeMap, mapping specific exit codes or ranges to different HTTP statuses (including back to 200). See CONFIGURATION.md for the full semantics.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fexec-0.1.39-cp313-cp313-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: fexec-0.1.39-cp313-cp313-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 5.3 MB
- Tags: CPython 3.13, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88cb8d502688e1b0442afd4229969591c101503c317b2c52a56aa64287ae29b4
|
|
| MD5 |
0286092c6fb902e80cc69c6b8eed77b1
|
|
| BLAKE2b-256 |
e83c0eb3a4c5066911d2504a070345c51f6055b3b8d4a7d9fdb35c54369e4a09
|
File details
Details for the file fexec-0.1.39-cp313-cp313-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: fexec-0.1.39-cp313-cp313-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 5.0 MB
- Tags: CPython 3.13, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da8077301b38940bad5439c9b0aec7449e178752aed0032b46ec7ab5e0c5ef49
|
|
| MD5 |
2baee5a4cc2ba10c2a385874c9cb3bd8
|
|
| BLAKE2b-256 |
c18eeec3c87e185587f1a38e9dd9715e45b7160e2b7c1979e2f0a5fdfbb8f8a5
|
File details
Details for the file fexec-0.1.39-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: fexec-0.1.39-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 5.3 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3abb3917b4aef7bcd8ee3da7266fc29087734f4e518bc3c7f5796389431e6d7c
|
|
| MD5 |
144db3964be8618c8a4d6c62163f1845
|
|
| BLAKE2b-256 |
bda91fff8755a2959e6e3be1d60b691000f298145deb8fb5b7cb57c772faaf1f
|
File details
Details for the file fexec-0.1.39-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.
File metadata
- Download URL: fexec-0.1.39-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- Upload date:
- Size: 5.2 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ ARM64, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
443165dc3f4b35b427f706926b9085ec92fc96a88971bda35e68f7f64045ca5a
|
|
| MD5 |
25e5122d4cf462c68e28c10d1314f6c3
|
|
| BLAKE2b-256 |
02737c0336d2fa37f899e88155e09d7187086683e27c135019dfd114ad9a5aa1
|
File details
Details for the file fexec-0.1.39-cp312-cp312-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: fexec-0.1.39-cp312-cp312-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 5.3 MB
- Tags: CPython 3.12, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65a68412320793047720daf0312becec9e21fcf9704f4b515536a8d236e89e82
|
|
| MD5 |
f85f245d0ab8c62cd5d50cfb897d2599
|
|
| BLAKE2b-256 |
f88edf59cde8a0e0b3e74edbf74dd0f11691ad890b3fc7f10811b8b961bf44d7
|
File details
Details for the file fexec-0.1.39-cp312-cp312-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: fexec-0.1.39-cp312-cp312-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 5.0 MB
- Tags: CPython 3.12, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d16692688657c7c9dbb0b83b5e296034ed88e7472ec1cb483ed78c456126337
|
|
| MD5 |
97c2ca930f3ce23a0c3288596b7f4853
|
|
| BLAKE2b-256 |
5b9da9ee07133b23a7febf4e510019e5c5dad1787a9d0bd99a0497e2372e70bd
|
File details
Details for the file fexec-0.1.39-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: fexec-0.1.39-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 5.3 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abcca855dcf1c96e26a00667087a91dde477df18bfb2cbd0ef3ef77d6b288bd0
|
|
| MD5 |
56ddea0c1fb04e3556b1a1ab49495141
|
|
| BLAKE2b-256 |
3d04e4d2ef0468ff7771ab75779a28b5af6dcde309a239f5550984cc6bd5c773
|
File details
Details for the file fexec-0.1.39-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.
File metadata
- Download URL: fexec-0.1.39-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- Upload date:
- Size: 5.2 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ ARM64, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a0681017f894f8bc9e1ae4a8eb457fe01105014650d76d1b9e3a0c36b4d1499
|
|
| MD5 |
e8fcf6a567f33820a603cd744c88eb82
|
|
| BLAKE2b-256 |
e9fc73e0a42acd46358aa6e9291fa10d82610e23f90d7426b4dfba2a972845ff
|