Skip to content
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

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

Merged
merged 3 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`,
);
});
},
);