Skip to content
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
67 changes: 67 additions & 0 deletions packages/rollup-plugin-webbundle/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/

import test from 'ava';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as rollup from 'rollup';
import crypto from 'crypto';
Expand Down Expand Up @@ -146,6 +148,71 @@ test('static', async (t) => {
t.snapshot(parseWebBundle(output[keys[0]].source));
});

test('static with symbolic link throws error', async (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wbn-rollup-symlink-'));
try {
const targetFile = path.join(tmpDir, 'target.txt');
fs.writeFileSync(targetFile, 'sensitive data');
const symlinkFile = path.join(tmpDir, 'symlink.txt');
fs.symlinkSync(targetFile, symlinkFile);

const bundle = await rollup.rollup({
input: 'fixtures/index.js',
plugins: [
webbundle({
baseURL: 'https://wbn.example.com/',
output: 'out.wbn',
static: { dir: tmpDir },
}),
],
});

const error = await t.throwsAsync(
async () => {
await bundle.generate({ format: 'esm' });
},
{ instanceOf: Error }
);
t.is(
error.message,
`Refusing to bundle symbolic link at ${symlinkFile}. Replace it with a regular file or directory.`
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

test('static with directory-symlink loop throws refusal error without ELOOP', async (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wbn-rollup-loop-'));
try {
const loopLink = path.join(tmpDir, 'loop');
fs.symlinkSync('.', loopLink);

const bundle = await rollup.rollup({
input: 'fixtures/index.js',
plugins: [
webbundle({
output: 'out.wbn',
static: { dir: tmpDir },
}),
],
});

const error = await t.throwsAsync(
async () => {
await bundle.generate({ format: 'esm' });
},
{ instanceOf: Error }
);
t.is(
error.message,
`Refusing to bundle symbolic link at ${loopLink}. Replace it with a regular file or directory.`
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

test('relative', async (t) => {
const bundle = await rollup.rollup({
input: 'fixtures/index.js',
Expand Down
94 changes: 94 additions & 0 deletions packages/shared/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
*/

import test from 'ava';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { BundleBuilder } from 'wbn';
import { getValidatedOptionsWithDefaults } from '../lib/types.js';
import { addFilesRecursively } from '../lib/utils.js';
import * as wbnSign from 'wbn-sign';

const TEST_ED25519_PRIVATE_KEY = wbnSign.parsePemKey(
Expand Down Expand Up @@ -50,3 +55,92 @@ test('headerOverride - IWA with bad headers', async (t) => {
}
}
});

test('addFilesRecursively - refuses symbolic link to file', (t) => {
const tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'wbn-test-symlink-file-')
);
try {
const targetFile = path.join(tmpDir, 'target.txt');
fs.writeFileSync(targetFile, 'sensitive data');
const symlinkFile = path.join(tmpDir, 'symlink.txt');
fs.symlinkSync(targetFile, symlinkFile);

const builder = new BundleBuilder();
const error = t.throws(
() => {
addFilesRecursively(builder, 'https://example.com/', tmpDir, {
baseURL: 'https://example.com/',
output: 'out.wbn',
});
},
{ instanceOf: Error }
);
t.is(
error.message,
`Refusing to bundle symbolic link at ${symlinkFile}. Replace it with a regular file or directory.`
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

test('addFilesRecursively - refuses symbolic link to directory', (t) => {
const tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'wbn-test-symlink-dir-')
);
try {
const targetDir = path.join(tmpDir, 'target-dir');
fs.mkdirSync(targetDir);
fs.writeFileSync(path.join(targetDir, 'inner.txt'), 'sensitive data');
const symlinkDir = path.join(tmpDir, 'symlink-dir');
fs.symlinkSync(targetDir, symlinkDir);

const builder = new BundleBuilder();
const error = t.throws(
() => {
addFilesRecursively(builder, 'https://example.com/', tmpDir, {
baseURL: 'https://example.com/',
output: 'out.wbn',
});
},
{ instanceOf: Error }
);
t.is(
error.message,
`Refusing to bundle symbolic link at ${symlinkDir}. Replace it with a regular file or directory.`
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

test('addFilesRecursively - refuses root directory being a symbolic link', (t) => {
const tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'wbn-test-root-symlink-')
);
try {
const targetDir = path.join(tmpDir, 'target-dir');
fs.mkdirSync(targetDir);
fs.writeFileSync(path.join(targetDir, 'inner.txt'), 'some data');
const symlinkDir = path.join(tmpDir, 'symlink-root');
fs.symlinkSync(targetDir, symlinkDir);

const builder = new BundleBuilder();
const error = t.throws(
() => {
addFilesRecursively(builder, 'https://example.com/', symlinkDir, {
baseURL: 'https://example.com/',
output: 'out.wbn',
});
},
{ instanceOf: Error }
);
t.is(
error.message,
`Refusing to bundle symbolic link at ${symlinkDir}. Replace it with a regular file or directory.`
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
22 changes: 19 additions & 3 deletions packages/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import * as path from 'path';
import mime from 'mime';
import { combineHeadersForUrl, BundleBuilder } from 'wbn';
import { IntegrityBlockSigner } from 'wbn-sign';
import { checkAndAddIwaHeaders } from './iwa-headers';
import { ValidIbSignPluginOptions, ValidPluginOptions } from './types';
import { checkAndAddIwaHeaders } from './iwa-headers.js';
import { ValidIbSignPluginOptions, ValidPluginOptions } from './types.js';

// If the file name is 'index.html', create an entry for both baseURL/dir/ and
// baseURL/dir/index.html which redirects to the aforementioned. Otherwise just
Expand Down Expand Up @@ -84,13 +84,29 @@ export function addFilesRecursively(
pluginOptions: ValidPluginOptions,
recPath = ''
) {
if (recPath === '' && fs.lstatSync(dir).isSymbolicLink()) {
throw new Error(
`Refusing to bundle symbolic link at ${dir}. ` +
`Replace it with a regular file or directory.`
);
}

const files = fs.readdirSync(dir);
files.sort(); // Sort entries for reproducibility.

for (const fileName of files) {
const filePath = path.join(dir, fileName);

if (fs.statSync(filePath).isDirectory()) {
const stat = fs.lstatSync(filePath);
if (stat.isSymbolicLink()) {
// Refuse to follow symlinks so that files outside the static directory
// (credentials, SSH keys, etc.) cannot be pulled into the bundle.
throw new Error(
`Refusing to bundle symbolic link at ${filePath}. ` +
`Replace it with a regular file or directory.`
);
}
if (stat.isDirectory()) {
addFilesRecursively(
builder,
baseURL,
Expand Down
29 changes: 29 additions & 0 deletions packages/webbundle-webpack-plugin/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import webpack from 'webpack';
import MemoryFS from 'memory-fs';
import url from 'url';
import fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as wbn from 'wbn';
import * as wbnSign from 'wbn-sign';
Expand Down Expand Up @@ -101,6 +102,34 @@ test('static', async (t) => {
t.is(new TextDecoder('utf-8').decode(resp.body), html.toString());
});

test('static with symbolic link throws error', async (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wbn-webpack-symlink-'));
try {
const targetFile = path.join(tmpDir, 'target.txt');
fs.writeFileSync(targetFile, 'sensitive data');
const symlinkFile = path.join(tmpDir, 'symlink.txt');
fs.symlinkSync(targetFile, symlinkFile);

const error = await t.throwsAsync(
async () => {
await run({
baseURL: 'https://example.com/',
static: { dir: tmpDir },
output: 'example.wbn',
});
},
{ instanceOf: Error }
);
t.true(
error.message.includes(
`Refusing to bundle symbolic link at ${symlinkFile}. Replace it with a regular file or directory.`
)
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

test('relative', async (t) => {
const { memfs } = await run({
static: { dir: path.join(__dirname, 'fixtures', 'static') },
Expand Down
Loading