-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(cli) Add command to CREATE an encrypted secret #2967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
AzamAbdul
merged 2 commits into
agent/browse-v4-7-context-names
from
feat/cli-create-secret-v4
Sep 23, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "browse": minor | ||
| --- | ||
|
|
||
| Add `browse cloud secrets create` with public-key lookup, local encryption, and secret input from stdin, a named environment variable, or a hidden prompt. |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { Args } from "@oclif/core"; | ||
| import { BrowseCommand } from "../../../base.js"; | ||
| import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js"; | ||
| import { createSecret } from "../../../lib/secrets/api.js"; | ||
| import { readSecretValue } from "../../../lib/secrets/input.js"; | ||
| import { secretInputFlags } from "../../../lib/secrets/flags.js"; | ||
| import { outputJson } from "../../../lib/output.js"; | ||
|
|
||
| export default class SecretsCreate extends BrowseCommand { | ||
| static override description = | ||
| "Create a project secret. Encrypts the value locally with the project public key."; | ||
| static override examples = [ | ||
| "browse cloud secrets create SERVICE_TOKEN", | ||
| "browse cloud secrets create SERVICE_TOKEN --env MY_SERVICE_TOKEN", | ||
| "browse cloud secrets create SERVICE_TOKEN --stdin < ./secret.txt", | ||
| ]; | ||
| static override args = { | ||
| key: Args.string({ | ||
| description: "Name exposed in the function context.secrets object.", | ||
| required: true, | ||
| }), | ||
| }; | ||
| static override flags = { ...apiCommonFlags, ...secretInputFlags }; | ||
| async run(): Promise<void> { | ||
| const { args, flags } = await this.parse(SecretsCreate); | ||
| const options = toApiOptions(flags); | ||
| const value = await readSecretValue({ stdin: flags.stdin, env: flags.env }); | ||
| try { | ||
| outputJson(await createSecret(options, args.key, value)); | ||
| } finally { | ||
| value.fill(0); | ||
| } | ||
| } | ||
| } | ||
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import password from "@inquirer/password"; | ||
| import { fail } from "../errors.js"; | ||
|
|
||
| export async function readSecretValue(options: { | ||
| stdin?: boolean; | ||
| env?: string; | ||
| }): Promise<Uint8Array> { | ||
| if (options.env !== undefined) { | ||
| if (options.stdin) fail("--env and --stdin cannot be used together."); | ||
| if (!options.env) fail("--env requires an environment variable name."); | ||
| const value = Object.prototype.hasOwnProperty.call(process.env, options.env) | ||
| ? process.env[options.env] | ||
| : undefined; | ||
| if (value === undefined) | ||
| fail("The environment variable selected by --env is not set."); | ||
| return Buffer.from(value, "utf8"); | ||
| } | ||
| if (options.stdin) { | ||
| if (process.stdin.isTTY) | ||
| fail("--stdin requires piped input or file redirection."); | ||
| const chunks: Buffer[] = []; | ||
| for await (const chunk of process.stdin) { | ||
| chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); | ||
| } | ||
| return Buffer.concat(chunks); | ||
| } | ||
| if (!process.stdin.isTTY) | ||
| fail( | ||
| "Use --stdin for piped input or --env to read an environment variable.", | ||
| ); | ||
| try { | ||
| return Buffer.from( | ||
| await password({ message: "Secret value:" }, { output: process.stderr }), | ||
| "utf8", | ||
| ); | ||
| } catch { | ||
| fail("Secret input cancelled."); | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { Aes256Gcm, CipherSuite, HkdfSha256 } from "@hpke/core"; | ||
| import { DhkemX25519HkdfSha256 } from "@hpke/dhkem-x25519"; | ||
| import { fail } from "../errors.js"; | ||
|
|
||
| export async function sealSecret( | ||
| publicKey: unknown, | ||
| value: Uint8Array, | ||
| ): Promise<string> { | ||
| if (typeof publicKey !== "string") { | ||
| fail("The secrets API returned an invalid X25519 public key."); | ||
| } | ||
| const rawKey = Buffer.from(publicKey, "base64"); | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| if (rawKey.length !== 32 || rawKey.toString("base64") !== publicKey) { | ||
| fail("The secrets API returned an invalid X25519 public key."); | ||
| } | ||
| const suite = new CipherSuite({ | ||
| kem: new DhkemX25519HkdfSha256(), | ||
| kdf: new HkdfSha256(), | ||
| aead: new Aes256Gcm(), | ||
| }); | ||
| try { | ||
| const recipientPublicKey = await suite.kem.deserializePublicKey( | ||
| new Uint8Array(rawKey).buffer, | ||
| ); | ||
| const sender = await suite.createSenderContext({ recipientPublicKey }); | ||
| const ciphertext = await sender.seal(new Uint8Array(value).buffer); | ||
| // Go's crypto/hpke.Open expects the encapsulated key followed by ciphertext. | ||
| return Buffer.concat([ | ||
| Buffer.from(sender.enc), | ||
| Buffer.from(ciphertext), | ||
| ]).toString("base64"); | ||
| } catch { | ||
| fail("Failed to encrypt the secret with the project's public key."); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.