# Bedrock Web Search in GovCloud: an ADR on grounding under compliance

On September 2, 2026 AWS brought the server-side Web Search tool on Amazon Bedrock to GovCloud (US-West). I wrote this ADR because the real decision is not "web search or no web search" — it is which retrieval mode you operate in, and the default of the `external_web_access` parameter pushes regulated teams to exactly the wrong side, with an HTTP 200 and no visible error.

- URL: https://fernando.moretes.com/blog/web-search-do-bedrock-no-govcloud-um-adr-sobre-grounding-regulado-web-search-o

- Markdown: https://fernando.moretes.com/blog/web-search-do-bedrock-no-govcloud-um-adr-sobre-grounding-regulado-web-search-o/article.md?lang=en

- Published: 2026-09-03T10:16:16.722Z

- Category: AI & Agents

- Tags: bedrock, govcloud, grounding, iam, finops, adr, compliance, observability

- Reading time: 8 min

- Source: [Web Search on Amazon Bedrock is now available in AWS GovCloud (US-West)](https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-bedrock-web-aws-govcloud/)

---

The AWS note dated September 2, 2026 carries two lines of substance: the built-in Web Search tool on Amazon Bedrock now exists in GovCloud (US-West), and it keeps request data inside the AWS boundary by default. For anyone designing AI platforms in regulated environments — government, but equally banking, insurance, payments — this is not "one more feature". It is the difference between an agent that cites verifiable sources and an agent answering from memory about prices, service limits and regulation that changed last week. Except adoption is not a binary decision, and the path of least resistance is the wrong one. This is the decision record I would write before enabling it in production.

## Context and forces

The problem that brought me here is old: LLM-assisted decision systems in regulated domains need *provenance*, not fluency. When an analyst asks "what is the current limit on that quota" or "is this product still authorized", an answer without an auditable link is a liability, not an asset.

Until now I solved it with one of two ugly options. First: my own index — crawler, normalization, embeddings, reindexing — which nobody wants to operate and which ages in days. Second: a third-party search API called from a Lambda, which means egress outside the account, a secret to rotate, an egress allowlist, and the tool-call loop written by hand. I ran exactly that design for months in my own content pipeline, with hand-rolled SigV4, and it broke for reasons that had nothing to do with the business problem.

The forces that weigh on this decision, hardest first: (1) query data must not leave the provider boundary without explicit control — in GovCloud that is a requirement, not a preference; (2) every claim shown to a user must carry a source; (3) cost is per query and is not negligible; (4) the team should operate neither index nor crawler; (5) authorization must be central, enforceable per Organization and per Region, with an audit trail. The tool landing in GovCloud addresses (1) and (4), but (2), (3) and (5) remain my work.

## What the tool actually is — and where it lives

Before deciding, it is worth pinning down the object. Web Search is a **server-side** tool exposed only through the Responses API on the `bedrock-mantle` endpoint — it does not exist on `bedrock-runtime`, nor in `Converse`, nor in `InvokeModel`. You add `{"type": "web_search"}` to the `tools` array using OpenAI's own client library with a Bedrock API key, and the model decides whether to call it. That ties the decision to one model family: per the documentation, GovCloud (US-West) supports `openai.gpt-5.6-terra`, `openai.gpt-5.6-luna` and `openai.gpt-5.4`; commercial Regions add `gpt-5.6-sol` and `gpt-5.5`. There is no Claude, Nova or Mistral on this path.

Internally there are **two operations**, and that distinction is the heart of the ADR. `Search` returns title, URL and snippet from the web index and knowledge graph maintained by Amazon, and makes **no outbound call**. `Fetch` retrieves the content of a specific URL: with `external_web_access=false` it reads only the Bedrock cache; with `true` it checks the cache and reaches the external web only on a cache miss.

Retrieval is strictly regional — each Region runs its own search and fetch tier, and queries, fetches, index data and results are not routed across Regions. `us-gov-west-1` is one of those Regions, alongside `us-east-1`, `us-east-2` and `us-west-2`. Context volume per Search call is governed by `search_context_size`: `low` up to 5 observations, `medium` up to 11 (the default), `high` up to 25 — a budget shared across multiple queries in the same call. That parameter does **not** cap how many Search calls the model makes per turn.

## Options considered

### A. Native Web Search in cache-only mode (`external_web_access: false`)

**Pros**
- Request data does not leave the AWS boundary; Search comes from the Bedrock index and Fetch from cache only.
- Zero infrastructure: no crawler, no index, no tool-call loop, no third-party secret to rotate.
- `url_citation` annotations with character offsets come from the model itself, ready to render as footnotes.
- Requires no `ExternalWebAccess` permission, so it coexists with an SCP that denies it outright.

**Cons**
- Freshness is bounded by what is already in Amazon's index and cache — you do not control the refresh window.
- Locks grounding to the GPT models on the `bedrock-mantle` endpoint.

**Verdict:** Chosen.

### B. Native Web Search with external web access (`true` + `ExternalWebAccess`)

**Pros**
- Best freshness: on a cache miss, Fetch pulls the page from origin.

**Cons**
- AWS's own documentation records an exfiltration risk: an agent can encode query data into a URL and have it fetched.
- Request data may leave the AWS boundary — unacceptable in the very perimeter that motivates GovCloud.
- The managed policies `...ExternalWebSearchReadOnly` and `...FullAccess` currently have identical effective permissions; "ReadOnly" restricts nothing.

**Verdict:** Rejected for regulated workloads.

### C. Bedrock AgentCore Web Search via Gateway

**Pros**
- Model-agnostic: serves agents on Claude, Nova or anything else, not just GPT.
- Lower per-query price — the AgentCore page lists US$ 7 per 1,000 queries.

**Cons**
- A surface to operate: Gateway, agent identity, request signing. I had exactly this and retired it.
- Governance lives in a different control plane, not in the three `bedrock-websearch:*` actions.

**Verdict:** Kept as the multi-model plan B.

### D. Own index / search (crawler or third-party search API)

**Pros**
- Full source control: a real domain allowlist, which none of the native options delivers.

**Cons**
- Egress, secrets, retry, backoff, dedupe and the tool-call loop become your code — and that is where my incidents lived.
- Recurring operational cost with no proportional quality gain.

**Verdict:** Rejected, unless source allowlisting is an explicit regulatory requirement.

## Decision

**Decision: adopt option A — native Web Search in cache-only mode — with the parameter set explicitly and the permission denied by SCP.** The configuration is deliberately redundant, because the parameter and the permission fail in different ways.

In code, every caller sends `tools=[{"type":"web_search","external_web_access":false,"search_context_size":"low"}]`. `low` is my wrapper's default; `medium` is opt-in per research route; `high` requires justification, because 25 observations per call inflate input by something on the order of 10k tokens per turn.

At the perimeter, an SCP denies `bedrock-websearch:ExternalWebAccess` across the whole Organization. Note that IAM granularity here is short: the three actions (`InvokeSearch`, `InvokeFetch`, `ExternalWebAccess`) support only `Resource: "*"`, and the sole condition key is the global `aws:RequestedRegion`. So the "restrict by Region" the announcement mentions is literally a `StringEquals` on `aws:RequestedRegion` — and I use it to pin `us-gov-west-1` in the regulated perimeter, which is also the only way to stop a mis-routed call from being served by another regional tier.

On application roles I do not use `AmazonBedrockFullAccess`. I use a custom policy with `InvokeSearch` and `InvokeFetch` under the Region condition, because I want the absence of `ExternalWebAccess` to be a recorded decision, not a side effect of whichever managed policy somebody attached.

CloudTrail with data events enabled for `bedrock-websearch` is part of the decision, not a follow-up: without it, denials are reported nowhere.

## One Web Search turn: two IAM gates and one boundary line

The path of a single Responses API call. Search never leaves the boundary; Fetch is the only point where egress is possible, and that is where the SCP cuts. Note the caller still gets a 200 even when Fetch is denied.

### 🟧 AWS GovCloud (us-gov-west-1) — bedrock-mantle

- Responses API tools=[web_search] (compute)
- openai.gpt-5.6-terra decides if it needs current info (ai)

### 🔐 IAM — bedrock-websearch (Resource: *)

- InvokeSearch aws:RequestedRegion = us-gov-west-1 (security)
- InvokeFetch fetchMode = USE_CACHE_ONLY (security)
- ExternalWebAccess DENIED by SCP (security)

### 🔎 Retrieval tier — in-Region, no cross-Region routing

- Bedrock web index + knowledge graph (data)
- Bedrock page cache title + URL + content (storage)

### 🌐 Outside the AWS boundary

- External web origin reached only on cache miss (external)

### 📋 Audit & provenance — my responsibility

- CloudTrail data events fetchedSources, urlCount (messaging)
- DynamoDB citation ledger PK run#<id> / SK cite#<sha256(url)> (data)
- UI footnotes url_citation offsets (frontend)

### Flows

- app -> resp: 1. POST /openai/v1/responses
- resp -> model: 2. tool exposed to model
- model -> g1: 3. query (no outbound call)
- g1 -> index: 4. ≤5 observations (low)
- index -> model: 5. title + URL + snippet
- model -> g2: 6. Fetch chosen URL
- g2 -> cache: 7. cache read only
- g2 -> g3: 8. cache miss → needs permission
- g3 -> web: 9. blocked (AccessDenied)
- g2 -> ct: 10. CACHE / EXTERNAL_WEB counts
- model -> app: 11. text + url_citation (HTTP 200 even if 9 failed)
- app -> ledger: 12. persist provenance
- ledger -> ui: 13. render the source

> **The consequence that bites: silent degradation with HTTP 200:** `external_web_access` defaults to `true`, for compatibility with OpenAI's API. But `AmazonBedrockFullAccess` grants `InvokeSearch` and `InvokeFetch` and does **not** grant `ExternalWebAccess`. In that combination — the most common one — every Fetch attempt fails its backend authorization **before the cache is read**: you lose page content you would have had for free. And the Responses API request still returns HTTP 200, with `url_citation` annotations derived only from Search observations, with no guarantee the model mentions the failure. Grounding quality drops, nobody sees an error, no alarm fires. Worse: the presence of a citation does not prove the page was fetched. The only reliable evidence is the `additionalEventData.fetchedSources` field in CloudTrail — which exists only if you enabled data events for `bedrock-websearch`, which are off by default and billed separately.

## Consequences: cost, latency, and what CloudTrail does not tell you

**Cost.** Billing is per query — a query is one search request. The AgentCore page lists US$ 7 per 1,000 queries for its variant; independent analyses put the built-in tool near US$ 12 per 1,000 (confirm the current rate for your Region on the Bedrock pricing page before modelling). The budget-breaking detail is not the unit price: it is that `search_context_size` caps observations per call but does **not** cap how many Search calls the model makes in a turn. A multi-hop turn that reformulates the query twice costs three queries. At US$ 0.012 per query, 50k turns/month at 3 queries is US$ 1,800/month in search alone, before inference — and before the input inflated by observations. Model per turn, not per query, and put a daily token *and* query ceiling in your own wrapper, on the application side, because IAM does not count calls.

**Idempotency.** The call is not idempotent: a retry re-runs the searches and bills again. My retry is `max_attempts=2` with jitter plus a result cache keyed on `sha256(prompt + modelId + toolConfig)`, TTL in hours. Caller-side timeouts must accommodate serial hops inside a single call — in my article generator I operate with a 300 s ceiling.

**Audit.** By design, CloudTrail does **not** expose query text, URLs or raw results: query text is treated like an inference prompt. You get who called, when, from where, `fetchMode`, `urlCount`, `failedUrlCount` and `fetchedSources`. If your regulator wants to know *what* the agent consulted, that answer is not in CloudTrail — it is in the citation ledger you write from the `url_citation` annotations.

## Consequences by pillar

- **security**: Cache-only plus an SCP denying `ExternalWebAccess` closes the only egress path and the URL-encoded exfiltration vector AWS itself documents. In exchange, I accept that `Resource` is always `*` and the sole condition key is `aws:RequestedRegion` — there is no domain allowlist in IAM.
- **reliability**: The dominant failure mode stops being an error and becomes silence: denied Fetch with HTTP 200. I mitigate with CloudTrail data events and an alarm on the `failedUrlCount / urlCount` ratio, plus counting answers with zero `url_citation` annotations on routes that should cite.

## Failure modes I watch once the decision is made

**The ReadOnly illusion.** `AmazonBedrockExternalWebSearchReadOnly` and `AmazonBedrockExternalWebSearchFullAccess` currently have identical effective permissions — the "ReadOnly" version does not restrict external retrieval. If your security review approves by policy name, it approved external web access believing it approved reads.

**The illusion of restricting sources via IAM.** Denying `InvokeSearch` while keeping `InvokeFetch` does not confine the model to your sources: Fetch applies to any URL the model produces, including one that came from the user's prompt or from its own parametric memory. Source control, if it is a requirement, must live in the application — validating each cited URL against an allowlist before display.

**Prompt injection through retrieved content.** Snippets and fetched pages are untrusted input entering the same context as the instruction. In regulated workloads I treat a Search observation as data, never as a command, and keep the injection filter on both sides of the turn — the same discipline I apply to any RAG.

**Freshness that misleads.** In cache-only mode, freshness is Amazon's index and cache, not the origin. For pricing, quotas and regulatory deadlines I do not trust what came back without checking the date in the answer itself — and I write around whatever I cannot confirm.

**Model coupling.** The tool exists only on the Responses API over `bedrock-mantle`, on GPT models. If tomorrow the model decision moves to Claude or Nova, the grounding path moves with it — which is why I kept option C alive in the record, not as a footnote.

> **Curator's note:** I have lived this ADR from the wrong side. In my own content pipeline I kept a Lambda talking to a search AgentCore Gateway over hand-rolled SigV4, with a dedupe bug that always answered "this is new": no error, HTTP 200, and repeated articles on the same topic for days. I replaced it with provider-native search and the problem stopped being mine. It is precisely the same failure class as the denied Fetch returning 200 here — and the lesson I would apply again is this: **whatever the HTTP response does not tell you, you have to measure from outside**. Before enabling Web Search in any regulated account, I turn on CloudTrail data events for `bedrock-websearch`, pin `external_web_access: false` in code, deny `ExternalWebAccess` via SCP, and only then open the route — in the reverse order, you learn about the problem from a user complaint rather than an alarm.

## Verdict

Adopt it — in cache-only mode, and treat the configuration as part of the control, not as a call-site detail. Web Search landing in GovCloud (US-West) closes a real gap: it gives citable grounding to compliance workloads with no crawler, no index and no egress, on GPT models that already had FedRAMP High and DoD IL-4/5 in GovCloud since June 2026. What does not solve itself is governance: `external_web_access` is born `true`, `AmazonBedrockFullAccess` does not grant the matching permission, and the combination degrades grounding while returning HTTP 200. Write `false` explicitly, deny `ExternalWebAccess` by SCP, pin the Region with `aws:RequestedRegion`, enable CloudTrail data events before the first request, and keep your own citation ledger — CloudTrail does not retain what was consulted. Done that way, it is the best grounding cost-benefit available today in a regulated environment. Done on defaults, it is a quality regression nobody will see.

**Rating:** Adopt — cache-only, with SCP + CloudTrai

## References

- [AWS What's New — Web Search on Amazon Bedrock is now available in AWS GovCloud (US-West) (Sep 2, 2026)](https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-bedrock-web-aws-govcloud/)
- [Amazon Bedrock User Guide — Web Search (models, Regions, search_context_size, external_web_access, citations)](https://docs.aws.amazon.com/bedrock/latest/userguide/web-search.html)
- [Amazon Bedrock User Guide — Identity and access management for Web Search (bedrock-websearch actions, managed policies, ](https://docs.aws.amazon.com/bedrock/latest/userguide/security-web-search.html)
- [Amazon Bedrock User Guide — Monitor Web Search (CloudTrail data events, fetchMode, fetchedSources)](https://docs.aws.amazon.com/bedrock/latest/userguide/monitoring-web-search.html)
- [AWS ML Blog — Introducing Web Search on Amazon Bedrock for foundation model grounding](https://aws.amazon.com/blogs/machine-learning/introducing-web-search-on-amazon-bedrock-for-foundation-model-grounding/)
- [Amazon Bedrock AgentCore pricing — Web Search at US$ 7 per 1,000 queries](https://aws.amazon.com/bedrock/agentcore/pricing/)
- [AWS What's New — OpenAI GPT and NVIDIA Nemotron models on Bedrock receive FedRAMP High and DoD IL-4/5 in AWS GovCloud (U](https://aws.amazon.com/about-aws/whats-new/2026/06/addl-bedrock-model-fedramp-il-5-govcloud/)
- [Unite.AI — Amazon Bedrock gets built-in Web Search for grounded model responses (independent analysis, per-query pricing](https://www.unite.ai/amazon-bedrock-gets-built-in-web-search-for-grounded-model-responses/)
