Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@danieldietrich/copy
Advanced tools
Simple yet powerful copy tool.
The copy tool recursively copies files, directories and links from a source directory to a destination directory.
Features:
The copy tool intentionally does not
npm i @danieldietrich/copy
The module supports ES6 import and CommonJS require style.
import copy from '@danieldietrich/copy';
(async function() {
// Performs a dry run of copying ./node_modules to ./temp
const totals = await copy('node_modules', 'temp', { dryRun: true });
console.log('Totals:', totals);
})();
Totals contains information about the copy operation:
{
directories: 1338,
files: 7929,
symlinks: 48,
size: 87873775
}
The number of directories, files and symlinks corresponds to the source. The size reflects the number of written bytes. In particular, the size might be smaller than the source, if existing files are not ovewritten.
See also option precedence.
const copy = require('@danieldietrich/copy');
const path = require('path');
(async function() {
// move dist/ dir contents to parent dir and rename index.js files to index.mjs
const rename = (source, target) => {
if (source.stats.isDirectory() && source.path.endsWith('/dist')) {
return path.dirname(target.path);
} else if (source.stats.isFile() && source.path.endsWith('/index.js')) {
return path.join(path.dirname(target.path), 'index.mjs');
} else {
return;
}
};
// recursively copy all .js files
const filter = (source, target) =>
source.stats.isDirectory() || source.stats.isFile() && source.path.endsWith('.js');
// transform the contents of all index.mjs files to upper case
const transform = (data, source, target) => {
if (source.stats.isFile() && target.path.endsWith('/index.mjs')) {
return Buffer.from(data.toString('utf8').toUpperCase(), 'utf8');
} else {
return data;
}
};
// log some information about copied files
const afterEach = (source, target, options) => {
const dryRun = options.dryRun ? '[DRY RUN] ' : '';
if (source.stats.isDirectory()) {
console.log(`${dryRun}Created ${target.path}`);
} else {
// target.stats is undefined on a dry run if the target does not already exist!
const size = target.stats?.size || '?';
console.log(`${dryRun}Copied ${source.path} to ${target.path} (${size} bytes)`);
}
};
const totals = await copy('node_modules', 'temp', {
rename,
filter,
transform,
afterEach
});
console.log('Totals:', totals);
})();
In the following example we change the file owner uid. A chgrp or chmod may be performed in a similar way.
import * as copy from '@danieldietrich/copy';
import * as fs from 'fs';
const { lchown } = fs.promises;
async function changeOwner(src: string, dst: string, uid: number) {
copy(src, dst, {
afterEach: async (source, target) => {
lchown(target.path, uid, source.stats.gid);
}
});
}
import * as copy from '@danieldietrich/copy';
async function copyWithProgress(src: string, dst: string, callback: (curr: copy.Totals, sum: copy.Totals) => void) {
const curr: copy.Totals = {
directories: 0,
files: 0,
symlinks: 0,
size: 0
};
const sum = await copy(src, dst, { dryRun: true });
const interval = 100; // ms
let update = Date.now();
await copy(src, dst, { afterEach: (source) => {
if (source.stats.isDirectory()) {
curr.directories += 1;
} else if (source.stats.isFile()) {
curr.files += 1;
curr.size += source.stats.size;
} else if (source.stats.isSymbolicLink()) {
curr.symlinks += 1;
curr.size += source.stats.size;
}
if (Date.now() - update >= interval) {
update = Date.now();
callback(curr, sum);
}
}});
callback(sum, sum);
}
// usage
(async function() {
copyWithProgress('node_modules', 'temp', (curr, sum) => {
const progress = Math.min(100, Math.floor(curr.size / sum.size * 100));
console.log(`${Number.parseFloat(progress).toFixed(1)} %`);
});
})();
The general signature of copy is:
async function copy(sourcePath: string, targetPath: string, options?: copy.Options): Promise<copy.Totals>;
The public types are:
// compatible to fs-extra.copy
type Options = {
overwrite?: boolean;
errorOnExist?: boolean;
dereference?: boolean;
preserveTimestamps?: boolean;
dryRun?: boolean;
rename?: (source: Source, target: Target, options: Options) => string | void | Promise<string | void>;
filter?: (source: Source, target: Target, options: Options) => boolean | Promise<boolean>;
transform?: (data: Buffer, source: Source, target: Target, options: Options) => Buffer | Promise<Buffer>;
afterEach?: (source: Source, target: Target, options: Options) => void | Promise<void>;
};
type Source = {
path: string;
stats: fs.Stats;
};
type Target = {
path: string;
stats?: fs.Stats;
};
type Totals = {
directories: number;
files: number;
symlinks: number;
size: number; // not size on disk in blocks
};
Copy is a superset of fs-extra/copy. Option names and default values correspond to fs-extra/copy options.
Option | Description |
---|---|
overwrite | Preserves exising files when set to false. Default: true |
errorOnExist | Used in conjunction with overwrite: false. Default: false |
dereference | Copies files if true. Default: false |
preserveTimestamps | Preserves the original timestamps. Default: false |
dryRun*) | Does not perform any write operations. afterEach is called and needs to check options.dryRun. Default: false |
rename*) | Optional rename function. A target path is renamed when returning a non-empty string, otherwise the original name is taken. When moving a directory to a different location, internally a recursive mkdir might be used. In such a case at least node v10.12 is required. |
filter | Optional path filter. Paths are excluded when returning false and included on true. |
transform*) | Optional transformation of file contents. |
afterEach*) | Optional action that is performed after a path has been copied, even on a dry-run. Please check options.dryRun and/or if target.stats is defined. |
*) fs-extra does not have this feature
Copyright © 2020 by Daniel Dietrich. Released under the MIT license.
FAQs
Simple yet powerful copy tool.
The npm package @danieldietrich/copy receives a total of 19,535 weekly downloads. As such, @danieldietrich/copy popularity was classified as popular.
We found that @danieldietrich/copy demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.