Skip to content

Commit

Permalink
feat(dotenv): allow reading from .env files without granting env ac…
Browse files Browse the repository at this point in the history
…cess (#3306)
  • Loading branch information
jsejcksn committed Apr 19, 2023
1 parent 0f28d39 commit 29e2dc5
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 20 deletions.
17 changes: 7 additions & 10 deletions dotenv/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const RE_ExpandValue =

export function parse(
rawDotenv: string,
restrictEnvAccessTo: StringList = [],
restrictEnvAccessTo?: StringList,
): Record<string, string> {
const env: Record<string, string> = {};

Expand Down Expand Up @@ -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) : {};
Expand Down Expand Up @@ -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) : {};
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -344,7 +344,7 @@ function assertSafe(
conf: Record<string, string>,
confExample: Record<string, string>,
allowEmptyValues: boolean,
restrictEnvAccessTo: StringList = [],
restrictEnvAccessTo?: StringList,
) {
const currentEnv = readEnv(restrictEnvAccessTo);

Expand Down Expand Up @@ -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>,
Expand Down
60 changes: 50 additions & 10 deletions dotenv/mod_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ Deno.test("expand variables", () => {
"variables within and without brackets expanded",
);
});

Deno.test("stringify", async (t) => {
await t.step(
"basic",
Expand Down Expand Up @@ -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"],
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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`,
);
});
},
);

0 comments on commit 29e2dc5

Please sign in to comment.