Skip to content

onInit can only access secrets if they are bound to the calling function - make secrets available for initializing global value? #1694

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

Open
pklitscher opened this issue May 21, 2025 · 1 comment

Comments

@pklitscher
Copy link

pklitscher commented May 21, 2025

Related issues

[REQUIRED] Version info

node: v20.18.1

firebase-functions: 6.3.2

firebase-tools: 14.4.0

firebase-admin: 13.4.0

[REQUIRED] Test case

index.ts

import { onInit } from "firebase-functions/v2";
import { onRequest } from "firebase-functions/https";
import { defineSecret } from "firebase-functions/params";

const SECRET = defineSecret("SECRET");

let coolApi: null | CoolApi = null;

onInit(() => {
  coolApi = new CoolApi(SECRET.value());
});

/*
  Can we do something like this??
  onInit(
    { secrets: [SECRET] },
    () => {
      coolApi = new CoolApi(SECRET.value());
    });
*/

export const joke1 = onRequest(
  { secrets: [SECRET] },
  async (_req, res) => {
    try {
      const joke = await coolApi!.tellJoke();
      res.status(200).send(joke);
    } catch (error) {
      console.error("Error: ", error);
      res.status(500).send("Error fetching joke");
    }
  },
);

export const joke2 = onRequest(
  async (_req, res) => {
    try {
      const joke = await coolApi!.tellJoke();
      res.status(200).send(joke);
    } catch (error) {
      console.error("Error: ", error);
      res.status(500).send("Error fetching joke");
    }
  },
);

class CoolApi {
  private apiKey: string;

  constructor(apiKey: string) {
    if (apiKey) {
      throw new Error("API key must be supplied");
    }
    this.apiKey = apiKey;
  }

  async tellJoke(): Promise<unknown> {
    if (!this.apiKey) {
      throw new Error("API key is not set");
    }
    const response = await fetch(`https://official-joke-api.appspot.com/random_joke?apiKey=${this.apiKey}`); // doesn't really need apikey
    console.log("Response: ", response);
    return response.json();
  }
}

[REQUIRED] Steps to reproduce

  1. Deploy functions
  2. Go to link of joke1 function - joke is returned
  3. Go to link of joke2 function - errors & no joke is returned

[REQUIRED] Expected behavior

Allow secrets to be bound to onInit() (or another initialization callback) so that global value can be initialized without having secrets declared in every Cloud Function consuming the global value.

Let's say we use a library initialized with an API key. It is heavily integrated to our firebase project and used in a lot of Cloud Functions and functions across the codebase. For example:

import {GoogleGenAI} from '@google/genai';
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;

const ai = new GoogleGenAI({apiKey: GEMINI_API_KEY});
...
// taken from @google/genai

// or in Firebase
import { GoogleGenAI } from "@google/genai";
import { defineSecret } from "firebase-functions/params";

const GEMINI_API_KEY = defineSecret("GEMINI_API_KEY");

let ai;
onInit(() => {
  ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY });
});

The issue is there is no way to check that the necessary secrets have been bound to all the Cloud Functions consuming the global value. This can be problematic as the project grows, or a Cloud Function is modified to use the API when it was not previously. Even more so when the API is used in shared functions that are called by Cloud Functions. The shared function is removed from the context of the Cloud Function where the secrets would be bound. The only way to surface this issue is at runtime.

Finally in the example above. If I do this

onInit(() => {
  coolApi = new CoolApi(SECRET.value());
});

This will error whenever a Cloud Function is called that does not have SECRET bound to it. I could check if SECRET.value() is falsy before trying to initialize CoolApi - but it would then fail silently if it was ever undefined and should not be. I have no way of differentiating if it should be falsy or not.

I guess making a secret available in the global scope when it was not bound to a function introduces vulnerabilities??

We are migrating from firebase.config and having to trace back to the originating Cloud Function for every function that uses a globally declared API

It would be insanely useful to be able to initialize a global value and know it is going to be initialized properly and not wait to find out at runtime.

[REQUIRED] Actual behavior

Secrets cannot be accessed by onInit() unless they were bound to the Cloud Function that will use the global value. There is no way to ensure the Cloud Function has a secret bound until it fails at runtime

Were you able to successfully deploy your functions?

Yes

@google-oss-bot
Copy link
Collaborator

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants