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.
wekan/server/lib/tests/fileStoreStrategy.security....

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);
});
});