diff --git a/src/handlers/identity/api-key-credential-provider/create/index.tsx b/src/handlers/identity/api-key-credential-provider/create/index.tsx index 1b8d5dcbf..068c31d0d 100644 --- a/src/handlers/identity/api-key-credential-provider/create/index.tsx +++ b/src/handlers/identity/api-key-credential-provider/create/index.tsx @@ -14,9 +14,12 @@ export const createCreateApiKeyCredentialProviderHandler = (core: Core, io: AppI description: "create an API key credential provider", flags: [ flag("name", "the name of the API key credential provider", z.string().optional()), - flag("api-key", "the API key value (inline, file://path, or -)", z.string().optional(), { - sensitive: true, - }), + flag( + "api-key", + "the API key (file://path or - for stdin; inline values are rejected)", + z.string().optional(), + { sensitive: true }, + ), flag( "api-key-secret-reference", 'external secret reference JSON: {"secretId":"","jsonKey":""}', @@ -44,7 +47,7 @@ export const createCreateApiKeyCredentialProviderHandler = (core: Core, io: AppI } const resolver = new SourceResolver({ stdin: io.stdin }); - const apiKey = await resolver.resolveText("api-key", flags["api-key"]); + const apiKey = await resolver.resolveSecret("api-key", flags["api-key"]); const apiKeySecretConfig = hasSecretRef ? parseSecretReference("api-key-secret-reference", flags["api-key-secret-reference"]!) : undefined; diff --git a/src/handlers/identity/api-key-credential-provider/update/index.tsx b/src/handlers/identity/api-key-credential-provider/update/index.tsx index bb50b5802..4ec4f9490 100644 --- a/src/handlers/identity/api-key-credential-provider/update/index.tsx +++ b/src/handlers/identity/api-key-credential-provider/update/index.tsx @@ -14,9 +14,12 @@ export const createUpdateApiKeyCredentialProviderHandler = (core: Core, io: AppI description: "update an API key credential provider", flags: [ flag("name", "the name of the API key credential provider", z.string().optional()), - flag("api-key", "the new API key value (inline, file://path, or -)", z.string().optional(), { - sensitive: true, - }), + flag( + "api-key", + "the new API key (file://path or - for stdin; inline values are rejected)", + z.string().optional(), + { sensitive: true }, + ), flag( "api-key-secret-reference", 'external secret reference JSON: {"secretId":"","jsonKey":""}', @@ -57,7 +60,7 @@ export const createUpdateApiKeyCredentialProviderHandler = (core: Core, io: AppI } const resolver = new SourceResolver({ stdin: io.stdin }); - const apiKey = await resolver.resolveText("api-key", flags["api-key"]); + const apiKey = await resolver.resolveSecret("api-key", flags["api-key"]); const apiKeySecretConfig = hasSecretRef ? parseSecretReference("api-key-secret-reference", flags["api-key-secret-reference"]!) : undefined; diff --git a/src/handlers/identity/identity.test.tsx b/src/handlers/identity/identity.test.tsx index 122c961cf..8be21378b 100644 --- a/src/handlers/identity/identity.test.tsx +++ b/src/handlers/identity/identity.test.tsx @@ -32,8 +32,8 @@ function createFixtureCore(): CoreClient { }); } -async function run(args: string[]): Promise { - const io = testIO(); +async function run(args: string[], stdin?: string): Promise { + const io = testIO({ stdin }); const root = createRootHandler(createFixtureCore(), { io: io.io, logger: createSilentLogger(), @@ -101,29 +101,35 @@ describe("api-key-credential-provider TUI dispatch", () => { describe("api-key-credential-provider CRUDL", () => { test("creates an API key credential provider", async () => { - const stdout = await run([ - "identity", - "api-key-credential-provider", - "create", - "--name", - FIXTURE_PROVIDER_NAME, - "--api-key", + const stdout = await run( + [ + "identity", + "api-key-credential-provider", + "create", + "--name", + FIXTURE_PROVIDER_NAME, + "--api-key", + "-", + ], "test-api-key-value", - ]); + ); matchGolden(FIXTURES, "create.golden.json", stdout); }); test("creates a second API key credential provider for pagination", async () => { - const stdout = await run([ - "identity", - "api-key-credential-provider", - "create", - "--name", - FIXTURE_PROVIDER_NAME_2, - "--api-key", + const stdout = await run( + [ + "identity", + "api-key-credential-provider", + "create", + "--name", + FIXTURE_PROVIDER_NAME_2, + "--api-key", + "-", + ], "test-api-key-value-2", - ]); + ); matchGolden(FIXTURES, "create-2.golden.json", stdout); }); @@ -176,15 +182,18 @@ describe("api-key-credential-provider CRUDL", () => { }); test("updates an API key credential provider", async () => { - const stdout = await run([ - "identity", - "api-key-credential-provider", - "update", - "--name", - FIXTURE_PROVIDER_NAME, - "--api-key", + const stdout = await run( + [ + "identity", + "api-key-credential-provider", + "update", + "--name", + FIXTURE_PROVIDER_NAME, + "--api-key", + "-", + ], "updated-api-key-value", - ]); + ); matchGolden(FIXTURES, "update.golden.json", stdout); expect(JSON.parse(stdout).name).toBe(FIXTURE_PROVIDER_NAME); @@ -301,6 +310,19 @@ describe("api-key-credential-provider CRUDL", () => { ], /mutually exclusive/, ], + [ + "create: --api-key with an inline value", + [ + "identity", + "api-key-credential-provider", + "create", + "--name", + "x", + "--api-key", + "sk-inline", + ], + /file:\/\//, + ], ] as const)("rejects invalid secret input for `%s`", async (_label, args, message) => { expect(run([...args])).rejects.toThrow(message); }); diff --git a/src/handlers/identity/oauth2-credential-provider/create/index.tsx b/src/handlers/identity/oauth2-credential-provider/create/index.tsx index a57e65ac3..62248fdb8 100644 --- a/src/handlers/identity/oauth2-credential-provider/create/index.tsx +++ b/src/handlers/identity/oauth2-credential-provider/create/index.tsx @@ -23,11 +23,9 @@ export const createCreateOauth2CredentialProviderHandler = (core: Core, io: AppI flag("vendor", "the OAuth2 vendor (e.g. CustomOauth2, GithubOauth2)", z.string().optional()), flag( "client-secret", - "the client secret (inline, file://path, or -)", + "the client secret (file://path or - for stdin; inline values are rejected)", z.string().optional(), - { - sensitive: true, - }, + { sensitive: true }, ), flag( "client-secret-reference", @@ -81,7 +79,7 @@ export const createCreateOauth2CredentialProviderHandler = (core: Core, io: AppI validateProviderConfigMode(providerConfigMode, vendor); const resolver = new SourceResolver({ stdin: io.stdin }); - const clientSecret = await resolver.resolveText("client-secret", flags["client-secret"]); + const clientSecret = await resolver.resolveSecret("client-secret", flags["client-secret"]); const clientSecretConfig = hasSecretRef ? parseSecretReference("client-secret-reference", flags["client-secret-reference"]!) diff --git a/src/handlers/identity/oauth2-credential-provider/oauth2.fixture.test.tsx b/src/handlers/identity/oauth2-credential-provider/oauth2.fixture.test.tsx index 970ffc3c0..8ddc286a4 100644 --- a/src/handlers/identity/oauth2-credential-provider/oauth2.fixture.test.tsx +++ b/src/handlers/identity/oauth2-credential-provider/oauth2.fixture.test.tsx @@ -32,8 +32,8 @@ function createFixtureCore(): CoreClient { }); } -async function run(args: string[]): Promise { - const io = testIO(); +async function run(args: string[], stdin?: string): Promise { + const io = testIO({ stdin }); const root = createRootHandler(createFixtureCore(), { io: io.io, logger: createSilentLogger(), @@ -46,41 +46,47 @@ async function run(args: string[]): Promise { describe("oauth2-credential-provider CRUDL", () => { test("creates an OAuth2 credential provider", async () => { - const stdout = await run([ - "identity", - "oauth2-credential-provider", - "create", - "--name", - FIXTURE_PROVIDER_NAME, - "--vendor", - "CustomOauth2", - "--client-id", - "fixture-client-id", - "--discovery-url", - "https://example.com/.well-known/openid-configuration", - "--client-secret", + const stdout = await run( + [ + "identity", + "oauth2-credential-provider", + "create", + "--name", + FIXTURE_PROVIDER_NAME, + "--vendor", + "CustomOauth2", + "--client-id", + "fixture-client-id", + "--discovery-url", + "https://example.com/.well-known/openid-configuration", + "--client-secret", + "-", + ], "fixture-secret", - ]); + ); matchGolden(FIXTURES, "create.golden.json", stdout); }); test("creates a second OAuth2 credential provider for pagination", async () => { - const stdout = await run([ - "identity", - "oauth2-credential-provider", - "create", - "--name", - FIXTURE_PROVIDER_NAME_2, - "--vendor", - "CustomOauth2", - "--client-id", - "fixture-client-id-2", - "--discovery-url", - "https://example.com/.well-known/openid-configuration", - "--client-secret", + const stdout = await run( + [ + "identity", + "oauth2-credential-provider", + "create", + "--name", + FIXTURE_PROVIDER_NAME_2, + "--vendor", + "CustomOauth2", + "--client-id", + "fixture-client-id-2", + "--discovery-url", + "https://example.com/.well-known/openid-configuration", + "--client-secret", + "-", + ], "fixture-secret-2", - ]); + ); matchGolden(FIXTURES, "create-2.golden.json", stdout); }); @@ -133,21 +139,24 @@ describe("oauth2-credential-provider CRUDL", () => { }); test("updates an OAuth2 credential provider", async () => { - const stdout = await run([ - "identity", - "oauth2-credential-provider", - "update", - "--name", - FIXTURE_PROVIDER_NAME, - "--vendor", - "CustomOauth2", - "--client-id", - "updated-client-id", - "--discovery-url", - "https://example.com/.well-known/openid-configuration", - "--client-secret", + const stdout = await run( + [ + "identity", + "oauth2-credential-provider", + "update", + "--name", + FIXTURE_PROVIDER_NAME, + "--vendor", + "CustomOauth2", + "--client-id", + "updated-client-id", + "--discovery-url", + "https://example.com/.well-known/openid-configuration", + "--client-secret", + "-", + ], "updated-secret", - ]); + ); matchGolden(FIXTURES, "update.golden.json", stdout); expect(JSON.parse(stdout).name).toBe(FIXTURE_PROVIDER_NAME); diff --git a/src/handlers/identity/oauth2-credential-provider/oauth2.test.tsx b/src/handlers/identity/oauth2-credential-provider/oauth2.test.tsx index 575e1ed46..185de45cb 100644 --- a/src/handlers/identity/oauth2-credential-provider/oauth2.test.tsx +++ b/src/handlers/identity/oauth2-credential-provider/oauth2.test.tsx @@ -67,8 +67,9 @@ const UPDATE_RESPONSE = { async function run( args: string[], core = new TestCoreClient(), + stdin?: string, ): Promise<{ core: TestCoreClient; stdout: string }> { - const io = testIO(); + const io = testIO({ stdin }); const root = createRootHandler(core, { io: io.io, logger: createSilentLogger(), @@ -287,6 +288,23 @@ describe("oauth2-credential-provider flag validation", () => { ], /requires one of --discovery-url or --authorization-server-metadata/, ], + [ + "create: --client-secret with an inline value", + [ + "identity", + "oauth2-credential-provider", + "create", + "--name", + "x", + "--vendor", + "CustomOauth2", + "--discovery-url", + "https://example.com", + "--client-secret", + "s-inline", + ], + /file:\/\//, + ], ] as const)("enforces vendor/config-mode rules for `%s`", async (_label, args, message) => { expect(run([...args])).rejects.toThrow(message); }); @@ -477,9 +495,10 @@ describe("OAuth2 update handler", () => { PROVIDER_NAME, ...vendorArgs, "--client-secret", - "updated-secret", + "-", ], core, + "updated-secret", ); const updateCall = core.identity.calls[1]; diff --git a/src/handlers/identity/oauth2-credential-provider/update/index.tsx b/src/handlers/identity/oauth2-credential-provider/update/index.tsx index 9df3e1ca4..fa438f265 100644 --- a/src/handlers/identity/oauth2-credential-provider/update/index.tsx +++ b/src/handlers/identity/oauth2-credential-provider/update/index.tsx @@ -45,11 +45,9 @@ export const createUpdateOauth2CredentialProviderHandler = (core: Core, io: AppI flag("vendor", "the OAuth2 vendor", z.string().optional()), flag( "client-secret", - "the client secret (inline, file://path, or -)", + "the client secret (file://path or - for stdin; inline values are rejected)", z.string().optional(), - { - sensitive: true, - }, + { sensitive: true }, ), flag( "client-secret-reference", @@ -131,7 +129,7 @@ export const createUpdateOauth2CredentialProviderHandler = (core: Core, io: AppI validateCompleteConfigKey(providerConfigMode, existing.oauth2ProviderConfigOutput); const resolver = new SourceResolver({ stdin: io.stdin }); - const clientSecret = await resolver.resolveText("client-secret", flags["client-secret"]); + const clientSecret = await resolver.resolveSecret("client-secret", flags["client-secret"]); const clientSecretConfig = hasSecretRef ? parseSecretReference("client-secret-reference", flags["client-secret-reference"]!) diff --git a/src/handlers/project/add/runtime/index.test.ts b/src/handlers/project/add/runtime/index.test.ts index adc4df843..e59297533 100644 --- a/src/handlers/project/add/runtime/index.test.ts +++ b/src/handlers/project/add/runtime/index.test.ts @@ -325,6 +325,10 @@ describe("project add runtime", () => { "--api-key is only available on template path", ["--name", "my_agent", ...byo, "--api-key", "-"], ], + [ + "--api-key rejects an inline secret value", + ["--name", "my_agent", ...template, "--api-key", "sk-inline"], + ], [ "invalid memory JSON schema", ["--name", "my_agent", ...template, "--memory", '{"mode":"invalid"}'], diff --git a/src/handlers/project/add/runtime/index.ts b/src/handlers/project/add/runtime/index.ts index ea900d648..4bdaacd6d 100644 --- a/src/handlers/project/add/runtime/index.ts +++ b/src/handlers/project/add/runtime/index.ts @@ -151,7 +151,7 @@ export const createAddRuntimeHandler = (config: AddProjectResourceConfig) => const entrypoint = flags.entrypoint ?? "main.py"; const source = new SourceResolver({ stdin: config.io.stdin }); - const apiKey = await source.resolveText("api-key", flags["api-key"]); + const apiKey = await source.resolveSecret("api-key", flags["api-key"]); if (flags["custom-docker-build-args"] && !flags.dockerfile && !flags["build-context-path"]) throw new InputValidationError(