Mailglass.Credo.NoPiiInResponseBody (Mailglass v1.4.3)

Copy Markdown View Source

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 (so err/error used as an inline body field — %{detail: err} — is caught, not only reason-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-encoded body carve-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.