Skip to content

Commit 1feb666

Browse files
committed
fix: 🐛 (security) prevent shell injection by replacing exec with execFile
1 parent 5e420d0 commit 1feb666

File tree

5 files changed

+40
-11
lines changed

5 files changed

+40
-11
lines changed

helpers/shells/deleteRepo.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bash
22

3+
### DEPRECATED ### NodeJS will handle this in the future.
4+
35
# Shell created by Raven for BorgWarehouse.
46
# This shell takes 1 arg : [repositoryName] with 8 char. length only.
57
# This shell **delete the repository** in arg and **all his data** and the line associated in the authorized_keys file.

helpers/shells/getLastSave.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bash
22

3+
### DEPRECATED ### NodeJS will handle this in the future.
4+
35
# Shell created by Raven for BorgWarehouse.
46
# Get the timestamp of the last modification of the file integrity.* for of all repositories in a JSON output.
57
# stdout will be an array like :

helpers/shells/getStorageUsed.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bash
22

3+
### DEPRECATED ### NodeJS will handle this in the future.
4+
35
# Shell created by Raven for BorgWarehouse.
46
# Get the size of all repositories in a JSON output.
57
# stdout will be an array like :

helpers/shells/updateRepo.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bash
22

3+
### DEPRECATED ### NodeJS will handle this in the future.
4+
35
# Shell created by Raven for BorgWarehouse.
46
# This shell takes 4 args: [repositoryName] [new SSH pub key] [quota] [append-only mode (boolean)]
57
# This shell updates the SSH key and the quota for a repository.

services/shell.service.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import path from 'path';
22
import { promisify } from 'util';
3-
import { exec as execCallback } from 'node:child_process';
3+
import { execFile as execFileCallback } from 'node:child_process';
44
import { LastSaveDTO, StorageUsedDTO } from '~/types';
5+
import repositoryNameCheck from '~/helpers/functions/repositoryNameCheck';
56

6-
const exec = promisify(execCallback);
7+
const execFile = promisify(execFileCallback);
78
const shellsDirectory = path.join(process.cwd(), '/helpers/shells');
89

910
// This is to prevent the cronjob from being executed multiple times
1011
let isLastSaveListRunning = false;
1112
let isStorageUsedRunning = false;
1213

14+
function isValidSshKey(key: string): boolean {
15+
return /^ssh-(rsa|ed25519|ed25519-sk) [A-Za-z0-9+/=]+(\s.+)?$/.test(key.trim());
16+
}
17+
1318
export const ShellService = {
1419
getLastSaveList: async (): Promise<LastSaveDTO[]> => {
1520
if (isLastSaveListRunning) {
@@ -19,7 +24,7 @@ export const ShellService = {
1924
}
2025

2126
try {
22-
const { stdout } = await exec(`${shellsDirectory}/getLastSave.sh`);
27+
const { stdout } = await execFile(`${shellsDirectory}/getLastSave.sh`);
2328
return JSON.parse(stdout || '[]');
2429
} finally {
2530
isLastSaveListRunning = false;
@@ -33,15 +38,15 @@ export const ShellService = {
3338
isStorageUsedRunning = true;
3439
}
3540
try {
36-
const { stdout } = await exec(`${shellsDirectory}/getStorageUsed.sh`);
41+
const { stdout } = await execFile(`${shellsDirectory}/getStorageUsed.sh`);
3742
return JSON.parse(stdout || '[]');
3843
} finally {
3944
isStorageUsedRunning = false;
4045
}
4146
},
4247

4348
deleteRepo: async (repositoryName: string) => {
44-
const { stdout, stderr } = await exec(`${shellsDirectory}/deleteRepo.sh ${repositoryName}`);
49+
const { stdout, stderr } = await execFile(`${shellsDirectory}/deleteRepo.sh`, [repositoryName]);
4550
return { stdout, stderr };
4651
},
4752

@@ -51,9 +56,19 @@ export const ShellService = {
5156
storageSize: number,
5257
appendOnlyMode: boolean
5358
) => {
54-
const { stdout, stderr } = await exec(
55-
`${shellsDirectory}/updateRepo.sh ${repositoryName} "${sshPublicKey}" ${storageSize} ${appendOnlyMode}`
56-
);
59+
if (!isValidSshKey(sshPublicKey)) {
60+
throw new Error('Invalid SSH key format');
61+
}
62+
if (!repositoryNameCheck(repositoryName)) {
63+
throw new Error('Invalid repository name format');
64+
}
65+
66+
const { stdout, stderr } = await execFile(`${shellsDirectory}/updateRepo.sh`, [
67+
repositoryName,
68+
sshPublicKey,
69+
storageSize.toString(),
70+
appendOnlyMode.toString(),
71+
]);
5772
return { stdout, stderr };
5873
},
5974

@@ -62,9 +77,15 @@ export const ShellService = {
6277
storageSize: number,
6378
appendOnlyMode: boolean
6479
): Promise<{ stdout?: string; stderr?: string }> => {
65-
const { stdout, stderr } = await exec(
66-
`${shellsDirectory}/createRepo.sh "${sshPublicKey}" ${storageSize} ${appendOnlyMode}`
67-
);
80+
if (!isValidSshKey(sshPublicKey)) {
81+
throw new Error('Invalid SSH key format');
82+
}
83+
84+
const { stdout, stderr } = await execFile(`${shellsDirectory}/createRepo.sh`, [
85+
sshPublicKey,
86+
storageSize.toString(),
87+
appendOnlyMode.toString(),
88+
]);
6889
return { stdout, stderr };
6990
},
7091
};

0 commit comments

Comments
 (0)