As for the MCP env (#213), a placeholder in a custom tool's exec keeps the
stored header, proxy or body only while the url (exact string) and the kind
are unchanged. A changed or emptied url, or a kind change, with a resolved
placeholder is refused (400 on the API and the test endpoint, a tool error
for update_custom_tool) before any write and before the test request is
sent. An explicit value with a new url is allowed.
Phase 3 of secrets at rest (#213, #216), same AES-256-GCM scheme and key.
- tools.exec joins the secret columns for system=false rows (AAD
tools.exec): the whole JSON is sealed in the {"__artex_enc":"enc:v1:..."}
envelope, so headers, body, proxy, script code and command are covered;
{} stays {}
- CreateCustomTool and UpdateCustomTool seal it; scanTool opens it, so
GetTool, ListTools and ListCustomTools (and through them customTools,
the platform tools and the test endpoint) see the clear JSON in memory
- the startup backfill, the no-key refusal and the wrong-key check cover
it, for system=false rows only; built-in rows stay in clear for the seeds
API: GET /api/tools returns the header values, proxy and body of a custom
tool masked, with headers_set, proxy_set and body_set. On update (API and
update_custom_tool) the placeholder keeps the stored value, an explicit
value replaces it and "" clears it; on create (API and create_custom_tool)
a placeholder is refused. The test endpoint takes an optional key: the
saved tool's stored values replace the placeholders; the editor sends it.