This experimental service is designed for trusted application-to-service traffic on a private network. Do not expose the example routes or Python server directly to the internet. Across hosts, put a TLS proxy in front of the worker as Cross-host use behind TLS shows.
- Generate a service token of at least 32 non-whitespace ASCII characters. Never commit it. A token reaches only the jobs it submitted: another token's job answers 404 as if it did not exist, and idempotency keys are per token. Give each application, and the MCP server, its own token in
BRIDGE_TOKENSorBRIDGE_TOKENS_FILE(mode 0600 or stricter), so one can be revoked by removing its entry and restarting the worker without touching the others, and a leaked token exposes only its own client's jobs. Capacity is still shared, so any token can fill the worker;/healthzand/metricsanswer every token, and the metrics carry the token names, never the tokens. The job table stores a job's owner by name only. - Enforce end-user authentication and per-job ownership in the PHP application. The example deliberately omits those application-specific policies and publishes no ports.
- Only trusted task implementations are allowed. The service removes
BRIDGE_TOKENandBRIDGE_TOKENSfrom its environment before it starts its child processes, and readsBRIDGE_TOKENS_FILEwithout putting its content there, so no token is in either their environment nor their initial environment block (/proc/<pid>/environ), which a removal inside the child would leave as it was; the children never speak HTTP. Everything else in the service environment stays visible to them. A child serves many jobs in turn and keeps its loaded models; it keeps no reference to a finished job's input or result, but the memory they used is freed, not wiped, so a core dump of a long-lived child can still hold earlier jobs' data where a process that died after each job could not. They are not suitable for arbitrary user-provided scripts. - Container configuration uses a non-root user, read-only filesystem, dropped capabilities, no-new-privileges, CPU/memory/PID bounds, and an internal-only network for execution. It mounts only this checkout and the optional dependency/model cache, never a Docker socket or home directory.
- Dependency acquisition is separate and internet-enabled. Execution installs hash-checked wheels from the local cache without internet access. The model downloader retrieves fixed files at a pinned revision and checks their hashes; it does not execute model-repository code.
- CI runs on GitHub's hosted runners only, because the repository is public. GitHub runs a pull request's workflows as the pull request has them, so a fork's pull request executes its own code, workflow edits included; on a disposable runner of GitHub's that holds nothing of the maintainer's, that is the intended boundary. No workflow names a self-hosted runner, and
tests/python/test_workflows.pyfails if one does again; a fork can edit that test along with the workflows, so while any self-hosted runner is still registered for the repository, keep requiring approval for outside contributors' workflow runs, and unregister it so that there is none a pull request could route its code onto. Every action a workflow uses is pinned by commit hash, and theactions/checkoutstep does not persist credentials; the same test fails if either changes. - Model files, native libraries and runtime dependencies still carry supply-chain risk. Review upgrades, regenerate locks deliberately and rerun tests before changing pins.
- Requests and results may contain sensitive documents. They live in RAM, may be present in process/core dumps, and are not encrypted by this service. With
BRIDGE_STATE_DIRset,jobs.sqlite(and its-waland-shmfiles) in that directory holds each retained job's ID, task, status, error, progress, deadline, idempotency key, submission and finish times, and its result until retention expires; it never holds a job's input, though results such as redacted text, rankings and embedding vectors reveal much of it. SQLite'ssecure_deleteoverwrites an expired row in the database file, but older copies of its page can stay in the-walfile until a checkpoint reuses it, so keep the directory on tmpfs or an encrypted volume readable only by the service user (compose'sworkeruses a 64 MB tmpfs owned by uid 65532 with mode 0700). Minimize retention and use appropriate host/storage policies. Embedding vectors are derived from their input text and can reveal much of it; store and transmit them with the same care as the text. - Redaction is assistive, not a compliance control. The rules find only the structured identifiers they recognise, and the optional named-entity models were trained on English news, so their recall on names from other domains or languages is lower and unmeasured here; review redacted output before treating it as free of personal data. The model files are third-party ONNX conversions of the original weights, pinned by revision and hash but not signed by the original author.
- The MCP server is a client of this service for an AI agent over stdio, where there is no authentication: the host that launches it inherits its trust, and it holds the token from its environment and never returns it. Running it over HTTP or SSE would be a new authorisation surface that the bearer token does not cover; it is out of scope. An agent can be prompt-injected and can then fill a worker's capacity, so point the MCP server at a dedicated worker, never at one serving a production application; compose's
mcpandmcp-modeleach have their own. It reads files only underBRIDGE_MCP_ROOTand writes none; snippets it returns are the agent's own file content and enter the agent's context like any file it reads. Index files built forbridge_searchare derived data:index.jsonholds every chunk's text verbatim andvectors.f32holds vectors that can reveal it, so keep them with the same care as their sources. The builder runs with the service token like the PHP client and is the only component that writes, into the directory the operator names. GET /metricsreveals aggregate numbers only: how many jobs of each task were submitted, refused for capacity and ended in each terminal status, how many are queued and running, the pool's size and readiness, the concurrency and capacity settings, the backend name, and histograms of job and queue durations per task. It never reveals a job ID, idempotency key, input, result, error message, model path or the token; its label values come from fixed sets, never from a request. Counts and timings still describe the traffic (how busy the service is, which tasks run, how long they take), so the endpoint is behind the same service token as every route: scrape it from inside the private network with that token, never through a public route. How often it is scraped is the operator's concern; a scrape copies a few counters under the store's lock and formats them outside it.- Access logs are disabled to avoid accidentally recording identifiers and inputs. Internal exceptions become generic task errors. This is not a full observability or compliance solution.
- Cancel/timeout terminates the task process, but cannot roll back previously completed external effects. No task in the prototype modifies an external system.
- The default health endpoint proves HTTP service availability, not model integrity, ability to spawn processes or successful inference. An ONNX worker checks before it starts that each model directory holds its tokenizer and model files and refuses to start otherwise; that proves presence, not integrity, which the fetcher's hashes establish. Use a controlled model smoke test for readiness validation.
The FrankenPHP executable is copied from its pinned image into a task-only executable tmpfs. This removes upstream file capabilities without granting the container capabilities. Other temporary storage is non-executable; the optional Python dependency tmpfs must permit native library mappings.