Basics
This check is disabled by default.
Learn how to enable it via .credo.exs.
This check has a base priority of high and works with any version of Elixir.
Explanation
Do not place provider/error payloads in HTTP response bodies on the webhook + inbound-ingress egress surfaces.
"No PII on egress" must hold on the RESPONSE PATH, not only in logs and
telemetry. A persist failure typically returns an %Ecto.Changeset{} whose
changes carry recipient PII (subject/from/to/cc/bcc/reply_to/text/html
bodies). Interpolating that via inspect(reason) into a send_resp/
send_json/put_resp_body body leaks recipient email contents to the
provider on a transient error.
Return a static closed code instead — e.g.
send_json(conn, 500, %{status: "error", reason: "persist_failed"}) — and
route any debuggable detail to the telemetry stop-metadata as a PII-free
classified atom (the full-fidelity record already lives in the committed
tenant-scoped evidence row).
This check flags a response-body sink call when:
- any argument contains an
inspect(...)application, - any argument contains an
%Ecto.Changeset{...}literal, - any argument contains a bare error-like variable whose name contains
reason/changeset/err(soerr/errorused as an inline body field —%{detail: err}— is caught, not onlyreason-named vars), OR - the BODY-POSITION argument (the last positional arg of
send_resp/send_json/put_resp_body) is a BARE LOCAL VARIABLE that is not the documented-safe Jason-encodedbodycarve-out.
The bare-variable body-arg rule is what catches a payload assembled in a
prior assignment and passed bare to the sink — e.g.
payload = %{detail: inspect(changeset)}; send_json(conn, 500, payload).
A static map/binary literal is not a bare variable, so the legitimate
closed-code body does NOT trip it, and a body = Jason.encode!(payload)
binary is carved out so the JSON encoder helper stays clean.
Boundary (still out of scope)
The guard analyzes the sink-call arguments plus the body-position variable.
It does NOT perform full intra-function dataflow across multiple hops: a
payload assembled across several prior assignments and then transformed
before the sink call — so the body arg is neither a bare error-named
variable nor a directly-passed error/inspect/changeset term — can still
escape. True multi-hop intra-function dataflow tracking is out of scope;
keep the body-position arg a literal closed code or a Jason-encoded binary
and route detail to telemetry, and this guard holds.
Check-Specific Parameters
Use the following parameters to configure this check:
:included_path_prefixes
Only files in these path prefixes are linted (the webhook + ingress egress surfaces).
This parameter defaults to ["lib/mailglass/webhook/", "mailglass_inbound/lib/mailglass_inbound/ingress/"].
:response_sinks
Response-body call heads to inspect.
This parameter defaults to [:send_resp, :send_json, :put_resp_body].
:suspicious_fragments
Variable-name fragments treated as error/payload hints.
This parameter defaults to ["reason", "changeset", "err"].
General Parameters
Like with all checks, general params can be applied.
Parameters can be configured via the .credo.exs config file.