---
title: Attack Vectors, Traced — N1 Threat Assessment
---

# Attack Vectors, Traced

Each vector is walked gate by gate through N1's real landscape and asked one question — with the controls that exist today, can you actually get in? Every verdict is backed by an artifact: a config read on the live production cluster, a `file:line` in the service code, or a Cloudflare control-plane record. **There is no unauthenticated path from the open internet to patient data.** Every realistic route runs through something N1 already trusts — a device, an account, or a signed-in person.

### Metadata

- **Source:** Landscape + live verification
- **Cluster:** n1-production · 191421493115 · us-east-2
- **CF snapshot:** state.json · 2026-06-08
- **Method:** claim → artifact, or it doesn't ship

## How each verdict was decided — claim → artifact

### Verdict Summary

- **4 Reachable** — A grounded path exists with today's controls. Worth real defensive weight.
- **7 Bounded** — A genuine weakness, but a verified control stands in the way. Named each time.
- **2 Needs-evidence** — The deciding artifact lives in a SaaS console, not in code. A question, not a finding.
- **3 Blocked** — The controls close the path. Kept to show the work.
- **3 Amplifiers** — Not a way in. Turns a small foothold into a large one. Matters after entry.

## 02 · A worked attack, start to finish — the most realistic chain

## Compromise a remote contractor's laptop, then try to reach patient data.

Entry layer: endpoints. Objective: PHI in Aurora / S3.

1. **Phish or infect an unmanaged laptop.** Remote contractors work on personal machines — no device management, no endpoint detection on N1 devices.Landscape §2
2. **Ride the trusted session.** The device holds an always-on WARP tunnel and a signed-in Google session; the attacker inherits an identity that already passed 2FA.§2, §4.2
3. **See only what that contractor can see.** A contractor account is not an admin — no AWS access (only three named accounts), not on the prod-deploy list. Blast radius = that one person.§4.5, §7A.1
4. **Reach staging data over WARP.** Any WARP-enrolled `@n1.healthcare` device can resolve `db.staging.n1` and read the staging database, read-only.§6.7
5. **Is staging real or synthetic? — open.** The whole severity of step 4 turns on this. No seed script, fixture, or prod→staging copy job exists in any repo, and `synthetic-medical-reports` is an OCR-training generator, not a staging seeder. The absence of a seeder leans toward organic (possibly real) data — but that's inference. Settles at the console: query staging Aurora for account emails / ages.
6. **Production PHI — stops here.** Production data is never exposed over WARP. Reaching it needs a production app session, AWS credentials (three people), or a pod foothold. The stolen laptop provides none.§6.7, §4.5, §17.1

**Verdict — Reachable but bounded.** The laptop is the realistic front door; it yields staging access and a trusted identity, but not production PHI by itself. The chain continues to the crown jewels only if (a) the laptop belongs to a privileged account, or (b) staging holds real records (step 5, open).

**A second realistic chain — the in-cluster ArgoCD bypass.** A foothold in any pod (via V-02, a supply-chain image) on the flat pod network (V-12) can reach `argocd-server:8080` directly and authenticate as the enabled local `admin` account — bypassing Cloudflare Access, Google SSO and ArgoCD RBAC — for full production deploy control. See V-07 Finding A.

## 03 · Every vector, traced

Reachable

### V-01: Unmanaged endpoint compromise — the keystone

**Verdict:** 🔴 Reachable | **Category:** `Endpoints`


No device management and no endpoint detection on N1 machines; access is gated by the session on the device, so stealing the session skips 2FA entirely. Stops at the privileges of the account on that laptop — a contractor device ≠ AWS ≠ prod deploy. The crown-jewel version requires a privileged-account device. §2, §4.5

### V-02: Path-traversal write in the document parser

**Verdict:** 🔴 Reachable | **Category:** `Application · ingestion`


◕ signed-in clinician uploads›
✕ filename unsanitized›
✕ joined into a write path

An authenticated upload reaches api-backend, which takes the filename unsanitized — `records.py:254` (`file.filename or "unknown"`) — and passes it to `parser-gate .../converters/office.py:191`: `input_path = temp_path / source_filename`. A name like `../../etc/cron.d/evil.docx` resolves outside the temp dir. It is a filesystem **write**, not command injection (no `shell=True` anywhere). Same flaw at `office.py:141`.

Bounded by container filesystem permissions — a non-root user with read-only rootfs largely neuters it (confirm in the Helm chart). No other reachable sink on the active path: mineru 2.7.6 emits its own image filenames; parser-sequential gets pre-parsed JSON; OcrRouter / pdf-converter are archived and off-path; helix-parser is a separate genetic pipeline.

Fix

Strip directory components: `temp_path / Path(source_filename).name` at `office.py:141` and `:191`.

### V-04: Dangling DNS takeover (plan.n1-research.com)

**Verdict:** 🔴 Reachable | **Category:** `Edge · DNS`


A live CNAME points at an external Cloudflare Pages project not in N1's account — a claimable dangling target on a trusted domain. Impact is phishing / brand abuse on `*.n1-research.com`, a stepping stone to V-01-style credential theft, not direct data access. The retired GKE tunnels behind other dead DNS are harmless 502 dead-ends; this CNAME is the only live takeover candidate. §6.4, §6.7

### V-20: The alarm shares fate with the building

**Verdict:** 🔴 Reachable | **Category:** `Detection integrity`


SigNoz sends alerts to Slack, and Slack federates to Google — so the channel that would warn about an identity compromise can be silenced by that same compromise. There is no out-of-band alert path independent of Google and Slack. A response gap, not an entry point. §13, §6.6

Bounded

### V-03: Indirect prompt injection via uploaded records

**Verdict:** 🟡 Bounded | **Category:** `— nuisanceAI pipeline`


CHR agents (forge-runner, on the AGNO framework) read patient data through MCP tools; instructions hidden in an uploaded document can influence the model the agent acts on. The deciding question — what those tools can reach — resolves against the attacker (V-10 below): the tools are read-only, single-patient, and have no attacker-controllable egress. A successful injection is therefore a **nuisance** (garbage in the generated report), not an exfiltration path.

Every tool reads only the one patient pinned in `WORK_DIR` (`patient-data/server.py:27`); nothing writes patient records; outbound calls go only to hardcoded NCBI / OpenTargets / N1 endpoints. No cross-patient read, no egress to an attacker.

### V-07: ArgoCD authentication

**Verdict:** 🟡 Bounded | **Category:** `— in-cluster bypassKubernetes · deploy control`


✓ CF Access app on prod host›
✓ @n1.healthcare SSO + deny-all›
✕ local admin bypasses it in-cluster

Production ArgoCD is gated by a Cloudflare Access application named **“ArgoCD”** whose `self_hosted_domains` include `argocd.n1-research.com` (CF snapshot, 2026-06-08). Policies: (1) allow `email_domain=n1.healthcare` + Google login; (2) **deny everyone**; (3) non-identity `any_valid_service_token`. The external door is shut. RBAC default is `role:readonly`; named deployers are jasper, umar.shah, arun, darpan.bafana. Three residuals from the live cluster:

Path A (green): internet → Cloudflare Access (Google SSO, deny-all) → tunnel → ArgoCD. Path B (red): a pod inside the flat network reaches `argocd-server:8080` directly and logs in as the enabled `admin` account — no CF Access, no SSO, full RBAC bypass.

⚠ Finding A — local admin account enabled (real)

Live `argocd-cm`: `admin.enabled: "true"`; `argocd-secret` carries `admin.password` + `admin.passwordMtime`. CF Access only fronts the external hostname; with no NetworkPolicies (V-12), any pod can reach `argocd-server:8080` and log in as `admin` (superuser), bypassing CF Access, SSO and RBAC — full prod deploy control. **Fix:** `admin.enabled: "false"` + a NetworkPolicy on the `argocd` namespace.

⚠ Finding B — service-token policy unscoped

Access policy #3 admits `any_valid_service_token` — any valid Cloudflare service token in the account (currently two SigNoz ingestion tokens) can reach prod ArgoCD non-interactively, around the human SSO gate. **Fix:** scope to a named token, or remove policy #3.

⚠ Finding C — jwt-proxy may be off the served path (drift)

Helm intends `argocd-server` → jwt-proxy `:8888` (it mints the `Cf-Access-Authenticated-User-Email` header Dex authproxy needs). Live, that Service targets raw `:8080`; the `:8888` path is on a separate `argocd-server-proxy` Service. If the dashboard-managed tunnel forwards to the raw service, SSO-by-header breaks and login falls back to the shared `admin`. `server.insecure:true` ⇒ in-cluster traffic is plaintext. **Confirm:** tunnel targets `argocd-server-proxy:8888`.

### V-11: Phishing a privileged Google identity

**Verdict:** 🟡 Bounded | **Category:** `Identity`


2FA is mandatory; onsite staff use hardware YubiKeys that defeat remote phishing outright. A remote user on a TOTP authenticator app can be relay-phished, but the blast radius is that account's reach; reaching the crown jewels needs one of the three AWS accounts, the hardest to phish. §4.3, §4.5

### V-13: GitHub account → production code

**Verdict:** 🟡 Bounded | **Category:** `Source & CI/CD`


Org-wide 2FA, branch protection (no force-push, ≥1 approval), and signed commits on every branch of every repo. A stolen session can't produce a signed commit without the key, nor merge without a second approver. The realistic version needs a full device compromise (V-01, for the key) plus a second account. §17.3

### V-15: LiteLLM key theft

**Verdict:** 🟡 Bounded | **Category:** `AI pipeline`


A LiteLLM key buys model access — cost abuse / denial-of-service — not a read of stored PHI; you'd already need the data to send it. Keys are Access-gated, self-service, 30-day expiry, invalidated on reissue. §11, §17.2

### V-18: Cloudflare account / tunnel compromise

**Verdict:** 🟡 Bounded | **Category:** `Edge · second trust root`


Catastrophic if it happens (owns ingress, the identity gate, TLS termination) but low-likelihood and dependent on Cloudflare's own security. Tunnel tokens live in Secrets Manager, needing a prior AWS/pod foothold to extract. How the Cloudflare dashboard account itself is protected (2FA / SSO / admin-seat count) is not confirmed here, though the `state.json` snapshot likely captures part of it. §6.7, §8.8

### V-19: Unpinned ingress image (cloudflared:latest)

**Verdict:** 🟡 Bounded | **Category:** `Supply chain`


The sole ingress component is pulled by mutable tag, as are a few first-party Go proxies and mineru — no digest pin, so a bad upstream image lands on restart. Realistic only via upstream registry compromise. Base/tool images are Docker Hardened Images and the prod path runs a Grype scan. The public R2 wheel bucket is a read-only public-exposure issue, not an injection path unless write access is separately compromised. §9.2, §9.4, §8.2, §7A.5, §6.5

Needs-evidence

### V-08: Rogue OAuth-app consent grant (Workspace)

**Verdict:** 🔵 Needs Evidence | **Category:** `Identity`


Whether Google Workspace restricts third-party OAuth app access (unconfigured = open; restricted = blocked). Not derivable from any repo or the cluster. **Would need:** the Workspace app-access / API-controls settings.

### V-09: Staging holds real PHI

**Verdict:** 🔵 Needs Evidence | **Category:** `Data · developer access`


Every enrolled `@n1.healthcare` device can read the staging DB and Redis, read-only — a broad internal surface (verified, §6.7). Whether those are real or synthetic records decides severity. No seed script, fixture, or prod→staging copy job exists in any repo, and `synthetic-medical-reports` is an OCR-training generator, not a staging seeder — the absence leans toward organic (possibly real) data, but that's inference. **Would need:** staging Aurora account emails / ages, or the `automated-testing` image entrypoint.

Blocked

### V-05: Broken object-level access (IDOR) on patient records

**Verdict:** 🟢 Blocked | **Category:** `Application · API`


Object-level authorization is enforced at the router mount, not scattered per handler. Every PHI router is `APIRouter(dependencies=[Depends(phi_scope_for(...))])` (`records.py:57,59`) → `authorize()` (`policy.py:555,624`), requiring the caller to be the data subject **or** hold a `completed=True` doctor-patient association (`auth_service_client.py:164`; pending associations don't count). A CI test fails the build if any route lacks a guard (`tests/unit/test_route_coverage.py`); a second targets the IDOR class (`tests/security/test_authz_full_audit.py`). No bare lookup-by-path-id without an ownership check was found.

### V-06: SSRF / injection in api-backend

**Verdict:** 🟢 Blocked | **Category:** `Application · API`


No user-controlled outbound-URL sink exists — every outbound URL is from environment config (`support.py:39`, `base.py:721`, `hydra_auth.py:106`). The one `requests.post(external_api_url,...)` is inside a docstring example (`exceptions.py:495`), not live code. SQL is SQLAlchemy ORM / parameterized; the two f-string SQL spots are a hardcoded isolation-level literal (`biomarkers_service.py:53`) and migration DDL. `subprocess` is test-only; no `os.system` / `eval` on user input.

### V-17: Direct attack on AWS infrastructure

**Verdict:** 🟢 Blocked | **Category:** `Cloud · network`


Nothing to scan or connect to in AWS — ingress is a Cloudflare Tunnel that dials outward; no public IPs, load balancers, or inbound ports. The EKS API server is public but IAM/OIDC-authenticated and held by three accounts. This concentrates risk onto the edge (V-18) and identity (V-11). §17.1, §8.7

Amplifiers

### V-12: Flat pod network — free lateral movement

**Verdict:** ⚠️ Threat Amplifier | **Category:** `Kubernetes`


Each cluster has exactly two NetworkPolicies, both for Prometheus scraping; pod-to-pod traffic is otherwise unrestricted (NETWORK-SERVICES NS-1). Not a way in — it turns one compromised pod into reach across the whole cluster, and it is what makes V-07's local-admin bypass reachable (any pod → `argocd-server:8080`). Staging and prod remain separate VPCs with no link.

### V-14: No human gate after a merge to main

**Verdict:** ⚠️ Threat Amplifier | **Category:** `Source & CI/CD`


The deploy-approval step exists but is commented out of the shared workflow, so a merge to main ships to production with no second checkpoint. It removes the catch after the merge gate, so the entire defense collapses onto the PR review. Mitigated by SHA-pinned actions, safe-chain, and a Grype scan on the prod path. §17.3, §7A.5

### V-16: Mass exfiltration via valid credentials

**Verdict:** ⚠️ Threat Amplifier | **Category:** `Data · the objective`


The objective, not an entry point — it only happens after another vector succeeds. S3 is fully public-access-blocked and KMS-encrypted at rest, so accidental exposure isn't the risk; abuse via valid credentials is. The missing control is detection of bulk reads and unusual egress through the single NAT. §8.4, §8.7, §17.4

## 04 · What to act on, in priority order

1. **Disable the ArgoCD local `admin` account** (`admin.enabled: "false"`) — closes the in-cluster bypass of Cloudflare Access. *(V-07 A)*
2. **Scope the ArgoCD Access service-token policy** to a named token, or drop policy #3. *(V-07 B)*
3. **Add NetworkPolicies** to the `argocd` and app namespaces — defangs V-07 A/B and is the V-12 fix.
4. **Confirm the tunnel targets `argocd-server-proxy:8888`**, not raw `:8080`; reconcile the service drift. *(V-07 C)*
5. **Patch the parser-gate path-traversal** at `office.py:141,191`. *(V-02)*
6. **Endpoints (V-01)** — no EDR; the keystone. Watch for session-token theft and unusual access from known-good devices.
7. **Data-read volume & NAT egress (V-16)** — bulk reads from Aurora / S3 are not monitored.
8. **Answer V-09 at the console** — real or synthetic PHI in staging.
9. **Out-of-band alerting (V-20)** — an alert path independent of Google and Slack.
10. **DNS hygiene (V-04)** — claim or remove the dangling `plan.n1-research.com` CNAME.

Open questions

| Question | Status |
| --- | --- |
| Does staging hold real or synthetic PHI? | Open — narrowed (no seeder in any repo) |
| How is the Cloudflare dashboard account protected? | Open — snapshot may close it |
| Does Google Workspace restrict third-party OAuth apps? | Open — Workspace console |
| Does the tunnel route ArgoCD through the jwt-proxy (`:8888`)? | Open — confirm tunnel ingress target |
Verdicts reflect controls as configured on **2026-06-11** (production cluster `191421493115`) and the Cloudflare snapshot of **2026-06-08**, not as continuously re-verified against live config. Items marked Needs-evidence / Open are questions to resolve, not confirmed exposures. Method: `n1-adversarial` — a claim ships only with the artifact that backs it.
