feat(dotenv): allow reading from .env files without granting env access (#3306)

This commit is contained in:
Jesse Jackson 2023-04-19 04:05:51 -05:00 committed by GitHub
parent 0f28d392e3
commit 29e2dc51e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 20 deletions

View File

@ -178,7 +178,7 @@ const RE_ExpandValue =
export function parse(
rawDotenv: string,
restrictEnvAccessTo: StringList = [],
restrictEnvAccessTo?: StringList,
): Record<string, string> {
const env: Record<string, string> = {};
@ -224,7 +224,7 @@ export function loadSync(
defaultsPath = ".env.defaults",
export: _export = false,
allowEmptyValues = false,
restrictEnvAccessTo = [],
restrictEnvAccessTo,
}: LoadOptions = {},
): Record<string, string> {
const conf = envPath ? parseFileSync(envPath, restrictEnvAccessTo) : {};
@ -268,7 +268,7 @@ export async function load(
defaultsPath = ".env.defaults",
export: _export = false,
allowEmptyValues = false,
restrictEnvAccessTo = [],
restrictEnvAccessTo,
}: LoadOptions = {},
): Promise<Record<string, string>> {
const conf = envPath ? await parseFile(envPath, restrictEnvAccessTo) : {};
@ -305,7 +305,7 @@ export async function load(
function parseFileSync(
filepath: string,
restrictEnvAccessTo: StringList = [],
restrictEnvAccessTo?: StringList,
): Record<string, string> {
try {
return parse(Deno.readTextFileSync(filepath), restrictEnvAccessTo);
@ -317,7 +317,7 @@ function parseFileSync(
async function parseFile(
filepath: string,
restrictEnvAccessTo: StringList = [],
restrictEnvAccessTo?: StringList,
): Promise<Record<string, string>> {
try {
return parse(await Deno.readTextFile(filepath), restrictEnvAccessTo);
@ -344,7 +344,7 @@ function assertSafe(
conf: Record<string, string>,
confExample: Record<string, string>,
allowEmptyValues: boolean,
restrictEnvAccessTo: StringList = [],
restrictEnvAccessTo?: StringList,
) {
const currentEnv = readEnv(restrictEnvAccessTo);
@ -381,10 +381,7 @@ function assertSafe(
// a guarded env access, that reads only a subset from the Deno.env object,
// if `restrictEnvAccessTo` property is passed.
function readEnv(restrictEnvAccessTo: StringList) {
if (
restrictEnvAccessTo && Array.isArray(restrictEnvAccessTo) &&
restrictEnvAccessTo.length > 0
) {
if (restrictEnvAccessTo && Array.isArray(restrictEnvAccessTo)) {
return restrictEnvAccessTo.reduce(
(
accessedEnvVars: Record<string, string>,

View File

@ -581,6 +581,7 @@ Deno.test("expand variables", () => {
"variables within and without brackets expanded",
);
});
Deno.test("stringify", async (t) => {
await t.step(
"basic",
@ -758,7 +759,7 @@ Deno.test("type inference based on restrictEnvAccessTo", async (t) => {
});
Deno.test(
"prevent file systems reads of default path parameter values by using explicit null",
"prevent file system reads of default path parameter values by using explicit null",
{
permissions: {
env: ["GREETING", "DO_NOT_OVERRIDE"],
@ -785,13 +786,15 @@ Deno.test(
examplePath: null,
} satisfies LoadOptions;
await t.step("load", async () => {
assertStrictEquals(Object.keys(await load(optsNoPaths)).length, 0);
const env = await load(optsOnlyEnvPath);
const assertEnv = (env: Record<string, string>): void => {
assertStrictEquals(Object.keys(env).length, 2);
assertStrictEquals(env["GREETING"], "hello world");
assertStrictEquals(env["DO_NOT_OVERRIDE"], "overridden");
};
await t.step("load", async () => {
assertStrictEquals(Object.keys(await load(optsNoPaths)).length, 0);
assertEnv(await load(optsOnlyEnvPath));
assertRejects(
() => load(optsEnvPath),
@ -814,11 +817,7 @@ Deno.test(
await t.step("loadSync", () => {
assertStrictEquals(Object.keys(loadSync(optsNoPaths)).length, 0);
const env = loadSync(optsOnlyEnvPath);
assertStrictEquals(Object.keys(env).length, 2);
assertStrictEquals(env["GREETING"], "hello world");
assertStrictEquals(env["DO_NOT_OVERRIDE"], "overridden");
assertEnv(loadSync(optsOnlyEnvPath));
assertThrows(
() => loadSync(optsEnvPath),
@ -840,3 +839,44 @@ Deno.test(
});
},
);
Deno.test(
"use restrictEnvAccessTo with empty array to prevent env access and read only from fs",
{ permissions: { read: [testOptions.envPath] } },
async (t) => {
const optsOnlyEnvPath = {
envPath: testOptions.envPath,
defaultsPath: null,
examplePath: null,
} satisfies LoadOptions;
const optsNoEnvAccess = {
...optsOnlyEnvPath,
restrictEnvAccessTo: [],
} satisfies LoadOptions;
const assertEnv = (env: Record<string, string>): void => {
assertStrictEquals(Object.keys(env).length, 2);
assertStrictEquals(env["GREETING"], "hello world");
assertStrictEquals(env["DO_NOT_OVERRIDE"], "overridden");
};
await t.step("load", async () => {
assertEnv(await load(optsNoEnvAccess));
assertRejects(
() => load(optsOnlyEnvPath),
Deno.errors.PermissionDenied,
`Requires env access to all, run again with the --allow-env flag`,
);
});
await t.step("loadSync", () => {
assertEnv(loadSync(optsNoEnvAccess));
assertThrows(
() => loadSync(optsOnlyEnvPath),
Deno.errors.PermissionDenied,
`Requires env access to all, run again with the --allow-env flag`,
);
});
},
);