-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfix-dynamic-code-exec.sh
More file actions
executable file
·206 lines (179 loc) · 6.69 KB
/
fix-dynamic-code-exec.sh
File metadata and controls
executable file
·206 lines (179 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env bash
# SPDX-License-Identifier: PMPL-1.0-or-later
#
# fix-dynamic-code-exec.sh — Mitigate dynamic code execution patterns
#
# Fixes or flags dangerous patterns:
# - JavaScript: eval(expr) → Function('"use strict"; return ' + expr)()
# - JavaScript: new Function(str) → warning comment added
# - Elixir: Code.eval_string → warning comment added
# - Python: exec()/eval() → warning comment added
#
# Usage: fix-dynamic-code-exec.sh <repo-path> <finding-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/third-party-excludes.sh" 2>/dev/null || true
REPO_PATH="${1:?Usage: $0 <repo-path> <finding-json>}"
FINDING_JSON="${2:?Missing finding JSON file}"
DESCRIPTION=$(jq -r '.description // ""' "$FINDING_JSON")
PATTERN_ID=$(jq -r '.pattern_id // ""' "$FINDING_JSON")
echo "=== Dynamic Code Execution Fix ==="
echo " Repo: $REPO_PATH"
echo " Pattern: $PATTERN_ID"
echo ""
# Use shared third-party exclusions
FIND_EXCLUDES=(-not -path "*/.git/*" "${FIND_THIRD_PARTY_EXCLUDES[@]}")
FIXED_COUNT=0
# --- JavaScript / MJS files ---
JS_FILES=()
while IFS= read -r -d '' f; do
JS_FILES+=("$f")
done < <(find "$REPO_PATH" -type f \( -name "*.js" -o -name "*.mjs" \) "${FIND_EXCLUDES[@]}" -print0 2>/dev/null)
for file in "${JS_FILES[@]}"; do
# Skip binary files
if file "$file" | grep -q "binary"; then
continue
fi
CHANGED=false
# Pattern 1: Replace simple eval(expr) with safer Function form.
# Matches: eval(someExpression) where the argument is a simple variable or
# string concatenation (no nested parens). Skips lines already containing
# the SECURITY WARNING comment or lines that are comments.
if grep -qP '^\s*[^/]*\beval\(' "$file" 2>/dev/null; then
# Add warning comment above eval lines that don't already have one.
# Then replace simple eval(variable) with Function form.
tmpfile=$(mktemp)
awk '
/\/\/ SECURITY WARNING: dynamic code execution/ { print; next }
/^\s*\/\// { print; next }
/^\s*\*/ { print; next }
/\beval\(/ {
# Only add warning if previous line does not already have it
if (prev !~ /SECURITY WARNING: dynamic code execution/) {
# Preserve indentation
match($0, /^[[:space:]]*/);
indent = substr($0, RSTART, RLENGTH);
print indent "// SECURITY WARNING: dynamic code execution — consider eliminating eval";
}
# Replace simple eval(varName) with Function form
# Matches eval(identifier) but not eval("string") or eval(complex.expr())
gsub(/\beval\(([A-Za-z_][A-Za-z0-9_]*)\)/, "Function('\"use strict\"; return ' + \\1)()")
print
prev = $0
next
}
{ print; prev = $0 }
' "$file" > "$tmpfile"
if ! diff -q "$file" "$tmpfile" >/dev/null 2>&1; then
cp "$tmpfile" "$file"
CHANGED=true
fi
rm -f "$tmpfile"
fi
# Pattern 2: new Function(str) — add warning comment (cannot safely auto-replace)
if grep -qP '^\s*[^/]*\bnew\s+Function\(' "$file" 2>/dev/null; then
tmpfile=$(mktemp)
awk '
/\/\/ SECURITY WARNING: dynamic code execution/ { print; next }
/^\s*\/\// { print; next }
/^\s*\*/ { print; next }
/\bnew\s+Function\(/ {
if (prev !~ /SECURITY WARNING: dynamic code execution/) {
match($0, /^[[:space:]]*/);
indent = substr($0, RSTART, RLENGTH);
print indent "// SECURITY WARNING: dynamic code execution — new Function() is equivalent to eval";
}
print
prev = $0
next
}
{ print; prev = $0 }
' "$file" > "$tmpfile"
if ! diff -q "$file" "$tmpfile" >/dev/null 2>&1; then
cp "$tmpfile" "$file"
CHANGED=true
fi
rm -f "$tmpfile"
fi
if [[ "$CHANGED" == true ]]; then
rel_path="${file#"$REPO_PATH"/}"
echo " Fixed JS patterns in $rel_path"
((FIXED_COUNT++)) || true
fi
done
# --- Elixir files ---
EX_FILES=()
while IFS= read -r -d '' f; do
EX_FILES+=("$f")
done < <(find "$REPO_PATH" -type f \( -name "*.ex" -o -name "*.exs" \) "${FIND_EXCLUDES[@]}" -print0 2>/dev/null)
for file in "${EX_FILES[@]}"; do
if file "$file" | grep -q "binary"; then
continue
fi
if grep -qP '^\s*[^#]*\bCode\.eval_string\b' "$file" 2>/dev/null; then
tmpfile=$(mktemp)
awk '
/# SECURITY:.*Code\.eval_string/ { print; next }
/^\s*#/ { print; next }
/\bCode\.eval_string\b/ {
if (prev !~ /SECURITY:.*Code\.eval_string/) {
match($0, /^[[:space:]]*/);
indent = substr($0, RSTART, RLENGTH);
print indent "# SECURITY: Code.eval_string is dangerous — consider Code.compile_quoted or pattern matching";
}
print
prev = $0
next
}
{ print; prev = $0 }
' "$file" > "$tmpfile"
if ! diff -q "$file" "$tmpfile" >/dev/null 2>&1; then
cp "$tmpfile" "$file"
rel_path="${file#"$REPO_PATH"/}"
echo " Flagged Code.eval_string in $rel_path"
((FIXED_COUNT++)) || true
fi
rm -f "$tmpfile"
fi
done
# --- Python files ---
PY_FILES=()
while IFS= read -r -d '' f; do
PY_FILES+=("$f")
done < <(find "$REPO_PATH" -type f -name "*.py" "${FIND_EXCLUDES[@]}" -print0 2>/dev/null)
for file in "${PY_FILES[@]}"; do
if file "$file" | grep -q "binary"; then
continue
fi
if grep -qP '^\s*[^#]*\b(exec|eval)\(' "$file" 2>/dev/null; then
tmpfile=$(mktemp)
awk '
/# SECURITY WARNING: dynamic code execution/ { print; next }
/^\s*#/ { print; next }
/\b(exec|eval)\(/ {
if (prev !~ /SECURITY WARNING: dynamic code execution/) {
match($0, /^[[:space:]]*/);
indent = substr($0, RSTART, RLENGTH);
print indent "# SECURITY WARNING: dynamic code execution — avoid exec()/eval() where possible";
}
print
prev = $0
next
}
{ print; prev = $0 }
' "$file" > "$tmpfile"
if ! diff -q "$file" "$tmpfile" >/dev/null 2>&1; then
cp "$tmpfile" "$file"
rel_path="${file#"$REPO_PATH"/}"
echo " Flagged exec/eval in $rel_path"
((FIXED_COUNT++)) || true
fi
rm -f "$tmpfile"
fi
done
echo ""
if [[ "$FIXED_COUNT" -gt 0 ]]; then
echo "Fixed/flagged $FIXED_COUNT file(s)"
else
echo "No fixable patterns found (may need manual review)"
fi