Skip to content

Commit

Permalink
fix: error if a user inadvertently uploads a Pages _workers.js file…
Browse files Browse the repository at this point in the history
… or directory as an asset (#6762)

* fix: add a warning if a user inadvertently uploads a Pages _workers.js file as an asset

Fixes N/A

* fix: add a warning if a user inadvertently uploads a Pages _workers.js file as an asset

Fixes N/A

* fix: add a warning if a user inadvertently uploads a Pages _workers.js file as an asset

Fixes N/A

* fix: add a warning if a user inadvertently uploads a Pages _workers.js file as an asset

Fixes N/A
  • Loading branch information
petebacondarwin authored Sep 20, 2024
1 parent fd68f6b commit 2840b9f
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-worms-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

fix: error if a user inadvertently uploads a Pages `_workers.js` file or directory as an asset
143 changes: 143 additions & 0 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4555,6 +4555,149 @@ addEventListener('fetch', event => {});`
`);
});

it("should error if it is going to upload a _worker.js file as an asset", async () => {
const assets = [
{ filePath: "_worker.js", content: "// some secret server-side code." },
];
writeAssets(assets, "some/path/assets");
writeWranglerToml(
{
experimental_assets: { directory: "assets" },
},
"some/path/wrangler.toml"
);
const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedExperimentalAssets: {
jwt: "<<aus-completion-token>>",
config: {},
},
expectedType: "none",
});
await expect(runWrangler("deploy --config some/path/wrangler.toml"))
.rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: Uploading a Pages _worker.js file as an asset.
This could expose your private server-side code to the public Internet. Is this intended?
If you do not want to upload this file, either remove it or add an ".assetsignore" file, to the root of your asset directory, containing "_worker.js" to avoid uploading.
If you do want to upload this file, you can add an empty ".assetsignore" file, to the root of your asset directory, to hide this error.]
`);
});

it("should error if it is going to upload a _worker.js directory as an asset", async () => {
const assets = [
{
filePath: "_worker.js/index.js",
content: "// some secret server-side code.",
},
{
filePath: "_worker.js/dep.js",
content: "// some secret server-side code.",
},
];
writeAssets(assets, "some/path/assets");
writeWranglerToml(
{
experimental_assets: { directory: "assets" },
},
"some/path/wrangler.toml"
);
const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedExperimentalAssets: {
jwt: "<<aus-completion-token>>",
config: {},
},
expectedType: "none",
});
await expect(runWrangler("deploy --config some/path/wrangler.toml"))
.rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: Uploading a Pages _worker.js directory as an asset.
This could expose your private server-side code to the public Internet. Is this intended?
If you do not want to upload this directory, either remove it or add an ".assetsignore" file, to the root of your asset directory, containing "_worker.js" to avoid uploading.
If you do want to upload this directory, you can add an empty ".assetsignore" file, to the root of your asset directory, to hide this error.]
`);
});

it("should not error if it is going to upload a _worker.js file as an asset and there is an .assetsignore file", async () => {
const assets = [
{ filePath: ".assetsignore", content: "" },
{ filePath: "_worker.js", content: "// some secret server-side code." },
];
writeAssets(assets, "some/path/assets");
writeWranglerToml(
{
experimental_assets: { directory: "assets" },
},
"some/path/wrangler.toml"
);
const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedExperimentalAssets: {
jwt: "<<aus-completion-token>>",
config: {},
},
expectedType: "none",
});
await runWrangler("deploy --config some/path/wrangler.toml");
expect(bodies.length).toBe(1);
expect(bodies[0]).toMatchInlineSnapshot(`
Object {
"manifest": Object {
"/_worker.js": Object {
"hash": "266570622a24a5fb8913d53fd3ac8562",
"size": 32,
},
},
}
`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});

it("should not error if it is going to upload a _worker.js file that is not at the root of the asset directory", async () => {
const assets = [
{
filePath: "foo/_worker.js",
content: "// some secret server-side code.",
},
];
writeAssets(assets, "some/path/assets");
writeWranglerToml(
{
experimental_assets: { directory: "assets" },
},
"some/path/wrangler.toml"
);
const bodies: AssetManifest[] = [];
await mockAUSRequest(bodies);
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedExperimentalAssets: {
jwt: "<<aus-completion-token>>",
config: {},
},
expectedType: "none",
});
await runWrangler("deploy --config some/path/wrangler.toml");
expect(bodies.length).toBe(1);
expect(bodies[0]).toMatchInlineSnapshot(`
Object {
"manifest": Object {
"/foo/_worker.js": Object {
"hash": "266570622a24a5fb8913d53fd3ac8562",
"size": 32,
},
},
}
`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});

it("should resolve assets directory relative to cwd if using cli", async () => {
const assets = [{ filePath: "file-1.txt", content: "Content of file-1" }];
writeAssets(assets, "some/path/assets");
Expand Down
38 changes: 32 additions & 6 deletions packages/wrangler/src/experimental-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { logger, LOGGER_LEVELS } from "./logger";
import { hashFile } from "./pages/hash";
import { isJwtExpired } from "./pages/upload";
import { APIError } from "./parse";
import { dedent } from "./utils/dedent";
import { createPatternMatcher } from "./utils/filesystem";
import type { Config } from "./config";
import type { ExperimentalAssets } from "./config/environment";
Expand Down Expand Up @@ -228,21 +229,21 @@ export const buildAssetManifest = async (dir: string) => {
const ignoreFn = await createAssetIgnoreFunction(dir);

await Promise.all(
files.map(async (file) => {
const filepath = path.join(dir, file);
const relativeFilepath = path.relative(dir, filepath);

files.map(async (relativeFilepath) => {
if (ignoreFn?.(relativeFilepath)) {
logger.debug("Ignoring asset:", relativeFilepath);
// This file should not be included in the manifest.
return;
}

const filepath = path.join(dir, relativeFilepath);
const filestat = await stat(filepath);

if (filestat.isSymbolicLink() || filestat.isDirectory()) {
return;
} else {
errorOnLegacyPagesWorkerJSAsset(relativeFilepath, !!ignoreFn);

if (counter >= MAX_ASSET_COUNT) {
throw new UserError(
`Maximum number of assets exceeded.\n` +
Expand Down Expand Up @@ -413,15 +414,15 @@ export function verifyMutuallyExclusiveAssetsArgsOrConfig(
}
}

const CF_ASSETS_IGNORE_FILENAME = ".assetsignore";

/**
* Create a function for filtering out ignored assets.
*
* The generated function takes an asset path, relative to the asset directory,
* and returns true if the asset should not be ignored.
*/
async function createAssetIgnoreFunction(dir: string) {
const CF_ASSETS_IGNORE_FILENAME = ".assetsignore";

const cfAssetIgnorePath = path.resolve(dir, CF_ASSETS_IGNORE_FILENAME);

if (!existsSync(cfAssetIgnorePath)) {
Expand All @@ -437,3 +438,28 @@ async function createAssetIgnoreFunction(dir: string) {

return createPatternMatcher(ignorePatterns, true);
}

/**
* Creates a function that logs a warning (only once) if the project has no `.assetsIgnore` file and is uploading _worker.js code as an asset.
*/
function errorOnLegacyPagesWorkerJSAsset(
file: string,
hasAssetsIgnoreFile: boolean
) {
if (!hasAssetsIgnoreFile) {
const workerJsType: "file" | "directory" | null =
file === "_worker.js"
? "file"
: file.startsWith("_worker.js")
? "directory"
: null;
if (workerJsType !== null) {
throw new UserError(dedent`
Uploading a Pages _worker.js ${workerJsType} as an asset.
This could expose your private server-side code to the public Internet. Is this intended?
If you do not want to upload this ${workerJsType}, either remove it or add an "${CF_ASSETS_IGNORE_FILENAME}" file, to the root of your asset directory, containing "_worker.js" to avoid uploading.
If you do want to upload this ${workerJsType}, you can add an empty "${CF_ASSETS_IGNORE_FILENAME}" file, to the root of your asset directory, to hide this error.
`);
}
}
}

0 comments on commit 2840b9f

Please sign in to comment.