Repository navigation
CARD-P2-01 S4: rag_api producer governed-host allowlist (G1-A) + LLM/IMDS paid-call guard (G1-F) - #115
Merged
Conversation
…ined link_state, typed failures
B. extraction.degraded (CARD-DEGRADED-EXTRACTION-SIGNAL). `locator_kind: "none"` meant both
"no addressable units" and "the DOCX structured walk failed and fell back to a flat read".
SafeDocxLoader now records WHY a structured walk returned None and stamps
`extraction_degraded` on the flat Documents ONLY for a failure (docx_block_unrepresentable,
docx_structure_unreadable) -- never for the legitimate no-body case. The receipt rolls it up
into `extraction.degraded {reason, scope, lost, units_affected}` and forces `status: partial`;
the stamp rides every stored chunk (cmetadata). The #90 limitation test is converted from
pinning the collision to asserting the distinction, with the no-body NEGATIVE binding.
C. QUARANTINED on the read side. Every /query* hit carries a top-level
`link_state: present | quarantined | none`, derived from the stored-link backfill card's own
marker (`quarantined_link`, CARD-F-EMBED-LINK-BACKFILL MUTATION). No second marker; metadata
still goes out exactly as stored.
D. FAILED typing. The five 400/503 intake failures were distinguishable only by prose.
`X-Failure-Attribution` (the same token the log carries) and `X-Error-Reference` now ride
the response; detail stays a string, no status code changes. client_safe_error carries
X-Error-Reference; the upload-path refusal carries request:invalid_path. /text returns the
extraction receipt (empty stays a 200 there). 422 verdict/empty/link/validation shapes pinned.
E. Never-reads-complete regression over every combination of escalation, read bound,
incomplete image coverage and degraded.
Red-first vs afd44b9 (base app mounted, new tests): 45 failed / 25 passed, 0 errors.
Green: 70/70 targeted. Full suite --network none, files01-ocr-test:wip: branch 40F/959P/141S/28E
vs base 40F/890P/143S/28E, identical failure sets (NLTK tagger absent in the image).
Six source mutations each reddened the tests that guard them. SYNTHETIC fixtures only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ATION decision (2))
A quarantined marker must never store or serve the raw refused URL -- it can carry
credentials, a token, or a javascript:/data: payload, and untrustworthiness is why it
was quarantined. `_redact_quarantined_link` reduces it to {scheme, host, refusal_reason,
sha256}: host is urlparse.hostname (no userinfo/port), sha256 is a one-way digest for
correlation. `_redacted_metadata` replaces a raw `quarantined_link` string on emit in
`_on_the_wire`, so the raw never leaves the process. This supersedes serving the raw
`quarantined_link` verbatim (43bb826).
retrieved == stored still holds: the redaction-aware backfill stores the object at rest
(CARD-F-EMBED-LINK-BACKFILL MUTATION updated 2026-09-25), and the wire serves it
unchanged; a legacy raw-at-rest row is redacted on emit. link_state (present|quarantined
|none) is unchanged -- derived from the PRESENCE of the marker, object or string.
Storage side: rag_api ingestion writes no quarantine marker (a bad `link` is refused at
write time, 422, before any store), so the only at-rest writer is the backfill; the
read-side transform covers legacy raw rows regardless.
Red-first vs 43bb826 (its app mounted, new tests): 5 failed / 28 passed -- the raw URL
rode the wire and there was no redaction helper. Green on branch: 83 passed (full S1
set). Four one-line mutations each reddened the guarding tests (redaction no-op 4F;
sha256 constant 3F; host=netloc leaks creds 4F; link_state ignores marker 17F);
worktree byte-identical, no leftover containers. SYNTHETIC, --network none.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…IMDS paid-call guard (G1-F) G1-A (producer-side governed-HOST allowlist): after the existing https scheme guard, _reject_ungoverned_link also checks the link host against RAG_GOVERNED_LINK_HOSTS (read once at import; comma/space-separated; exact host OR dot-prefixed subdomain suffix; hostname-only via urlparse.hostname, which drops userinfo+port). A scheme-valid link on a non-allowlisted host is refused with the typed reason link_host_not_allowed (same 422 envelope as link_scheme_not_allowed; the raw link is never echoed -- only the redacted host, reason, allowed_hosts). CONSERVATIVE DEFAULT: unset/empty allowlist => host check DISABLED, scheme-only behaviour preserved with zero new rejections (the allowlist VALUE is OPEN FOR RICHARD, card 11). No host value invented. G1-F (LLM/IMDS paid-call test guard, N5 twin of N1): conftest autouse _block_the_real_llm_provider replaces the boto3 bedrock-runtime client's socket-opening step (converse/converse_stream/ invoke_model) on every live holder (app.config.llm and document_routes.llm); AWS_EC2_METADATA_DISABLED + dummy creds are set before app.config imports so the import-time boto3 session makes no IMDS probe. Pins SUM_UP_KNOWLEDGE_FILES off and proves flipping it still cannot reach a provider. Tests (red-first, proven under --network none in the shipped-runtime image): tests/test_embed_link_host_allowlist.py, tests/test_llm_provider_is_blocked_in_tests.py. Existing scheme-guard and embeddings/rerank paid-call guards unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dboichuk-2
marked this pull request as ready for review
September 25, 2026 10:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CARD-P2-01 S4 — rag_api producer governed-host allowlist (G1-A) + LLM/IMDS paid-call guard (G1-F)
FILES lane. Branch off
5dfe7ad(the #114 head, S1 + link redaction), so this stacks on #114 — until #114 merges, the diff vsmainalso shows the S1 files.G1-A — producer-side governed-HOST allowlist (RV-145 N1, the owed half of CORE-CITATION-GOVERNED-LINK)
#556shipped the scheme/storage/presign half in Core; the host-level rule (RAG_GOVERNED_LINK_HOSTS) had no source of truth inrag_api. This adds it on the producer side, inapp/routes/document_routes.py:RAG_GOVERNED_LINK_HOSTSis read once at import (_governed_link_hosts()), comma- and/or whitespace-separated, lower-cased._reject_ungoverned_linkkeeps the scheme guard first (non-https still refused aslink_scheme_not_allowed); then, when the allowlist is configured, an https link whose host is not admitted is refused with the new typed reasonlink_host_not_allowed— same 422 envelope shape as the scheme case (message+link.reason+link.allowed_hosts).urlparse(...).hostname(drops userinfo/credentials and port, lower-cases). An exact entry (a.example.com) matches only itself; a dot-prefixed entry (.example.com) matches any subdomain but not the bare parent and not a look-alike (evilexample.com). Userinfo spoofing (https://allowed.host@evil.com/) is matched on the real host,evil.com.RAG_GOVERNED_LINK_HOSTSis unset/empty, the host check is DISABLED and current https-only behaviour is preserved with zero new rejections. The allowlist VALUE is a Richard policy/disclosure call — no host value or default list is invented here.G1-F — LLM/IMDS paid-call test guard (RV-150 N1 twin, per CARD-N5)
In
tests/conftest.py(test-only; no production behaviour change):_block_the_real_llm_providerreplaces the boto3bedrock-runtimeclient's socket-opening step (converse/converse_stream/invoke_model) on every live holder (app.config.llmanddocument_routes.llm), withraising=Trueon the.clientattribute (rename-loud). Placement blocks the socket step, not the high-levelllma test may stub.AWS_EC2_METADATA_DISABLED=true+ dummy AWS creds/region are set beforeapp.configimports (viasetdefault, so CI's own values win), so the import-time boto3 session makes no IMDS probe (proven with a socket spy: 0 attempts to 169.254.169.254).SUM_UP_KNOWLEDGE_FILESoff and proves that flipping it still cannot reach a provider (the guard, not the default, is what stands between tests and inference).Tests (red-first, proven under
--network nonein the shipped-runtime image, Python 3.10.21)tests/test_embed_link_host_allowlist.py— SET: non-allowlisted host refused (link_host_not_allowed); exact host, dot-prefixed subdomain, and userinfo+port host all accepted; bare parent + suffix look-alike + userinfo-spoof refused; raw link never echoed (body or log); refusal before any upload/extraction/store; length cap; matcher + parser unit cases. UNSET: arbitrary https host still ingests (conservative-default regression). Scheme guard runs first even with a host allowlist.tests/test_llm_provider_is_blocked_in_tests.py— summarizer path blocked; directllm.invokeblocked; clientconverse/converse_stream/invoke_modelraise the guard message; deliberate-regression (a real client DOES attempt the network — proving the guard is load-bearing); stubbed-llm untouched; attribute-name pin; config-reload gap; gate pin; no IMDS probe at import.Red-first: the host behavioural tests fail on
5dfe7ad(guard absent, 27 red) and the LLM file fails collection (ImportError, guard symbols absent). One targeted one-line mutation per guard was injected and each reddened the relevant tests; both files were restored byte-identical (md5/sha verified) before commit — no mutation is in this tree.Acceptance ladder
document_routes.py; G1-F inconftest.py).--network none; per-guard mutation caught).RAG_GOVERNED_LINK_HOSTSvalue is OPEN FOR RICHARD; until set, the host guard is inert by design.🤖 Generated with Claude Code