Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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: 16 additions & 1 deletion lib/winston/transports/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module.exports = class File extends TransportStream {
this.eol = (typeof options.eol === 'string') ? options.eol : os.EOL;
this.tailable = options.tailable || false;
this.lazy = options.lazy || false;
this.allowSymlinks = (typeof options.allowSymlinks === 'boolean') ? options.allowSymlinks : true;

// Internal state variables representing the number of files this instance
// has created and the current size (in bytes) of the current logfile.
Expand Down Expand Up @@ -610,7 +611,21 @@ module.exports = class File extends TransportStream {
const fullpath = path.join(this.dirname, this.filename);

debug('create stream start', fullpath, this.options);
const dest = fs.createWriteStream(fullpath, this.options)

const streamOptions = Object.assign({}, this.options);
if (!this.allowSymlinks) {
/* eslint-disable no-bitwise */
if (streamOptions.flags === 'a') {
streamOptions.flags = fs.constants.O_APPEND | fs.constants.O_CREAT | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW;
} else if (streamOptions.flags === 'w') {
streamOptions.flags = fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW;
} else if (typeof streamOptions.flags === 'number') {
streamOptions.flags |= fs.constants.O_NOFOLLOW;
}
/* eslint-enable no-bitwise */
}

const dest = fs.createWriteStream(fullpath, streamOptions)
// TODO: What should we do with errors here?
.on('error', err => debug(err))
.on('close', () => debug('close', dest.path, dest.bytesWritten))
Expand Down
73 changes: 73 additions & 0 deletions test/unit/winston/transports/file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,79 @@ describe('File Transport', function () {
});
});

describe('Symlink Option', function () {
it('should not write to a symlink when allowSymlinks is false', async function () {
const targetFilename = 'target.log';
const symlinkFilename = 'symlink.log';
const targetPath = getFilePath(targetFilename);
const symlinkPath = getFilePath(symlinkFilename);

// Create target file
fs.writeFileSync(targetPath, 'initial content');

// Create symlink
try {
fs.symlinkSync(targetPath, symlinkPath);
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}

const transport = new winston.transports.File({
dirname: testLogFixturesPath,
filename: symlinkFilename,
allowSymlinks: false
});

// We expect this to fail silently or emit an error, but definitely NOT write to target
// Using a small amount of data to avoid filling the buffer and hanging
await logToTransport(transport, { kbytes: 1 });

// Give it a moment to potentially flush (or fail)
await new Promise(resolve => setTimeout(resolve, 100));

const content = fs.readFileSync(targetPath, 'utf8');
assert.strictEqual(
content,
'initial content',
'Target file should not be modified'
);
});

it('should write to a symlink when allowSymlinks is true (default)', async function () {
const targetFilename = 'target_default.log';
const symlinkFilename = 'symlink_default.log';
const targetPath = getFilePath(targetFilename);
const symlinkPath = getFilePath(symlinkFilename);

// Create target file
fs.writeFileSync(targetPath, 'initial content');

// Create symlink
try {
fs.symlinkSync(targetPath, symlinkPath);
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}

const transport = new winston.transports.File({
dirname: testLogFixturesPath,
filename: symlinkFilename
// allowSymlinks: true is default
});

await logToTransport(transport, { kbytes: 1 });

// Wait for write
await new Promise(resolve => setTimeout(resolve, 500));

const content = fs.readFileSync(targetPath, 'utf8');
assert.notStrictEqual(
content,
'initial content',
'Target file should be modified'
);
});
});

// TODO: Reintroduce these tests
//
Expand Down