# `Mailglass.Credo.NoPiiInResponseBody`
[🔗](https://github.com/szTheory/mailglass/blob/v2.7.0/credo_checks/no_pii_in_response_body.ex#L1)

## Basics

> #### This check is disabled by default. {: .neutral}
>
> [Learn how to enable it](`e:credo:config_file.html#checks`) 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](`e:credo:check_params.html`) can be applied.

Parameters can be configured via the [`.credo.exs` config file](`e:credo:config_file.html`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
