diff --git a/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx b/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx
index dd4dd96599a..97ed87b975e 100644
--- a/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx
+++ b/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx
@@ -23,6 +23,7 @@ import { ProviderForm } from "../components/ProviderForm";
import { getProviderIcon } from "../components/ProviderIcon";
import {
aiProviderToFormValues,
+ bedrockExternalId,
getProviderDisplayType,
hasBedrockStoredCredentials,
isBedrockProvider,
@@ -196,6 +197,7 @@ const UpdateProviderPageView: React.FC = () => {
bedrockSavedAccessCredentials={hasBedrockStoredCredentials(
provider,
)}
+ bedrockExternalId={bedrockExternalId(provider)}
openAiAnthropicSavedApiKey={openAiAnthropicSavedApiKey}
openAiAnthropicMaskedApiKey={openAiAnthropicMaskedApiKey}
initialValues={aiProviderToFormValues(provider)}
diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.stories.tsx b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.stories.tsx
index 7851332862c..af492374278 100644
--- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.stories.tsx
+++ b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.stories.tsx
@@ -293,6 +293,40 @@ export const EditBedrockKeepCredentials: Story = {
},
};
+// The external ID is server-generated when a role is assumed. The edit form
+// surfaces it read-only next to Role ARN so operators can add the value to
+// their role's trust policy as an sts:ExternalId condition.
+export const EditBedrockWithExternalId: Story = {
+ args: {
+ editing: true,
+ bedrockSavedAccessCredentials: true,
+ bedrockExternalId: "7QF3ZK2MLP4RS6TUVWXY2ABCDE",
+ initialValues: {
+ type: "bedrock",
+ name: "bedrock",
+ displayName: "Bedrock",
+ baseUrl: "https://bedrock-runtime.us-east-2.amazonaws.com",
+ model: "anthropic.claude-opus-4-7",
+ smallFastModel: "anthropic.claude-haiku-4-5",
+ accessKey: "",
+ accessKeySecret: "",
+ roleArn: "arn:aws:iam::123456789012:role/BedrockRole",
+ enabled: true,
+ },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ await expect(canvas.findByText("External ID")).resolves.toBeVisible();
+ await expect(
+ canvas.findByText("7QF3ZK2MLP4RS6TUVWXY2ABCDE"),
+ ).resolves.toBeVisible();
+ await expect(canvas.findByText("sts:ExternalId")).resolves.toBeVisible();
+ await expect(
+ canvas.findByRole("button", { name: /copy code/i }),
+ ).resolves.toBeVisible();
+ },
+};
+
export const AddCopilot: Story = {
args: {
// The real add flow passes only the type; the form fills name and
diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.tsx b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.tsx
index 6a2b1dfc4ac..7a87b06d14a 100644
--- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.tsx
+++ b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.tsx
@@ -6,9 +6,11 @@ import * as Yup from "yup";
import type { AIProviderType } from "#/api/typesGenerated";
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
import { Button } from "#/components/Button/Button";
+import { CodeExample } from "#/components/CodeExample/CodeExample";
import { ConfirmDialog } from "#/components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Form, FormFields } from "#/components/Form/Form";
import { FormField } from "#/components/FormField/FormField";
+import { Label } from "#/components/Label/Label";
import { Link as DocsLink } from "#/components/Link/Link";
import { Spinner } from "#/components/Spinner/Spinner";
import { useUnsavedChangesPrompt } from "#/hooks/useUnsavedChangesPrompt";
@@ -242,6 +244,8 @@ type ProviderFormProps = {
editing?: boolean;
/** When editing Bedrock and the API already has keys, show masked placeholders until cleared. */
bedrockSavedAccessCredentials?: boolean;
+ /** Server-generated STS external ID, shown read-only when a role is assumed. */
+ bedrockExternalId?: string;
/** When editing openai/anthropic and a key is on file, show a masked placeholder until cleared. */
openAiAnthropicSavedApiKey?: boolean;
/** Masked rendering of the saved openai/anthropic key (e.g. `sk-***...ABCD`). Falls back to a generic mask when omitted. */
@@ -271,6 +275,7 @@ const baseUrlPlaceholder = (provider: string) =>
export const ProviderForm: FC
+ Server-generated. Add it to the assumed role's trust policy as
+ an sts:ExternalId condition so only this
+ deployment can assume the role.
+