Skip to content

Commit 2ff2bd2

Browse files
committed
fix(gimp): escape Script-Fu path literals to prevent injection
Adds _script_fu_escape() to safely escape values embedded in Script-Fu double-quoted strings. Prevents command injection through crafted file paths containing quotes or control characters. - Added _script_fu_escape() function for escaping backslashes, quotes, and control characters - Updated create_and_export() to use safe path variants - Updated apply_filter_and_export() to use safe path variants Fixes: HKUDS#60
1 parent 4e1ebfd commit 2ff2bd2

1 file changed

Lines changed: 26 additions & 8 deletions

File tree

gimp/agent-harness/cli_anything/gimp/utils/gimp_backend.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,26 @@
77
"""
88

99
import os
10+
import re
1011
import shutil
1112
import subprocess
1213
from typing import Optional
1314

1415

16+
def _script_fu_escape(s: str) -> str:
17+
"""Escape a string for safe embedding in Script-Fu double-quoted strings.
18+
19+
Escapes backslash, double-quote, and control characters to prevent
20+
Script-Fu injection through path literals.
21+
"""
22+
# Escape backslash first, then double quotes, then other special chars
23+
s = s.replace('\\', '\\\\')
24+
s = s.replace('"', '\\"')
25+
# Remove or escape control characters (newline, tab, etc.)
26+
s = re.sub(r'[\x00-\x1f\x7f]', lambda m: f'\\x{ord(m.group()):02x}', s)
27+
return s
28+
29+
1530
def find_gimp() -> str:
1631
"""Find the GIMP executable. Raises RuntimeError if not found."""
1732
for name in ("gimp", "gimp-2.10", "gimp-2.99"):
@@ -73,6 +88,7 @@ def create_and_export(
7388
) -> dict:
7489
"""Create a new image in GIMP and export it."""
7590
abs_output = os.path.abspath(output_path)
91+
safe_abs_output = _script_fu_escape(abs_output)
7692
os.makedirs(os.path.dirname(abs_output), exist_ok=True)
7793

7894
ext = os.path.splitext(output_path)[1].lower()
@@ -81,22 +97,22 @@ def create_and_export(
8197
if ext == ".png":
8298
export_cmd = (
8399
f'(file-png-save RUN-NONINTERACTIVE image layer '
84-
f'"{abs_output}" "{abs_output}" 0 9 1 1 1 1 1)'
100+
f'"{safe_abs_output}" "{safe_abs_output}" 0 9 1 1 1 1 1)'
85101
)
86102
elif ext in (".jpg", ".jpeg"):
87103
export_cmd = (
88104
f'(file-jpeg-save RUN-NONINTERACTIVE image layer '
89-
f'"{abs_output}" "{abs_output}" 0.85 0.0 0 0 "" 0 1 0 2)'
105+
f'"{safe_abs_output}" "{safe_abs_output}" 0.85 0.0 0 0 "" 0 1 0 2)'
90106
)
91107
elif ext == ".bmp":
92108
export_cmd = (
93109
f'(file-bmp-save RUN-NONINTERACTIVE image layer '
94-
f'"{abs_output}" "{abs_output}" 0)'
110+
f'"{safe_abs_output}" "{safe_abs_output}" 0)'
95111
)
96112
else:
97113
export_cmd = (
98114
f'(gimp-file-overwrite RUN-NONINTERACTIVE image layer '
99-
f'"{abs_output}" "{abs_output}")'
115+
f'"{safe_abs_output}" "{safe_abs_output}")'
100116
)
101117

102118
# Color mapping
@@ -162,28 +178,30 @@ def apply_filter_and_export(
162178

163179
abs_input = os.path.abspath(input_path)
164180
abs_output = os.path.abspath(output_path)
181+
safe_abs_input = _script_fu_escape(abs_input)
182+
safe_abs_output = _script_fu_escape(abs_output)
165183
os.makedirs(os.path.dirname(abs_output), exist_ok=True)
166184

167185
ext = os.path.splitext(output_path)[1].lower()
168186
if ext == ".png":
169187
export_cmd = (
170188
f'(file-png-save RUN-NONINTERACTIVE image drawable '
171-
f'"{abs_output}" "{abs_output}" 0 9 1 1 1 1 1)'
189+
f'"{safe_abs_output}" "{safe_abs_output}" 0 9 1 1 1 1 1)'
172190
)
173191
elif ext in (".jpg", ".jpeg"):
174192
export_cmd = (
175193
f'(file-jpeg-save RUN-NONINTERACTIVE image drawable '
176-
f'"{abs_output}" "{abs_output}" 0.85 0.0 0 0 "" 0 1 0 2)'
194+
f'"{safe_abs_output}" "{safe_abs_output}" 0.85 0.0 0 0 "" 0 1 0 2)'
177195
)
178196
else:
179197
export_cmd = (
180198
f'(gimp-file-overwrite RUN-NONINTERACTIVE image drawable '
181-
f'"{abs_output}" "{abs_output}")'
199+
f'"{safe_abs_output}" "{safe_abs_output}")'
182200
)
183201

184202
script = (
185203
f'(let* ('
186-
f'(image (car (file-png-load RUN-NONINTERACTIVE "{abs_input}" "{abs_input}")))'
204+
f'(image (car (file-png-load RUN-NONINTERACTIVE "{safe_abs_input}" "{safe_abs_input}")))'
187205
f'(drawable (car (gimp-image-flatten image)))'
188206
f')'
189207
f'{script_fu_filter}'

0 commit comments

Comments
 (0)