# Gatekeeper — Pre-Answer Security Questionnaire

**For:** Hospital and enterprise security reviewers conducting vendor risk assessments.
**About:** Gatekeeper is a drop-in reverse proxy that sits between a customer application and
OpenAI / Anthropic. Every proxied request runs through a fixed security pipeline — PII/PHI
redaction, jailbreak detection, threat-feed screening, policy enforcement, and audit logging —
before it is forwarded upstream.

This document pre-answers the questions that most commonly appear in healthcare vendor security
reviews. It is written to be handed directly to a reviewer.

## How to read this document

Every row uses the same four columns:

| Column | Meaning |
|---|---|
| **Question** | The reviewer's question. |
| **Answer** | The direct answer, drawn from the product's code and documentation. |
| **REAL / ROADMAP** | `REAL` = implemented and in effect today. `ROADMAP` = planned, not yet implemented. Where a decision is a business/legal one with no supporting artifact, the answer says **Pending Founder Decision** and the status is `ROADMAP`. |
| **Evidence** | The repository file(s) / section(s) that support the answer. |

**Principles honored in this document**

- Answers are grounded in repository evidence, not assumptions. Where evidence does not exist,
  the row says so and is marked `ROADMAP` rather than guessed.
- Unfinished work is never described as finished. The REAL vs ROADMAP line is exact.
- No evaluation metrics, benchmark numbers, latency figures, percentages, or other performance
  digits are included.
- No feature is invented.

**Two deployment models are referenced throughout.** They are determined solely by where the
single `DATABASE_URL` configuration points; the application code is identical in both:

- **Customer-deployed (self-hosted):** the customer runs the Gatekeeper service in their own
  environment and points it at **their own PostgreSQL**. All audit data — including any raw
  request text — stays within the customer's boundary.
- **Hosted (Gatekeeper-operated):** the service points at Gatekeeper's managed PostgreSQL (Neon).
  Audit data is stored in Gatekeeper's database.

---

## Section 1 — PHI Handling

| Question | Answer | REAL / ROADMAP | Evidence |
|---|---|---|---|
| What data enters Gatekeeper? | An OpenAI/Anthropic-shaped chat request on `POST /proxy/openai` or `POST /proxy/anthropic`, or raw text on the detect-only `POST /scan`. All incoming message contents are combined into a single working text for detection. | REAL | `backend_documentation.md` §6; `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.1; `api/main.py` (`full_text` assembly) |
| Are governance headers required for a request to be processed? | Yes. `X-Workflow-Tag` and `X-Actor-ID` are required; a request missing either is rejected before any scan or audit write. There is no silent "unclassified/anonymous" fallback at the entry point. | REAL | `api/app/request_context.py`; `backend_documentation.md` ("POST /scan" and proxy headers) |
| What gets redacted? | Detected PII/PHI is replaced with `[REDACTED_<TYPE>]` placeholders before any upstream forward, using Microsoft Presidio (spaCy small English model) plus custom pattern recognizers (SSN, credit card, API key, passport) plus a credentials/secrets layer. | REAL | `api/app/pii_detector.py`; `backend_documentation.md` §7–§8; `CLAUDE.md` ("PII detection") |
| Is redaction applied before the request is forwarded upstream? | Yes. On the proxy paths, the text forwarded to the model provider is the post-redaction text; raw content is not forwarded on the proxy path. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.1, §1.3; `api/main.py` (`scan_messages` → forward) |
| What is stored, and where? | Two text columns on the `audit_logs` table (one row per request): `original_text` (verbatim request text, before redaction) and `cleaned_text` (post-redaction text, exactly what was forwarded upstream). The finer-grained redaction log stores lengths only, never the text. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.1–§1.2; `api/app/database.py` (`audit_logs` schema, `log_audit`) |
| Does `original_text` ever leave the database or go to a third party? | No. `original_text` is not sent to any third party by Gatekeeper; it is stored only in the audit database (the customer's own under customer-deployed) and used for the audit trail. (The one nuance concerning the internal jailbreak judge is answered separately below.) | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.3 |
| Is raw request text included in exported evidence packets? | No. The evidence packet is a read-only projection of an audit row; it emits only entity **types and counts**, the decision, policy version, timestamps, and exception events. It never reads or emits `original_text` / `cleaned_text` — no PHI values appear in a packet. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.3; `docs/README_evidence_packet.md`; `api/app/evidence_formatter.py` |
| What reaches the model providers (OpenAI / Anthropic) on the proxy path? | Only the redacted text is forwarded to the provider on `POST /proxy/openai` / `POST /proxy/anthropic`. The customer's own provider API key is passed through in memory and is never stored. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.3; `GROUND_TRUTH.md` §3 |
| What reaches the internal jailbreak judge? | The internal judge (an OpenAI `gpt-4o-mini` classifier) runs **before** PII redaction, so on the minority of prompts routed to it, it currently receives the **raw, pre-redaction** prompt text — which may contain PHI — sent to OpenAI. Redacting the judge's input is a documented, not-yet-implemented follow-up (per F3b), deferred until the change freeze lifts. | REAL (raw text today) / ROADMAP (judge-input redaction) | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.3; `POST_FREEZE_BACKEND_QUEUE.md` item 5 (F3b); `api/main.py:896,921,1110`; `api/app/jailbreak_detector.py:520,390` |
| Hosted vs customer deployment — where does PHI physically live? | It depends only on where `DATABASE_URL` points. **Customer-deployed:** all audit rows (including raw text) live in the customer's own PostgreSQL and never leave the customer boundary for storage. **Hosted:** rows live in Gatekeeper's managed PostgreSQL (Neon). The self-hosted/Azure *packaging* of this claim is still a roadmap item, though the backend already reads any PostgreSQL supplied. | REAL (backend is deployment-agnostic) / ROADMAP (self-hosted packaging claim) | `POST_FREEZE_BACKEND_QUEUE.md` §2 (customer-deployed vs hosted residency); `api/app/database.py` (`DATABASE_URL`); `api/.env.example` |
| How long is stored text retained? | Indefinitely today. The audit trail is append-only; there is no automatic expiry, TTL, or purge job. A configurable retention window with scheduled purge is planned but not implemented. | REAL (no retention limit today) / ROADMAP (retention window) | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.4; `POST_FREEZE_BACKEND_QUEUE.md` item 2 |
| Can raw-text storage be turned off by configuration? | Not today. There is no configuration flag that disables storing `original_text` / `cleaned_text`; when a database is configured, both are written. A flag to disable raw-text storage (defaulting off for HIPAA workflows) is planned but not implemented. | REAL (not configurable today) / ROADMAP (`STORE_ORIGINAL_TEXT`) | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.5; `POST_FREEZE_BACKEND_QUEUE.md` item 1 |

---

## Section 2 — Security Controls

| Question | Answer | REAL / ROADMAP | Evidence |
|---|---|---|---|
| How are proxy requests authenticated? | Each proxy request presents an `X-Gatekeeper-Key` header, which is resolved to a customer app record; an invalid key is rejected. | REAL | `SITE_CLAIM_VERIFICATION.md` Q2; `api/app/database.py` (`get_app_by_key`) |
| Is `X-Gatekeeper-Key` strictly mandatory? | It provides per-app attribution and is actively validated, but it is not strictly mandatory: a request with no key falls back to a shared `default` app rather than being rejected. This fallback should be disclosed to reviewers. | REAL (with disclosed fallback) | `SITE_CLAIM_VERIFICATION.md` Q2; `D:\gatekeeper_frontend\src\components\landing\security-section.tsx` |
| Is data encrypted in transit? | Yes. All connections use HTTPS/TLS — client to Gatekeeper, Gatekeeper to the model providers, and Gatekeeper to the database. Responses also carry HTTP hardening headers including HSTS. | REAL | `D:\gatekeeper_frontend\src\components\landing\security-page-view.tsx`; `customer_guide.md` ("Security Headers"); `GROUND_TRUTH.md` §1 |
| Is data encrypted at rest? | Slack webhook URLs are encrypted at rest (Fernet symmetric encryption). The audit text columns are not application-encrypted today (they rely on provider disk encryption); column-level encryption reusing the existing pattern is planned. | REAL (Slack webhooks) / ROADMAP (audit text columns) | `backend_documentation.md` ("Slack Alerts / Encryption"); `POST_FREEZE_BACKEND_QUEUE.md` item 3 |
| Is there rate limiting? | Yes. A per-app limiter (backed by the database) protects the proxy endpoints, and a separate per-IP limiter protects the public scan endpoint over a rolling window, returning a friendly over-limit response. A frontend edge limiter additionally guards the public demo path. | REAL | `docs/rate_limit.md`; `backend_documentation.md` ("Rate Limiting — two independent mechanisms"); `api/app/demo_rate_limit.py` |
| What is the access model for audit data? | The customer dashboard exposes audit logs and is authentication-gated; the read-only evidence export requires an actor identity header. There is no role-based access control / admin-role model yet — export reuses the actor identity rather than a role system. | REAL (auth-gated access) / ROADMAP (RBAC) | `D:\gatekeeper_frontend\src\components\landing\security-page-view.tsx`; `SITE_CLAIM_VERIFICATION.md` Q6; `api/main.py:659` ("no role-based admin system, so the export reuses the [actor id]") |
| What are the governance headers and what do they do? | Every request must carry `X-Workflow-Tag` (the workflow/use-case) and `X-Actor-ID` (the acting identity). They are validated at the entry point, drive the enforced policy, and are recorded on every audit row for attribution. | REAL | `api/app/request_context.py`; `backend_documentation.md` (proxy/scan headers) |
| What is the `UNGOVERNED_CALL` control? | When sensitive data is handled without complete governance metadata, Gatekeeper does not silently proceed: it records an explicit, append-only `UNGOVERNED_CALL` exception listing exactly which governance fields were missing. It is emitted only from real request state and surfaces in the evidence packet and export aggregates. | REAL | `api/app/governance.py`; `D:\gatekeeper_frontend\src\components\landing\faq.tsx`; `docs/README_evidence_packet.md` |
| What is the detection methodology? | PII/PHI detection uses Microsoft Presidio (spaCy small English model) plus custom regex recognizers plus a credentials/secrets layer; a false-positive filter suppresses known mislabels. No transformer/RoBERTa model is deployed. Jailbreak detection is a deterministic regex fast path with heuristic escalation, backstopped by a `gpt-4o-mini` judge used only as a false-positive check on ambiguous prompts. | REAL | `CLAUDE.md` ("PII detection" / "Jailbreak detection"); `SITE_CLAIM_VERIFICATION.md` Q8; `api/app/pii_detector.py`; `api/app/jailbreak_detector.py` |
| Are customer provider (OpenAI/Anthropic) API keys stored? | No. Upstream provider keys are passed through in memory only and are never persisted. | REAL | `GROUND_TRUTH.md` §3; `backend_documentation.md` ("Important Notes"); `D:\gatekeeper_frontend\src\components\landing\security-section.tsx` |
| Are security response headers applied? | Yes — every response includes content-type, framing, XSS, HSTS, and referrer-policy hardening headers. Audit/decision data is only ever placed in the response body, never in headers. | REAL | `customer_guide.md` ("Security Headers"); `GROUND_TRUTH.md` §1 |
| Is inbound threat-intelligence screening applied? | Yes — each request is checked against an in-memory threat-feed cache (known malicious domains, files, and patterns) refreshed on a background loop. Caveat: if the feed is unavailable the cache can be empty, in which case this specific check has nothing to match and passes silently. | REAL (screening wired) / caveat: feed liveness is environment-dependent | `GROUND_TRUTH.md` §5; `backend_documentation.md` ("IOC Detector"); `api/app/ioc_detector.py` |
| Can customers enforce their own policy rules? | Yes — customers can register an external policy file (GitOps); Gatekeeper re-fetches it periodically and applies redact/block rules without a redeploy. | REAL | `backend_documentation.md` ("Policy-as-Code"); `customer_guide.md` ("Custom Policy Rules (GitOps)"); `api/app/policy_as_code.py` |

---

## Section 3 — Compliance & BAA

The consistent framing across the product is **"evidence, not proof of formal compliance status"**: the per-call
control mapping is evidence a reviewer evaluates, not an attestation about the platform or the customer.

| Question | Answer | REAL / ROADMAP | Evidence |
|---|---|---|---|
| Does Gatekeeper make us HIPAA compliant? | No product does. Gatekeeper provides a technical evidence layer for AI calls — per-call records mapped to specific HIPAA and SOC 2 controls — showing what was detected, redacted, or blocked, and under which policy. It is evidence for a compliance program, not a substitute for one (policies, training, BAAs, and risk analysis remain the customer's program). | REAL (stated posture) | `D:\gatekeeper_frontend\src\components\landing\faq.tsx`; `D:\gatekeeper_frontend\src\components\landing\security-section.tsx` |
| Is there a HIPAA audit-controls mapping (164.312(b))? | Yes. Each evidence record maps the audit-controls requirement to an "Audit trail" control with a satisfied/unavailable status and an audit-log identifier and timestamp — one durable audit row per request. | REAL | `docs/README_evidence_packet.md`; `api/app/evidence_formatter.py`; `api/app/database.py` |
| What is the SOC 2 status? | SOC 2 Type I & II engagement in process with Johanson Group LLP. The product provides SOC 2 control mappings and HIPAA-ready policy packs, framed as evidence, not proof of formal compliance status. There is no SOC 2 certificate in the repository. | ROADMAP (engagement in process) | `D:\gatekeeper_frontend\src\components\landing\marketing-footer.tsx`; `D:\gatekeeper_frontend\src\components\landing\security-page-view.tsx`; `D:\gatekeeper_frontend\src\components\landing\evidence-packet.tsx` |
| Is there a CC6.1 (logical access / redaction) mapping? | Yes. Each record maps CC6.1 to a "Redaction proof" control with a satisfied/not-applicable status and a redaction detail expressed as entity types and counts only — no PHI values. | REAL | `docs/README_evidence_packet.md`; `api/app/evidence_formatter.py`; `SITE_CLAIM_VERIFICATION.md` Q4 |
| Is there a CC6.3 (policy/version lineage) mapping? | Yes. Each record maps CC6.3 to a "Policy version lineage" control carrying the policy version, which is stamped with a policy Git-SHA lineage suffix. | REAL | `docs/README_evidence_packet.md`; `api/main.py` (`build_policy_version`); `SW_B_CLAIM10_VERIFICATION.md` |
| Is there a CC6.8 (unauthorized/malicious activity) mapping? | Yes. Each record maps CC6.8 to a "Jailbreak block" control — when a jailbreak is detected it is blocked before the model is called, and the block is recorded as satisfied, not merely logged. | REAL | `docs/README_evidence_packet.md`; `D:\gatekeeper_frontend\src\components\landing\evidence-packet.tsx` |
| Is there a complete audit trail? | Yes — one row per request in the audit table, written on both allowed and blocked paths, covering detection, decision, block reason, policy version, workflow tag, actor identity, and exception events. The trail is append-only in practice (there is no delete path for audit rows). Cryptographic tamper-proofing is not implemented. | REAL (append-only audit trail) / ROADMAP (cryptographic immutability) | `backend_documentation.md` §5; `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.4; `api/app/database.py` |
| What exactly does the compliance mapping certify? | Nothing is "certified." The mapping documents how a specific request was handled and maps that record to relevant control evidence; it does not make Gatekeeper or the customer SOC 2 certified, nor by itself make an application HIPAA compliant. | REAL ("evidence, not proof of formal compliance status") | `D:\gatekeeper_frontend\src\components\landing\evidence-packet.tsx` ("Mapping is evidence, not proof of formal compliance status"); `D:\gatekeeper_frontend\src\components\landing\security-section.tsx` |
| What is Gatekeeper's BAA readiness / stance? | There is no Business Associate Agreement, Data Processing Agreement, or subprocessor register in the repository. This is a legal/commercial decision with no supporting artifact to cite. Deployment context: under customer-deployed, PHI in audit rows never leaves the customer boundary, which may reduce BAA scope — but the BAA stance itself requires a founder decision. | ROADMAP — **Pending Founder Decision** | No BAA/DPA artifact present in repository; deployment-boundary context: `POST_FREEZE_BACKEND_QUEUE.md` §2 (customer-deployed vs hosted residency) |
| Is customer data used for AI training? | Gatekeeper redacts PII before every upstream call and sends no explicit "do not train" instruction; the guarantee otherwise relies on the providers' default API policies. The public "never used for AI training" statement is an unverified placeholder pending confirmation of the provider account settings. | ROADMAP — **Pending Founder Decision** (account-setting confirmation) | `GROUND_TRUTH.md` §3; `D:\gatekeeper_frontend\src\components\landing\security-page-view.tsx` (commented placeholder) |

---

## Section 4 — Incident Response & Availability

| Question | Answer | REAL / ROADMAP | Evidence |
|---|---|---|---|
| How does the jailbreak judge behave on failure (fail-open)? | The judge is fail-open. Any judge failure — timeout, network/API error, missing key, malformed response, or any exception — is caught and returns a "no jailbreak" verdict, so a failed judge never blocks a request. This is policy-independent (HIPAA behaves the same as SOC 2). The deterministic fast-path block for obvious jailbreaks is unaffected. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §2.1–§2.4; `api/app/jailbreak_detector.py` (judge exception handler) |
| Is a fail-open event silently swallowed, or is it recorded? | It is not silent. There is a three-layer trail: a loud application-log alert line; a structured per-request detector log entry; and a persisted "judge fallback" exception event on that request's own audit row, surfaced in the evidence packet. An operator can find historical fail-open events by querying the audit trail. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §2.5; `api/app/jailbreak_detector.py`; `api/main.py` (exception event builder); `api/app/database.py` (exceptions log) |
| Is there real-time alerting when a request is blocked? | Yes — a Slack alert fires on any blocked request as a fire-and-forget task (it never slows the proxy). Alerts are rate-limited and contain only metadata (app name, time, block reason); the original prompt content is never included. Webhook URLs are encrypted at rest and returned masked. | REAL | `backend_documentation.md` ("Slack Alerts"); `customer_guide.md` ("Slack Alerts"); `api/app/slack_alerts.py` |
| Is there a fail-closed (block-on-error) mode for high-assurance policies? | Not today — behavior is fail-open only. There is no configuration flag to make the judge fail closed. A scoped, opt-in fail-closed mode for high-assurance workflows is designed and its building blocks exist, but it is not wired; enabling it requires a decision on which workflows must fail closed. | ROADMAP + **Pending Founder Decision** (which workflows) | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §2.6–§2.7; `POST_FREEZE_BACKEND_QUEUE.md` item 4; `api/app/governance.py` |
| What is the rollback posture? | The semantic jailbreak escalation path can be disabled instantly via an environment flag with no redeploy, and its sensitivity is tunable without redeploy. Separately, an active change freeze protects reviewer-facing artifacts from silent deployment drift, with a documented exception/approval and re-verification process. | REAL | `api/.env.example` (`B2_ENABLED`); `D:\gatekeeper_frontend\docs\CHANGE_FREEZE.md` |
| Is the service resilient to dependency outages? | Yes, degradation is non-blocking: threat-feed failures fall back to cached data, the judge fails open on a provider outage, and the demo limiter fails open in memory. Caveat: an empty threat-feed cache means that specific check silently matches nothing, and there is no alert when the feed fails to refresh. | REAL (non-blocking degradation) / caveat: no alert on feed-refresh failure | `backend_documentation.md` ("Important Notes"); `GROUND_TRUTH.md` §5; `api/app/ioc_detector.py` |
| What is the breach-notification statement / timeline? | There is no breach-notification policy, timeline, or incident-response runbook in the repository. This is a legal/operational commitment requiring a founder decision, with no supporting artifact to cite. | ROADMAP — **Pending Founder Decision** | No breach-notification / IR-policy artifact present in repository |
| What is the uptime / availability commitment (SLA)? | There is no SLA, uptime commitment, or availability target documented in the repository. This requires a founder/product decision. | ROADMAP — **Pending Founder Decision** | No SLA / uptime-commitment artifact present in repository |

---

## Section 5 — Subprocessors / Fourth Parties

There is no formal subprocessor register or DPA in the repository (a formal register is a
**Pending Founder Decision**). The rows below describe each provider's **purpose** and the
**data it receives**, reconstructed strictly from documented technical data flows. The stored
`original_text` audit column **is never shared with any subprocessor**; the single nuance is the
OpenAI internal-judge path, stated explicitly in that row.

| Question | Answer (purpose · data received · original_text) | REAL / ROADMAP | Evidence |
|---|---|---|---|
| **OpenAI** — purpose and data received? | **Purpose:** model provider for `/proxy/openai`, and the internal jailbreak judge / block explainer (`gpt-4o-mini`). **Data received:** redacted request text on the proxy forward (with the customer's key, passed through in memory); and, on the judge/explainer path, a raw pre-redaction prompt snippet using Gatekeeper's own key. **original_text:** the stored `original_text` audit column is never exported or shared with OpenAI; however, raw pre-redaction prompt text does reach OpenAI on the judge path today (judge-input redaction is a roadmap item). | REAL (data flows) / ROADMAP (judge-input redaction) | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.3; `GROUND_TRUTH.md` §2–§3; `POST_FREEZE_BACKEND_QUEUE.md` item 5; `api/app/jailbreak_detector.py` |
| **Anthropic** — purpose and data received? | **Purpose:** model provider for `/proxy/anthropic`. **Data received:** redacted request text on the proxy forward, with the customer's key passed through in memory. Anthropic is not used for detection, the judge, or the explainer. **original_text:** never shared with Anthropic — only redacted text is forwarded, and the stored audit columns are never exported. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.3; `GROUND_TRUTH.md` §2 |
| **Neon** (PostgreSQL) — purpose and data received? | **Purpose:** managed PostgreSQL for the audit trail under the hosted model. **Data received:** the audit rows (including raw text) and governance data — under the hosted model only. Under customer-deployed, Neon is not used and the customer's own PostgreSQL holds the data instead. **original_text:** stored in the audit database by design; it is never forwarded onward from there to any other party. Customer provider API keys are never stored. | REAL | `docs/architecture/sw-d-storage-and-judge-fail-open.md` §1.2; `POST_FREEZE_BACKEND_QUEUE.md` §2 (customer-deployed vs hosted residency); `backend_documentation.md` §3, §10; `api/.env.example` |
| **Railway** — purpose and data received? | **Purpose:** application hosting/compute for the Gatekeeper service under the hosted model; terminates TLS. **Data received:** request text passes through its compute transiently in memory during processing; durable storage is the database, not the Railway app process. Under customer-deployed, Railway is not in the path. **original_text:** not persisted by the Railway app process; the stored audit column is never shared onward. | REAL | `backend_documentation.md` §10; `D:\gatekeeper_frontend\src\components\landing\security-page-view.tsx`; `POST_FREEZE_BACKEND_QUEUE.md` §2 (customer-deployed vs hosted residency); `docs/rate_limit.md` |
| **Vercel** — purpose and data received? | **Purpose:** hosting for the marketing site, dashboard, live sample-evidence packet, and the public demo; terminates TLS. **Data received:** user-entered demo text submitted through the public demo route, which forwards to the backend. **original_text:** the production audit database does not live on Vercel; demos use synthetic data by policy, and the stored audit column is never shared. | REAL | `docs/rate_limit.md`; `evidence-appendix/MANIFEST.md`; `D:\gatekeeper_frontend\src\components\landing\faq.tsx` |
| **Upstash Redis** — purpose and data received? | **Purpose:** optional edge rate-limit counters used by the demo limiter when configured. **Data received:** only IP-derived rate-limit keys/counters; it fails open (no enforcement) when unconfigured. **original_text:** never receives request content, prompt text, PHI, or the audit database. | REAL (optional; fails open when unconfigured) | `docs/rate_limit.md` ("Frontend edge limiter") |
| **AIDepShield** (threat-intel feed) — purpose and data received? | **Purpose:** external inbound IOC/threat-intelligence feed consumed read-only. **Data received:** nothing from the customer — the data flow is inbound only (Gatekeeper pulls threat data from it). **original_text:** never shared — Gatekeeper sends this feed no customer data. | REAL (read-only consumption) | `GROUND_TRUTH.md` §5; `backend_documentation.md` ("IOC Detector"); `api/app/ioc_detector.py` |

---

## Reviewer notes — open decisions and evidence

**Items requiring a founder / product decision (no supporting artifact exists yet):**

- **BAA / DPA / formal subprocessor register** (Section 3, Section 5).
- **Breach-notification policy and timeline** (Section 4).
- **Uptime / availability SLA** (Section 4).
- **Which workflows must fail closed** (Section 4) — depends on the fail-closed roadmap item.
- **"Data never used for AI training"** confirmation of provider account settings (Section 3).
- **Whether to disclose the `X-Gatekeeper-Key` default-app fallback** externally (Section 2).

**Reviewer note — sprint wording vs. implementation on judge input (important).** The TP-1
sprint text describes what reaches the model providers as *"cleaned text only — including through
our own judge, per F3b."* The questionnaire deliberately does **not** adopt that wording for the
internal judge, because it is contradicted by both the code and by F3b's own documentation:

- **The judge receives raw, pre-redaction text today.** Jailbreak detection runs before PII
  redaction: raw `full_text` is built at `api/main.py:896`, passed to `detect_jailbreak(full_text, …)`
  at `api/main.py:921`, and PII redaction (`scan_messages`) only runs afterward at `api/main.py:1110`.
  Inside the detector, that same raw `text` is passed to the judge `slow_check` at
  `api/app/jailbreak_detector.py:520` and embedded verbatim in the judge prompt at `:390`.
- **F3b does not supersede this — F3b documents it.** F3b is documentation-only (commit `7f1d5f0`,
  one file changed). `POST_FREEZE_BACKEND_QUEUE.md` item 5 states: *"F3b confirmed raw/original text
  currently reaches the judge"* and *"Current state: **NOT implemented** — the judge receives raw
  `full_text`."* Judge-input redaction is a **post-freeze follow-up**, not a shipped behavior.
- **Resolution.** The implementation is the authoritative source of truth for TP-1. The relevant
  rows (Section 1 "What reaches the internal jailbreak judge?" and the Section 5 OpenAI row) state
  the raw-text behavior as `REAL` today and mark judge-input redaction as `ROADMAP`. Adopting the
  sprint's "cleaned text through the judge" phrasing would be an unsupported claim; the discrepancy
  is flagged here so the sprint line can be corrected to match F3b and the code.

**Evidence note (deployment model & architecture doc).** The customer-deployed-vs-hosted residency
model is evidenced by `POST_FREEZE_BACKEND_QUEUE.md` §2 and the deployment-agnostic `DATABASE_URL`
handling in `api/app/database.py` / `api/.env.example` — all present in the working tree. The
broader one-page `architecture_brief.md` (which also covers §8a) is tracked at git HEAD but is
currently deleted from the working tree, so it is **no longer cited** in this document; every
remaining Evidence reference resolves to a file present in the working tree.

**Freeze honesty:** this document is documentation-only and changes no application code. Where a
capability is planned but not implemented — retention window, disable-raw-storage flag,
at-rest encryption of audit text, RBAC, fail-closed mode, judge-input redaction, cryptographic
audit immutability, completion of the SOC 2 Type I & II engagement in process — it
is marked `ROADMAP` and never described as live.
