# 2026-07-14 -- the two pynetbox importers could NEVER have written upstream (WAF User-Agent)

A C2 (feed-the-carve-upstream) blocker, found while establishing C2's feasibility. Measured, not
inferred.

## The gap

The upstream apex `netbox.baldurkeep.com` sits behind a filter that **403s the default Python
User-Agent** (`references/platform-traps.md`). The same token works from curl -- it looks EXACTLY
like an auth failure and is not. **MEASURED 2026-07-14 from vcloud:** `curl` -> HTTP 200;
`urllib`/pynetbox -> HTTP 403.

The stdlib tools (`prod-draft-dump.py`, `sandbox-seed.py`, `d115-office-carve.py`) already send an
accepted UA (`curl/8.5.0`). But the two **pynetbox-based** importers --
`roles-aggregates-import.py` and `dc-dc-prefixes-import.py` -- built `pynetbox.api(url, token)` with
the **default** UA and never overrode it. So they would 403 against upstream: **they could only ever
have written to the sandbox (which has no WAF), never to the apex C2 must feed.** The gap was
invisible because every real run so far was against the sandbox.

## The fix

`get_nb()` in both importers now sets the accepted UA on the underlying requests session:

    nb.http_session.headers["User-Agent"] = "curl/8.5.0"

Same value and rationale as the stdlib tools -- pynetbox exposes its `requests.Session` as
`.http_session`, so one line covers every call it makes.

## Tests

- `tests/dc-dc-prefixes-import/` -> **86**: the fake now has an `.http_session` (mirroring
  pynetbox's real one), and the test asserts AT RUNTIME that `get_nb()` sets the UA to `curl/8.5.0`
  -- not just that the string appears in source.
- `tests/roles-aggregates-import/` -> **20**: grep assertion that `get_nb` sets the WAF-safe UA.

GAUNTLET ALL GREEN (58); repo-lint 0 fail.

## What this does and does NOT unblock

**Does:** removes the WAF 403 from the two pynetbox importers' upstream write path.

**Does NOT** (still open for C2):
- `pynetbox` is NOT installed on vcloud (measured). The two importers must run on a host that HAS it
  and can reach upstream -- `office1-netbox` has pynetbox 7.0.0. `d115-office-carve.py` is stdlib and
  runs anywhere.
- The sandbox re-verify (hardened `sandbox-fidelity-check.py`) needs the operator-held sandbox token.
- The upstream write itself is a PRODUCTION IPAM mutation and stays operator-gated.

## Revert

    git revert <this commit>

The single line in each `get_nb()` is the only functional change; the rest is tests. No NetBox was
written.
