> ## Documentation Index
> Fetch the complete documentation index at: https://tashfiq.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Suppression Governance

> How Charter treats suppressions as governed exceptions instead of silent muting.

Suppressions are not a weakness in the model — they're the governance surface. Real repositories have false positives, accepted risk, and migration windows. Charter supports all of those. The question isn't whether to suppress; it's whether the suppression is intentional, documented, and bounded.

An unbounded suppression mechanism creates two problems fast: teams stop fixing issues because muting is easier than review, and later reviewers cannot tell which risks were understood versus forgotten. Charter's governance model is built to prevent both.

## Two suppression mechanisms

<Tabs>
  <Tab title="File suppression (.charter-suppress.yml)">
    `.charter-suppress.yml` is the repo-level governance file. It is the right choice when the finding has no precise line location, the target file cannot carry inline comments, or the team wants a central, reviewable suppression ledger.

    Generate an entry using `charter suppress`:

    ```bash theme={null}
    charter suppress AE-CC-001 --reason "Claude config lives in the infra repo" --expires 90d --approver "@tashfiqul"
    ```

    This writes a structured entry to `.charter-suppress.yml`:

    ```yaml theme={null}
    suppressions:
      - rule: AE-CC-001
        reason: "Claude config lives in the infra repo"
        expires: "2026-09-07"
        approver: "@tashfiqul"
    ```

    The `expires` field is stored as an absolute `YYYY-MM-DD` date. The CLI default is 90 days from today. Permanent waivers require `expires: permanent` and a mandatory `approver` field.

    Optional `path` field scopes the suppression to a specific file or glob, reducing the blast radius.
  </Tab>

  <Tab title="Inline directive (charter:ignore)">
    Inline suppressions use a `charter:ignore` directive placed in a comment on the **same line** as the triggering content. The comment leader can be `#`, `//`, or `<!-- -->`.

    ```yaml theme={null}
    MISE_GLOBAL_CONFIG_FILE: /shared/workspace/.mise.toml  # charter:ignore AE-ENV-001 reason="mise managed externally for this workspace"
    ```

    ```json theme={null}
    { "args": ["npx", "-y", "mcp-server-test@latest"] }  // charter:ignore AE-MCP-001 reason="vendored test fixture, not a real server config"
    ```

    Charter only reads a directive when it sits on a finding's own line. It does **not** scan the whole repo for comments that merely mention the syntax — documentation, READMEs, and examples are never misread as suppressions.

    Inline directives can also carry `expires=` and `approver=` fields — `expires=2026-12-31`, `expires=permanent`, or `approver="@handle"`. For a central, reviewable ledger across many findings, prefer `.charter-suppress.yml`.
  </Tab>
</Tabs>

## Three governance rules

Every scan audits suppressions through three dedicated rules:

| Rule                                        | Severity      | When it fires                                                                  |
| ------------------------------------------- | ------------- | ------------------------------------------------------------------------------ |
| [`AE-SUPPRESS-001`](/rules/AE-SUPPRESS-001) | Medium        | Any suppression entry is missing a `reason:` field                             |
| [`AE-SUPPRESS-002`](/rules/AE-SUPPRESS-002) | High          | A permanent suppression (`expires: permanent`) has no `approver:` field        |
| [`AE-SUPPRESS-003`](/rules/AE-SUPPRESS-003) | Informational | Suppression rate exceeds 30% of all findings (signal only, no score deduction) |

`AE-SUPPRESS-001` is the simplest rule in the model. Every suppression must explain itself. "False positive" alone is usually not enough. "Vendored test fixture" or "lives in a separate infra repo" is much better — it gives a future reviewer the context they would otherwise have to rediscover.

`AE-SUPPRESS-002` draws a hard line: permanent waivers require accountability. Without an `approver`, the underlying finding stays active and the accepted risk is treated as undocumented — regardless of how well-reasoned the suppression may be.

## What suppression does to the score

<Note>
  Suppressed findings are excluded from the base score calculation entirely. They are listed separately in `charter doctor` output under a "Suppressed" section and appear in the HTML report with full detail. In SARIF output, suppressed findings carry a `suppressed: true` annotation so downstream security tooling can distinguish them from active findings.
</Note>

This is why suppression governance matters so much: the mechanism directly changes the final score. Without an audit trail, a score of 95 could mean an excellent repo or a heavily-suppressed one. Charter makes both cases visible and distinguishable.

## Expiry behavior

<AccordionGroup>
  <Accordion title="What happens when a suppression expires">
    When a suppression's `expires` date passes, the finding re-surfaces as active on the next scan. The suppression entry itself remains in `.charter-suppress.yml` but is ignored — Charter treats expired dates as if the suppression were never there.

    The expired entry stays in the file deliberately. It provides an audit trail: this finding was suppressed, it expired on this date, and it was not re-suppressed. That history is valuable for security reviews.

    To re-suppress, either update the `expires` field to a new future date or add `expires: permanent` (with a required `approver` field). Do not delete the old entry; amend it.
  </Accordion>

  <Accordion title="Hand-authored entries vs CLI-generated entries">
    The CLI's `charter suppress` command always sets a default 90-day expiry and stores it as an absolute date. That is the recommended path.

    Hand-authored entries in `.charter-suppress.yml` with no `expires` field are honored without a default TTL. Charter does not infer a 90-day window from the file's last-modified time. If you write an entry by hand, the absence of `expires` means it has no expiry — which means it will also need an `approver` if it's effectively permanent, or it will trigger `AE-SUPPRESS-002`.

    The clean path: use `charter suppress` for all new suppressions and let the CLI handle the date arithmetic.
  </Accordion>
</AccordionGroup>

## When to suppress vs fix

Use suppression when the finding is a confirmed false positive in this specific repo, or when the risk is real but explicitly accepted with documented justification and a finite window.

Do **not** use suppression as a substitute for fixing the issue when:

* Charter can fix it safely → run `charter fix`
* The fix is straightforward and low-risk → fix it manually
* The finding is new and not yet understood → investigate first

A healthy suppression confirms the finding, records a meaningful reason, sets a bounded expiry, and adds an approver for any permanent waiver. `AE-SUPPRESS-001` and `AE-SUPPRESS-002` then keep the ledger honest on every scan — they are not optional. For the exact commands, see [Suppress a Finding](/docs/how-to/suppress-a-finding).

## Next steps

<CardGroup cols={2}>
  <Card title="Suppress a Finding" icon="ban" href="/docs/how-to/suppress-a-finding">
    The recipe for recording a governed suppression with a reason and expiry.
  </Card>

  <Card title="AE-SUPPRESS-001" icon="shield" href="/rules/AE-SUPPRESS-001">
    The reason-required rule, in full detail.
  </Card>

  <Card title="AE-SUPPRESS-002" icon="shield-lock" href="/rules/AE-SUPPRESS-002">
    Why permanent waivers must name an approver.
  </Card>

  <Card title="Scoring and Caps" icon="calculator" href="/docs/concepts/scoring-and-caps">
    How suppressed findings drop out of the base score.
  </Card>
</CardGroup>
