Skip to content

Commit 39b9bfb

Browse files
committed
feat(dashboard-agent): support dedicated non-global Bedrock credentials
Add DASHBOARD_AGENT_AWS_ACCESS_KEY_ID/SECRET_ACCESS_KEY/REGION so the agent's Bedrock creds don't hijack the default AWS chain the ECR/STS deploy clients rely on. Falls back to the default chain and existing AWS_REGION/AWS_DEFAULT_REGION order when unset.
1 parent 068f641 commit 39b9bfb

4 files changed

Lines changed: 94 additions & 7 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ const EnvironmentSchema = z
180180
AWS_SECRET_ACCESS_KEY: z.string().optional(),
181181
AWS_SESSION_TOKEN: z.string().optional(),
182182
AWS_BEARER_TOKEN_BEDROCK: z.string().optional(),
183+
// Dedicated, non-global credentials for the dashboard agent's Bedrock calls (a
184+
// Bedrock-invoke-only IAM user). Kept separate from AWS_ACCESS_KEY_ID/etc so
185+
// injecting them can't hijack the default credential chain the ECR/STS deploy
186+
// clients rely on.
187+
DASHBOARD_AGENT_AWS_ACCESS_KEY_ID: z.string().optional(),
188+
DASHBOARD_AGENT_AWS_SECRET_ACCESS_KEY: z.string().optional(),
189+
DASHBOARD_AGENT_AWS_REGION: z.string().optional(),
183190
DIRECT_URL: z
184191
.string()
185192
.refine(

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
332332
const repoSnapshot = await resolveDashboardAgentRepoSnapshot(project.id);
333333
const headStarted =
334334
dashboardAgentProvider() === "bedrock"
335-
? Boolean(env.AWS_REGION || env.AWS_DEFAULT_REGION)
335+
? Boolean(env.DASHBOARD_AGENT_AWS_REGION || env.AWS_REGION || env.AWS_DEFAULT_REGION)
336336
: Boolean(env.ANTHROPIC_API_KEY);
337337

338338
// The lookups and the mint all run before the chat row exists, so a failure here can't

internal-packages/dashboard-agent/src/model-provider.test.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { afterEach, describe, expect, it } from "vitest";
22
import { PROMPT_CACHE_CONTROL } from "./prompt-prefix";
33
import {
44
BEDROCK_MODEL_IDS,
5+
bedrockProviderSettings,
6+
bedrockRegion,
57
isLongLivedCacheBreakpoint,
68
isStepCacheBreakpoint,
79
resolveDashboardAgentModel,
@@ -14,8 +16,17 @@ function useBedrock() {
1416
process.env.DASHBOARD_AGENT_MODEL_PROVIDER = "bedrock";
1517
}
1618

19+
const AWS_ENV_VARS = [
20+
"DASHBOARD_AGENT_MODEL_PROVIDER",
21+
"DASHBOARD_AGENT_AWS_ACCESS_KEY_ID",
22+
"DASHBOARD_AGENT_AWS_SECRET_ACCESS_KEY",
23+
"DASHBOARD_AGENT_AWS_REGION",
24+
"AWS_REGION",
25+
"AWS_DEFAULT_REGION",
26+
] as const;
27+
1728
afterEach(() => {
18-
delete process.env.DASHBOARD_AGENT_MODEL_PROVIDER;
29+
for (const key of AWS_ENV_VARS) delete process.env[key];
1930
});
2031

2132
describe("resolveDashboardAgentModel", () => {
@@ -118,3 +129,49 @@ describe("cache breakpoints", () => {
118129
expect(withoutCacheBreakpoint(legacyStep)).toEqual({ anthropic: { keep: true } });
119130
});
120131
});
132+
133+
describe("Bedrock region and credential resolution", () => {
134+
it("prefers DASHBOARD_AGENT_AWS_REGION over the global AWS region vars", () => {
135+
process.env.AWS_REGION = "us-east-1";
136+
process.env.AWS_DEFAULT_REGION = "us-west-2";
137+
process.env.DASHBOARD_AGENT_AWS_REGION = "eu-west-1";
138+
expect(bedrockRegion()).toBe("eu-west-1");
139+
});
140+
141+
it("falls back to AWS_REGION, then AWS_DEFAULT_REGION", () => {
142+
process.env.AWS_DEFAULT_REGION = "us-west-2";
143+
expect(bedrockRegion()).toBe("us-west-2");
144+
145+
process.env.AWS_REGION = "us-east-1";
146+
expect(bedrockRegion()).toBe("us-east-1");
147+
});
148+
149+
it("treats an empty region as unset at every tier", () => {
150+
process.env.DASHBOARD_AGENT_AWS_REGION = "";
151+
process.env.AWS_REGION = "";
152+
process.env.AWS_DEFAULT_REGION = "";
153+
expect(bedrockRegion()).toBeUndefined();
154+
});
155+
156+
it("passes explicit credentials when the dedicated pair is set", () => {
157+
process.env.DASHBOARD_AGENT_AWS_ACCESS_KEY_ID = "AKIA_DASHBOARD_AGENT";
158+
process.env.DASHBOARD_AGENT_AWS_SECRET_ACCESS_KEY = "secret";
159+
process.env.DASHBOARD_AGENT_AWS_REGION = "eu-west-1";
160+
161+
expect(bedrockProviderSettings()).toEqual({
162+
region: "eu-west-1",
163+
accessKeyId: "AKIA_DASHBOARD_AGENT",
164+
secretAccessKey: "secret",
165+
});
166+
});
167+
168+
it("keeps the default credential chain when the dedicated pair is unset", () => {
169+
process.env.AWS_REGION = "us-east-1";
170+
expect(bedrockProviderSettings()).toEqual({ region: "us-east-1" });
171+
});
172+
173+
it("keeps the default chain when only one half of the dedicated pair is set", () => {
174+
process.env.DASHBOARD_AGENT_AWS_ACCESS_KEY_ID = "AKIA_DASHBOARD_AGENT";
175+
expect(bedrockProviderSettings()).toEqual({ region: undefined });
176+
});
177+
});

internal-packages/dashboard-agent/src/model-provider.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,34 @@ export function dashboardAgentProvider(): DashboardAgentProvider {
2121
return process.env.DASHBOARD_AGENT_MODEL_PROVIDER === "bedrock" ? "bedrock" : "anthropic";
2222
}
2323

24-
// Region passed explicitly since the SDK reads only AWS_REGION; credentials stay on
25-
// its own chain. `||` treats an empty region as unset, matching the webapp gate.
26-
const bedrock = createAmazonBedrock({
27-
region: process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION,
28-
});
24+
// Region passed explicitly since the SDK reads only AWS_REGION. `||` treats an empty
25+
// region as unset. DASHBOARD_AGENT_AWS_REGION takes priority over the global vars.
26+
export function bedrockRegion(): string | undefined {
27+
return (
28+
process.env.DASHBOARD_AGENT_AWS_REGION ||
29+
process.env.AWS_REGION ||
30+
process.env.AWS_DEFAULT_REGION ||
31+
undefined
32+
);
33+
}
34+
35+
// Dedicated, non-global credentials only — the default chain (and the global
36+
// AWS_ACCESS_KEY_ID/etc, if ever set) stays untouched for the ECR/STS deploy clients.
37+
function bedrockCredentials(): { accessKeyId: string; secretAccessKey: string } | undefined {
38+
const accessKeyId = process.env.DASHBOARD_AGENT_AWS_ACCESS_KEY_ID;
39+
const secretAccessKey = process.env.DASHBOARD_AGENT_AWS_SECRET_ACCESS_KEY;
40+
return accessKeyId && secretAccessKey ? { accessKeyId, secretAccessKey } : undefined;
41+
}
42+
43+
export function bedrockProviderSettings(): {
44+
region?: string;
45+
accessKeyId?: string;
46+
secretAccessKey?: string;
47+
} {
48+
return { region: bedrockRegion(), ...bedrockCredentials() };
49+
}
50+
51+
const bedrock = createAmazonBedrock(bedrockProviderSettings());
2952

3053
export const registry = createProviderRegistry({ anthropic, bedrock });
3154

0 commit comments

Comments
 (0)