diff --git a/docs/source/guides/private.md b/docs/source/guides/private.md index 6715f0d4e..365dbe8cc 100644 --- a/docs/source/guides/private.md +++ b/docs/source/guides/private.md @@ -3,7 +3,7 @@ -Due to the possibility of leaking access tokens to users of your website or web application, we only support accessing private/gated models from server-side environments (e.g., Node.js) that have access to the process' environment variables. +The `env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN` field makes the provided token visible to every user of your application. Only use this when the user is explicitly entering their own token. Do not embed your own token with this field and use it at your own risk. @@ -61,3 +61,13 @@ process.env.HF_TOKEN = 'hf_...'; // ... rest of your code ``` + +In browser environments where environment variables are not available, you can set the token at runtime using the `env` object: + +```js +import { env } from '@huggingface/transformers'; + +// WARNING: This exposes the token to every user. +// Only use when the user provides their own token. +env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN = 'hf_...'; +``` diff --git a/src/env.js b/src/env.js index 6b95d800c..cf7ded1ff 100644 --- a/src/env.js +++ b/src/env.js @@ -124,6 +124,9 @@ const localModelPath = RUNNING_LOCALLY * @property {Object} customCache The custom cache to use. Defaults to `null`. Note: this must be an object which * implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. * If you wish, you may also return a `Promise` from the `match` function if you'd like to use a file path instead of `Promise`. + * @property {string|null} [DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN=null] Access token used when making requests to the Hugging Face Hub. + * This value is visible to every user of your application. Only set it when the user is explicitly providing + * their own token (e.g., via an input field). Do not use it for any other purpose and use at your own risk. */ /** @type {TransformersEnvironment} */ @@ -155,6 +158,8 @@ export const env = { useCustomCache: false, customCache: null, ////////////////////////////////////////////////////// + + DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN: null, } diff --git a/src/utils/hub.js b/src/utils/hub.js index 56c2a643c..dde860d6a 100755 --- a/src/utils/hub.js +++ b/src/utils/hub.js @@ -229,23 +229,22 @@ export async function getFile(urlOrPath) { const headers = new Headers(); headers.set('User-Agent', `transformers.js/${version}; is_ci/${IS_CI};`); - // Check whether we are making a request to the Hugging Face Hub. const isHFURL = isValidUrl(urlOrPath, ['http:', 'https:'], ['huggingface.co', 'hf.co']); if (isHFURL) { - // If an access token is present in the environment variables, - // we add it to the request headers. - // NOTE: We keep `HF_ACCESS_TOKEN` for backwards compatibility (as a fallback). - const token = process.env?.HF_TOKEN ?? process.env?.HF_ACCESS_TOKEN; + const token = env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN ?? process.env?.HF_TOKEN ?? process.env?.HF_ACCESS_TOKEN; if (token) { headers.set('Authorization', `Bearer ${token}`); } } return fetch(urlOrPath, { headers }); } else { - // Running in a browser-environment, so we use default headers - // NOTE: We do not allow passing authorization headers in the browser, - // since this would require exposing the token to the client. - return fetch(urlOrPath); + const headers = new Headers(); + const isHFURL = isValidUrl(urlOrPath, ['http:', 'https:'], ['huggingface.co', 'hf.co']); + if (isHFURL && env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN) { + headers.set('Authorization', `Bearer ${env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN}`); + } + const options = headers.size > 0 ? { headers } : undefined; + return fetch(urlOrPath, options); } } diff --git a/tests/utils/hub-auth.test.js b/tests/utils/hub-auth.test.js new file mode 100644 index 000000000..3e9dc8d95 --- /dev/null +++ b/tests/utils/hub-auth.test.js @@ -0,0 +1,28 @@ +import { env } from "../../src/transformers.js"; +import { getFile } from "../../src/utils/hub.js"; +import { jest } from "@jest/globals"; + +describe("Hub authorization", () => { + it("Attaches Authorization header when env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN is set", async () => { + const originalFetch = global.fetch; + const mockFetch = jest.fn(() => Promise.resolve(new Response(null))); + global.fetch = mockFetch; + + const originalHFToken = process.env.HF_TOKEN; + const originalHFAccessToken = process.env.HF_ACCESS_TOKEN; + delete process.env.HF_TOKEN; + delete process.env.HF_ACCESS_TOKEN; + + env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN = "hf_dummy"; + + await getFile("https://huggingface.co/any/model"); + + const headers = mockFetch.mock.calls[0][1].headers; + expect(headers.get("Authorization")).toBe("Bearer hf_dummy"); + + env.DANGEROUSLY_AVAILABLE_TO_EVERY_USER_HF_TOKEN = null; + global.fetch = originalFetch; + if (originalHFToken !== undefined) process.env.HF_TOKEN = originalHFToken; else delete process.env.HF_TOKEN; + if (originalHFAccessToken !== undefined) process.env.HF_ACCESS_TOKEN = originalHFAccessToken; else delete process.env.HF_ACCESS_TOKEN; + }); +});