Skip to content

Commit 3fc6800

Browse files
committed
feat: enhance Daffodil class with parameter validation and shell argument escaping
- Added validation for required parameters in the Daffodil constructor to ensure correct types. - Introduced a private method `escapeShellArg` to prevent command injection by escaping shell arguments. - Updated various commands to use `escapeShellArg` for safer execution. - Changed error handling in SSH connection failure to throw an error instead of exiting the process. - Updated sample file to correct the transfer command argument from "s" to "dist".
1 parent 58c0497 commit 3fc6800

File tree

2 files changed

+33
-8
lines changed

2 files changed

+33
-8
lines changed

src/index.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ export class Daffodil {
6161
ignoreFile = ".scpignore",
6262
verbose = false,
6363
}) {
64+
// Validate required parameters
65+
if (!remoteUser || typeof remoteUser !== "string") {
66+
throw new Error("remoteUser is required and must be a string");
67+
}
68+
if (!remoteHost || typeof remoteHost !== "string") {
69+
throw new Error("remoteHost is required and must be a string");
70+
}
71+
if (port && (typeof port !== "number" || port < 1 || port > 65535)) {
72+
throw new Error("port must be a number between 1 and 65535");
73+
}
74+
6475
this.remoteUser = remoteUser;
6576
this.remoteHost = remoteHost;
6677
this.remotePath = remotePath;
@@ -88,6 +99,17 @@ export class Daffodil {
8899
return new Date().toISOString();
89100
}
90101

102+
/**
103+
* Escape shell arguments to prevent command injection
104+
* @param {string} arg - Argument to escape
105+
* @returns {string} Escaped argument
106+
* @private
107+
*/
108+
escapeShellArg(arg) {
109+
// Replace single quotes with '\'' and wrap in single quotes
110+
return `'${String(arg).replace(/'/g, "'\\''")}'`;
111+
}
112+
91113
/**
92114
* Log message with timestamp if verbose is enabled
93115
* @param {string} message - Message to log
@@ -238,7 +260,7 @@ export class Daffodil {
238260
this.logTimeConsumption("SSH Connection", startTime);
239261

240262
const ensurePath = path.posix.resolve(this.remotePath);
241-
await this.ssh.execCommand(`mkdir -p ${ensurePath}`);
263+
await this.ssh.execCommand(`mkdir -p ${this.escapeShellArg(ensurePath)}`);
242264
this.log(`Verified or created remote path: ${ensurePath}`, "blue");
243265
return; // ✅ Success, skip remaining
244266
} catch (err) {
@@ -269,7 +291,10 @@ export class Daffodil {
269291
);
270292
}
271293

272-
process.exit(1);
294+
// Throw error instead of process.exit for library code
295+
throw new Error(
296+
"SSH connection failed: No valid SSH keys worked. Please ensure your SSH keys are properly configured."
297+
);
273298
}
274299

275300
loadIgnoreList() {
@@ -338,7 +363,7 @@ export class Daffodil {
338363
const startTime = Date.now();
339364
const fullPath = path.posix.join(this.remotePath, dirName);
340365
this.log(`Creating remote directory: ${fullPath}`, "blue");
341-
await this.sshCommand(`mkdir -p ${fullPath}`);
366+
await this.sshCommand(`mkdir -p ${this.escapeShellArg(fullPath)}`);
342367
this.logTimeConsumption(`Create directory: ${fullPath}`, startTime);
343368
}
344369

@@ -542,7 +567,7 @@ export class Daffodil {
542567
if (this.verbose) {
543568
this.log("Removing old archive from remote if exists", "blue");
544569
}
545-
await this.ssh.execCommand(`rm -f "${remoteArchivePath}" || true`);
570+
await this.ssh.execCommand(`rm -f ${this.escapeShellArg(remoteArchivePath)} || true`);
546571

547572
// Transfer the archive file
548573
await this.ssh.putFile(archivePath, remoteArchivePath);
@@ -562,11 +587,11 @@ export class Daffodil {
562587
}
563588

564589
// Ensure destination directory exists
565-
await this.ssh.execCommand(`mkdir -p "${destinationPath}" || true`);
590+
await this.ssh.execCommand(`mkdir -p ${this.escapeShellArg(destinationPath)} || true`);
566591

567592
// Extract archive (overwrite existing files)
568593
const extractResult = await this.ssh.execCommand(
569-
`cd "${destinationPath}" && tar -xzf "${remoteArchivePath}" && rm -f "${remoteArchivePath}"`
594+
`cd ${this.escapeShellArg(destinationPath)} && tar -xzf ${this.escapeShellArg(remoteArchivePath)} && rm -f ${this.escapeShellArg(remoteArchivePath)}`
570595
);
571596

572597
if (extractResult.code !== 0) {
@@ -608,7 +633,7 @@ export class Daffodil {
608633
if (this.verbose) {
609634
this.log("Cleaning up remote archive after error", "yellow");
610635
}
611-
await this.ssh.execCommand(`rm -f "${remoteArchivePath}" || true`);
636+
await this.ssh.execCommand(`rm -f ${this.escapeShellArg(remoteArchivePath)} || true`);
612637
} catch (cleanupErr) {
613638
// Ignore cleanup errors
614639
if (this.verbose) {

src/samples/sample.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const steps = [
2525
},
2626
{
2727
step: "Transfer files.",
28-
command: () => deployer.transferFiles("s", "/root/test"),
28+
command: () => deployer.transferFiles("dist", "/root/test"),
2929
},
3030
];
3131

0 commit comments

Comments
 (0)