Skip to content

Integrate .env secret bindings with param prompting#10783

Merged
Berlioz merged 12 commits into
vsfan_env_params_usefrom
vsfan_env_param_prompting
Jul 15, 2026
Merged

Integrate .env secret bindings with param prompting#10783
Berlioz merged 12 commits into
vsfan_env_params_usefrom
vsfan_env_param_prompting

Conversation

@Berlioz

@Berlioz Berlioz commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This PR centers around changes made to the handleSecret()/now ensureSecret() method invoked during param resolution. Formerly, handleSecret() only made sure that a Cloud Secret with a resource name matching the name of the Secret Param existed with a readable latest version.

Now:

  1. It can use an alternative resource name and version identifier from the Secret Param, which has been merged into the Param object earlier in deployment by applyEnvSecretBindings().
  2. It emits a string representing the secret binding which should be written to .env files. These strings are propagated up the call chain to prepare.ts, which has them inserted to .env files only if not present using the same mechanisms that currently write other params.
  3. It logs an INFO-level notification in the case where the backing Cloud Secret already existed but no matching record in .env files existed, in which case we are basically "importing" a secret that already existed. Open to wording suggestions here, especially if we should specifically advise users to commit to version control.

Intended behavior after merging this PR on deploy:

  • Secret Param exists, .env secret exists: applyEnvSecretBindings seamlessly overrides the Secret to look in the place specified by .env, which satisfies ensureSecret().
  • Secret Param missing, .env secret exists: no-op; since defineSecret() was never called during discovery, no corresponding record exists in build.SecretEnvVars and applyEnvSecretBindings skips the .env secret.
  • Secret Param exists, .env secret missing: ensureSecret() checks to see whether a Secret resource exists at the "default" secret location. If so, it logs a warning and writes that default secret to .envs. Otherwise, it helps the user create a secret and upon success writes it to .env files.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for writing backing resource bindings for Functions secret parameters to .env files under the secretEnvParams experiment. The changes include updating resolveBackend and resolveParams to return resolved secret references, adding new fields to SecretParam, and implementing writeResolvedSecretRefs to persist these bindings. The review feedback highlights critical issues regarding case-sensitivity mismatches between camelCase secret names and uppercase .env keys in both applyEnvSecretBindings and writeResolvedSecretRefs, which could lead to deployment failures or duplicate keys. Additionally, the feedback suggests querying the specific pinned secret version instead of hardcoding 'latest' when retrieving secret metadata.

Comment thread src/deploy/functions/build.ts Outdated
Comment on lines +822 to +828
for (const secret of build.params.filter((param) => param.type === "secret")) {
if (secret.name === key) {
secret.resourceId = secretId;
secret.version = version;
secret.inLocalEnvironment = true;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using .filter() here loses the TypeScript type narrowing, meaning secret is still typed as the union Param rather than SecretParam. This will cause a compilation error when attempting to assign secret.resourceId, secret.version, and secret.inLocalEnvironment under strict null/type checks.

Additionally, comparing secret.name === key is case-sensitive. Since .env keys are validated to be uppercase (e.g., APIKEY), but secret parameter names can be camelCase (e.g., apiKey), this comparison will fail to match.

We can resolve both issues and optimize performance by using a simple for...of loop with case-insensitive comparison and type narrowing.

Suggested change
for (const secret of build.params.filter((param) => param.type === "secret")) {
if (secret.name === key) {
secret.resourceId = secretId;
secret.version = version;
secret.inLocalEnvironment = true;
}
}
for (const param of build.params) {
if (param.type === "secret" && param.name.toUpperCase() === key.toUpperCase()) {
param.resourceId = secretId;
param.version = version;
param.inLocalEnvironment = true;
}
}
References
  1. TypeScript: Never use any or unknown as an escape hatch. Define proper interfaces/types or use type guards. Use strict null checks and handle undefined/null explicitly. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that...true? I mean the case thing definitely is true, fixed.

Comment thread src/deploy/functions/params.ts Outdated
const version = secretParam.version || "latest";
let secretAlreadyExisted = false;

const metadata = await secretManager.getSecretMetadata(projectId, resourceId, "latest");

This comment was marked as resolved.

Comment thread src/functions/env.ts
Comment on lines +454 to +470
export function writeResolvedSecretRefs(
resolvedSecretRefs: Readonly<Record<string, string>>,
haveSecretRefs: Readonly<Record<string, string>>,
userEnvOpt: UserEnvsOpts,
): void {
const toWrite: Record<string, string> = {};

for (const secretName of Object.keys(resolvedSecretRefs)) {
const resolvedRef = resolvedSecretRefs[secretName];
if (!Object.prototype.hasOwnProperty.call(haveSecretRefs, secretName)) {
const reservedKey = "FIREBASE_SECRET_REF_" + secretName.toUpperCase();
toWrite[reservedKey] = resolvedRef;
}
}

writeUserEnvs(toWrite, userEnvOpt);
}

This comment was marked as resolved.

@wiz-9635d3485b

wiz-9635d3485b Bot commented Jul 9, 2026

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings 1 Medium
Software Management Finding Software Management Findings -
Total 1 Medium

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.

@Berlioz Berlioz changed the title WIP: Integrate .env secret bindings with param prompting Integrate .env secret bindings with param prompting Jul 9, 2026
@Berlioz
Berlioz requested review from ajperel and inlined July 9, 2026 22:33
Comment thread src/deploy/functions/params.ts Outdated
}

const secretRefString =
typeof version === "undefined" ? "resourceId" : `${resourceId}:${version}`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you mean resourceId to be quoted here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sad trombone noises

Comment thread src/deploy/functions/params.ts Outdated
}. Enter ${secretParam.format === "json" ? "a JSON value" : "a value"} for ${
if (experiments.isEnabled("secretEnvParams")) {
// TODO: Move the explanation and link to Cloud Secret Manager in the next prompt here once this makes it out of experimental.
resourceId = await input({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Did we want to default to the existing behavior if the secret doesn't already exist (YOLO secret = key)?
  2. I thought we wanted to confirm loop before reusing a secret
  3. If reusing a secret we shouldn't needt o create & add a version.

@Berlioz Berlioz Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e:

  1. I don't think so--we talked it over in chat and this would break flows where the user had multiple environments deployed in the same project. Added --force support though.
  2. I thought so too but I was working out the logic in my head and right now, if the secret already exists and was created manually or with secrets:set or whatever, we would just pull it in without asking, right? This doesn't actually change the current behavior of secrets?
  3. Ack. Recursing like this seems like the most elegant way of handling the input-a-reused-secret case.

} is in illegal state ${metadata.secretVersion.state}`,
);
} else {
secretAlreadyExisted = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I reading diffs wrong? It looks like this is set if the experiment is false?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staring at my monitor for a bit and trusting vs code's brace matching, I'm pretty sure this is the fall-through for the big "what's the state of the referenced secret" if block that begins if (!metadata.secret)

Comment thread src/functions/env.ts
}
}

writeUserEnvs(toWrite, userEnvOpt);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it safe to call writeUserEnvs repeatedly in different places? Does this append? If so, maybe wee should rename it appendUserEnvs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appends yeah. I did want to maintain some sense that this is a filesystem touch when skimming the code though, maybe writeNewUserEnvs()?

@Berlioz
Berlioz force-pushed the vsfan_env_param_prompting branch from 41c5985 to 5a993d6 Compare July 13, 2026 22:53
@Berlioz
Berlioz merged commit 353da19 into vsfan_env_params_use Jul 15, 2026
30 of 33 checks passed
@Berlioz
Berlioz deleted the vsfan_env_param_prompting branch July 15, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants