utils/safe_yaml.py (ent#314, PR #1961) consolidated every author-controlled YAML reader in the backend onto one hardened loader — size cap, alias policy, duplicate-key rejection — and its guard test walks the whole backend with an empty allowlist, so a new bare yaml.safe_load there fails the build.
The agent server was outside that sweep. test_no_service_parses_yaml_without_the_shared_loader walks _BACKEND.rglob("*.py") only, and docker/base-image/agent_server/ still parses author-controlled documents with bare yaml.safe_load:
| Site |
Document |
Backend-side policy for the same document |
routers/info.py:108,173,252 |
template.yaml |
BUDGET (catalog) / REJECT (live agent-writable, credential_requirements_service) |
routers/skills.py:66 |
skill frontmatter |
REJECT (skill_packaging) |
routers/dashboard.py:205 |
dashboard.yaml |
REJECT (via compatibility/static_checks) |
routers/files.py:244 |
.trinity/persistent-state.yaml |
— |
Why it matters
The vector ent#314 measured is amplification at serialization, not parse: a 416 B level-6 anchor bomb resolves in ~0.001 s and blows up to ~110 MB when something walks the graph. The backend proxies /info and /dashboard from the agent server, so the same walk happens — just inside the container first, then across the wire.
Lower privilege than the catalog path ent#314 closed (this one needs an already-created agent whose workspace you can write, rather than any creator-role user pointing creation at a public repo), which is why #1961 shipped without it. But template.yaml inside a running container is exactly the "live agent-writable" document ent#314 assigns the strictest REJECT policy on the backend side.
Shape of the fix
Invariant #5's vendored-mirror pattern, as already done for credential_paths.py and model_context.py: vendor utils/safe_yaml.py byte-identically into docker/base-image/agent_server/, add a parity test, wire the six call sites with the policy their backend counterpart uses, and extend the ent#314 AST guard to walk docker/base-image/ too — otherwise the next agent-server YAML reader lands unguarded exactly the same way.
Acceptance
Follow-up to trinity-enterprise#314 · surfaced in review of #1961.
utils/safe_yaml.py(ent#314, PR #1961) consolidated every author-controlled YAML reader in the backend onto one hardened loader — size cap, alias policy, duplicate-key rejection — and its guard test walks the whole backend with an empty allowlist, so a new bareyaml.safe_loadthere fails the build.The agent server was outside that sweep.
test_no_service_parses_yaml_without_the_shared_loaderwalks_BACKEND.rglob("*.py")only, anddocker/base-image/agent_server/still parses author-controlled documents with bareyaml.safe_load:routers/info.py:108,173,252template.yamlBUDGET(catalog) /REJECT(live agent-writable,credential_requirements_service)routers/skills.py:66REJECT(skill_packaging)routers/dashboard.py:205dashboard.yamlREJECT(viacompatibility/static_checks)routers/files.py:244.trinity/persistent-state.yamlWhy it matters
The vector ent#314 measured is amplification at serialization, not parse: a 416 B level-6 anchor bomb resolves in ~0.001 s and blows up to ~110 MB when something walks the graph. The backend proxies
/infoand/dashboardfrom the agent server, so the same walk happens — just inside the container first, then across the wire.Lower privilege than the catalog path ent#314 closed (this one needs an already-created agent whose workspace you can write, rather than any
creator-role user pointing creation at a public repo), which is why #1961 shipped without it. Buttemplate.yamlinside a running container is exactly the "live agent-writable" document ent#314 assigns the strictestREJECTpolicy on the backend side.Shape of the fix
Invariant #5's vendored-mirror pattern, as already done for
credential_paths.pyandmodel_context.py: vendorutils/safe_yaml.pybyte-identically intodocker/base-image/agent_server/, add a parity test, wire the six call sites with the policy their backend counterpart uses, and extend the ent#314 AST guard to walkdocker/base-image/too — otherwise the next agent-server YAML reader lands unguarded exactly the same way.Acceptance
safe_yaml.pyvendored into the agent server with a byte-parity test (thecredential_paths.pyshape)docker/base-image/with an empty-by-default allowlisttemplate.yamlis refused byGET /api/agents/{name}/infowith a named error, not a timeoutFollow-up to trinity-enterprise#314 · surfaced in review of #1961.