# Adding an Exception in `ecr_scan/config.py`

Exceptions live in `theorchard/python-deployment-utils`, in `ecr_scan/config.py`. Locate the user's local copy of the repo, or clone it with `gh repo clone theorchard/python-deployment-utils` if they don't have one.

## Suppression mechanisms, narrowest to broadest — pick the narrowest one that fits

The file supports several ways to suppress a finding. Always prefer the narrowest scope that actually addresses the case, so an exception for one image doesn't accidentally blind the scanner to a real finding somewhere else:

| Mechanism | Scope | When to use |
|---|---|---|
| `EXCEPTIONS_BY_PARENT_IMAGE` | One parent image (Dockerfile) | The default choice — a vulnerability specific to what's installed in one image variant |
| `EXCEPTIONS_BY_OS` | All images on a given OS name+version | A finding tied to the base OS itself, affecting every image built on it |
| `FILE_PATHS_TO_IGNORE` | Any finding whose affected file path matches a regex | Known false-positive file patterns (e.g. lockfiles nested in vendored dependencies that don't reflect what's actually installed) — not really "exceptions" so much as scanner noise filters |

## Adding a per-parent-image exception (the common case)

`EXCEPTIONS_BY_PARENT_IMAGE` is a dict keyed by the parent image name — this must exactly match the Dockerfile's `LABEL parent-images=<name>` line. The value is a list of vulnerability ID strings. Add the new CVE with an inline comment naming the affected package and, briefly, why it's excepted:

```python
"kafka-connect77": [
    ...
    "CVE-2026-54512",  # pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.16.0 - requires upstream fix
],
```

Match the existing comment style in the file you're editing — most entries note the package coordinate (`pkg:...`) and sometimes a short reason (`no fix available`, `vulnerability in upstream`, `requires update in node upstream`). If the exception is meant to be temporary (waiting on a fix), say so in the comment and reference whatever ticket is tracking the follow-up, so someone reading the file later knows it isn't meant to be permanent.

**Vulnerability IDs aren't limited to `CVE-YYYY-NNNNN`.** The matching logic is a plain string containment check against whatever ID the scanner reports, so GitHub Security Advisory IDs (`GHSA-xxxx-xxxx-xxxx`, common for npm packages without an assigned CVE) and ad-hoc IDs (e.g. `IN-DISCONTINUED-001`, used elsewhere in this file for an EOL-but-still-supported base image) are valid too — use whatever ID the scan output actually shows.

**Keep each line you add under 120 characters.** `ecr_scan/.flake8` sets `max-line-length = 120`, and CI runs that lint on the PR — a line over the limit fails the build. There's no need to run the full lint suite as part of this skill (nothing else about a small, targeted exception addition is likely to trip other rules), but do check the length of the specific line you're adding, since a verbose inline comment is the one realistic way this particular edit goes over. For example, this comes to 185 characters — 65 over budget:

```python
"CVE-2026-54512",  # pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.16.0 - confirmed base image bump to cp-kafka-connect:7.7.10 does not fix this, requires upstream fix
```

If a comment doesn't fit, trim the narrative rather than the package coordinate — the coordinate (`pkg:...`) is what a future reader needs most to know what's actually being excepted; the story of *why* (which base image bump was tried, what it didn't fix) belongs in the PR description, not squeezed into the inline comment:

```python
"CVE-2026-54512",  # pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.16.0 - requires upstream fix
```

If several CVEs share the same package and the same reason, there's no need to repeat the full explanation on every line either — a short comment on the first, or a one-line comment above the group, is enough.

## How "blocking" and "grace period" are computed (only needed if you're verifying behavior, not just adding an exception)

In `ecr_scan/main.py`'s `get_failed_findings`:
- A finding with an available fix becomes **blocking** once it's been open longer than `DAYS_FOR_ERROR[severity]` days since the CVE's `published` date (defaults: `CRITICAL` = 7 days, `HIGH` = 14 days).
- A finding with **no** fix available becomes blocking after `DAYS_FOR_ERROR["NON_BLOCKING"]` days (default 90).
- "Grace Period" in the scan output is literally `threshold_days - days_elapsed_since_published` — a non-blocking finding with a small grace period is close to flipping to blocking, even though today's build didn't fail because of it.

This means an exception isn't the only lever — if a non-blocking finding is about to tip into blocking and a fix genuinely isn't ready yet, the exception is what buys time; it isn't a workaround for a bug in the threshold logic.

## After editing

Don't touch anything else in the file for this task — no reformatting, no reordering, no touching unrelated image entries even if they look inconsistent with each other. Follow the same "propose then confirm then apply minimal edits" flow as the Dockerfile changes; see the main SKILL.md.
