mirror of https://github.com/wekan/wekan
The Open Source kanban (built with Meteor). Keep variable/table/field names camelCase. For translations, only add Pull Request changes to wekan/i18n/en.i18n.json , other translations are done at https://transifex.com/wekan/wekan only.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
108 lines
3.0 KiB
108 lines
3.0 KiB
/* eslint-env mocha */
|
|
import fs from 'fs';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
import { expect } from 'chai';
|
|
import { Random } from 'meteor/random';
|
|
import { FileStoreStrategyFilesystem } from '/models/lib/fileStoreStrategy';
|
|
|
|
function readStreamToString(stream) {
|
|
return new Promise((resolve, reject) => {
|
|
const chunks = [];
|
|
stream.on('data', (chunk) => chunks.push(chunk));
|
|
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
stream.on('error', reject);
|
|
});
|
|
}
|
|
|
|
describe('fileStoreStrategy security', function() {
|
|
let tempRoot;
|
|
let storageRoot;
|
|
let originalWritablePath;
|
|
|
|
beforeEach(function() {
|
|
originalWritablePath = process.env.WRITABLE_PATH;
|
|
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'wekan-fss-'));
|
|
storageRoot = path.join(tempRoot, 'files', 'attachments');
|
|
fs.mkdirSync(storageRoot, { recursive: true });
|
|
process.env.WRITABLE_PATH = tempRoot;
|
|
});
|
|
|
|
afterEach(function() {
|
|
if (typeof originalWritablePath === 'string') {
|
|
process.env.WRITABLE_PATH = originalWritablePath;
|
|
} else {
|
|
delete process.env.WRITABLE_PATH;
|
|
}
|
|
|
|
try {
|
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
} catch (err) {
|
|
// Ignore cleanup errors in tests.
|
|
}
|
|
});
|
|
|
|
function createAttachmentFileObj(filePath) {
|
|
return {
|
|
_id: Random.id(),
|
|
name: 'proof.txt',
|
|
collectionName: 'attachments',
|
|
versions: {
|
|
original: {
|
|
path: filePath,
|
|
storage: 'fs',
|
|
type: 'text/plain',
|
|
size: 4,
|
|
meta: {},
|
|
},
|
|
},
|
|
meta: {
|
|
boardId: Random.id(),
|
|
},
|
|
};
|
|
}
|
|
|
|
it('denies absolute path outside attachment storage root', function() {
|
|
const externalPath = path.join(tempRoot, 'outside-secret.txt');
|
|
fs.writeFileSync(externalPath, 'secret');
|
|
|
|
const strategy = new FileStoreStrategyFilesystem(
|
|
createAttachmentFileObj(externalPath),
|
|
'original',
|
|
);
|
|
|
|
const stream = strategy.getReadStream();
|
|
expect(stream).to.equal(undefined);
|
|
});
|
|
|
|
it('allows reading a regular file from storage root', async function() {
|
|
const insidePath = path.join(storageRoot, 'safe.txt');
|
|
fs.writeFileSync(insidePath, 'safe-data');
|
|
|
|
const strategy = new FileStoreStrategyFilesystem(
|
|
createAttachmentFileObj(insidePath),
|
|
'original',
|
|
);
|
|
|
|
const stream = strategy.getReadStream();
|
|
expect(stream).to.not.equal(undefined);
|
|
|
|
const content = await readStreamToString(stream);
|
|
expect(content).to.equal('safe-data');
|
|
});
|
|
|
|
it('denies symlink inside storage root that points outside', function() {
|
|
const externalPath = path.join(tempRoot, 'outside-via-link.txt');
|
|
fs.writeFileSync(externalPath, 'linked-secret');
|
|
const symlinkPath = path.join(storageRoot, 'escape-link.txt');
|
|
fs.symlinkSync(externalPath, symlinkPath);
|
|
|
|
const strategy = new FileStoreStrategyFilesystem(
|
|
createAttachmentFileObj(symlinkPath),
|
|
'original',
|
|
);
|
|
|
|
const stream = strategy.getReadStream();
|
|
expect(stream).to.equal(undefined);
|
|
});
|
|
}); |