Skip to content

Commit 8f0f7ad

Browse files
authored
Add Secure Code section (first draft) (#85)
1 parent 2f91f86 commit 8f0f7ad

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

Secure-Code.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<!-- prettier-ignore-start -->
2+
> _This wiki is automatically published from [ohmyzsh/wiki](https://github.com/ohmyzsh/wiki). To edit this page,_
3+
> _go to [ohmyzsh/wiki](https://github.com/ohmyzsh/wiki), make your changes and submit a Pull Request._
4+
<!-- prettier-ignore-end -->
5+
6+
# Secure Code
7+
8+
Oh My Zsh runs directly in people's environments, and there's currently no alternative.
9+
This is why it is paramount that our code is vetted and reviewed according to security
10+
best practices, specifically targeting Zsh.
11+
12+
## General Guidelines
13+
14+
Follow these practices when contributing code that runs in users' shells:
15+
16+
- Least privilege: design code to run with the minimal permissions required. Avoid elevate-right operations (sudo, setuid) unless absolutely necessary and documented.
17+
- Input validation and escaping: treat all external input as untrusted. Validate, constrain, and escape inputs (prefer arrays and properly quoted arguments; avoid building commands by concatenation).
18+
- Minimize use of eval/source/command substitution: avoid dynamic evaluation. If necessary, tightly validate inputs and limit the scope of what can be executed.
19+
- Safe error handling: return clear, minimal errors for operators but never leak secrets or sensitive environment/state in messages or logs.
20+
- Secrets and sensitive data: never hard-code credentials. Avoid writing secrets to logs, shell history, cache, or world-readable files; unset sensitive variables as soon as they are no longer needed (e.g., unset SECRET_VAR).
21+
- Secure file handling: use secure temporary files (mktemp), set restrictive permissions (umask, chmod), and avoid creating files in insecure locations.
22+
- Logging and telemetry: avoid logging and telemetry as much as possible. If necessary, only log what is necessary and redact or hash sensitive fields, and ensure log storage is access-controlled (e.g., not world-readable such as `/tmp`).
23+
- Dependencies and supply chain: prefer minimal, well-audited dependencies; pin and justify any third-party tools that run with elevated privileges.
24+
- Review, testing, and CI: require peer review for changes, include automated checks (shellcheck, unit/integration tests), and run security-focused CI scans before release.
25+
- Documentation: document any security-relevant design decisions, required privileges, and mitigation steps so reviewers can assess risk.
26+
27+
## Insecure patterns to avoid
28+
29+
> [!NOTE]
30+
> Here, we use `read something` as a command to symbolize untrusted input.
31+
> In real world scenarios, untrusted input usually comes from some other place.
32+
33+
### eval with untrusted input
34+
35+
**Past vulnerabilities:**
36+
37+
- [Issue #10414](https://github.com/ohmyzsh/ohmyzsh/issues/10414): Unsafe use of `eval` with untrusted input
38+
39+
#### Use case 1: Dynamic command execution
40+
41+
**Insecure pattern:**
42+
43+
```sh
44+
read filename # User input: "file; echo pwned"
45+
eval "stat $filename" # Executes: stat file; echo pwned
46+
```
47+
48+
This pattern is often seen in more complex code where the command is determined dynamically:
49+
50+
```sh
51+
somefunction() {
52+
local arg="$1" filename="$2"
53+
local cmd=""
54+
55+
case "$arg" in
56+
-s) cmd="stat" ;;
57+
-l) cmd="ls" ;;
58+
esac
59+
60+
eval "$cmd $filename" # VULNERABLE
61+
}
62+
63+
# Attack example
64+
somefunction -s "file; rm -rf $HOME"
65+
```
66+
67+
**Better pattern:**
68+
69+
```sh
70+
somefunction() {
71+
local arg="$1" filename="$2"
72+
local cmd=""
73+
74+
case "$arg" in
75+
-s) cmd="stat" ;;
76+
-l) cmd="ls" ;;
77+
esac
78+
79+
# SECURE: Zsh handles argument parsing safely
80+
$cmd "$filename"
81+
}
82+
```
83+
84+
The shell correctly parses each parameter as part of the same command. Even if `$filename` contains `;`, it will not be parsed as the beginning of a new command.
85+
86+
What's preventing injection are not the quotes around `$filename`, but the fact that we are passing each argument separately
87+
in the command. This way, zsh knows exactly what each argument is supposed to be in the parsing phase, before any variable expansion happens.
88+
89+
### Use case 2: Variable assignment
90+
91+
**Insecure pattern:**
92+
93+
This almost never makes sense:
94+
95+
```sh
96+
read var # User input: "file; echo pwned"
97+
eval "foo=$var" # Executes: foo=file; echo pwned
98+
```
99+
100+
But this one is more common:
101+
102+
```sh
103+
read var # User input: "echo pwned; myvariable"
104+
eval "$var=helloworld" # Executes: echo pwned; myvariable=helloworld
105+
```
106+
107+
**Better pattern:**
108+
109+
Use `typeset` (or `declare`, or any of the other built-in variable assignment commands):
110+
111+
```sh
112+
read var
113+
typeset "$var=helloworld" # SECURE: throws error instead of executing
114+
```
115+
116+
This approach throws an error on malicious input rather than executing arbitrary commands:
117+
118+
```sh
119+
➜ var="file; echo pwned"
120+
typeset "$var=helloworld"
121+
typeset -p myvariable
122+
typeset myvariable=helloworld
123+
124+
➜ var="echo pwned; myvariable"
125+
typeset "$var=helloworld"
126+
typeset: not valid in this context: echo pwned; myvariable
127+
```
128+
129+
### print -P with untrusted input
130+
131+
**Past vulnerabilities:**
132+
133+
- [Issue #10414](https://github.com/ohmyzsh/ohmyzsh/issues/10414): Command injection via `print -P` with untrusted input
134+
135+
#### Use case: displaying formatted output with prompt sequences
136+
137+
**Insecure pattern:**
138+
139+
```sh
140+
setopt promptsubst
141+
dailyquote="$(get_daily_quote_from_the_internet)"
142+
print -P "%F{yellow}%B${dailyquote}%b%f" # VULNERABLE
143+
```
144+
145+
If `dailyquote` contains `$()` or backticks (`` ` ``), zsh will execute those commands.
146+
147+
**Better pattern:**
148+
149+
```sh
150+
# Disable prompt substitution for untrusted content
151+
setopt localoptions nopromptsubst
152+
dailyquote="$(get_daily_quote_from_the_internet)"
153+
print -P "%F{yellow}%B${dailyquote}%b%f"
154+
155+
# Use `print` without `-P` to avoid prompt substitution
156+
setopt promptsubst
157+
dailyquote="$(get_daily_quote_from_the_internet)"
158+
print "${fg[yellow]}${dailyquote}${reset_color}"
159+
```
160+
161+
### Unescaped % characters in prompt functions
162+
163+
**Past vulnerabilities:**
164+
165+
- [CVE-2021-45444](https://www.cve.org/CVERecord?id=CVE-2021-45444): Command execution via crafted branch names in prompts (partially fixed in zsh 5.8.1)
166+
- [Issue #10414](https://github.com/ohmyzsh/ohmyzsh/issues/10414): Unquoted % characters in prompt functions
167+
168+
#### Use case: Functions that output untrusted content in prompts
169+
170+
**Insecure pattern:**
171+
172+
```sh
173+
gitbranch() {
174+
command git symbolic-ref --short HEAD
175+
}
176+
PROMPT='myuser:$(gitbranch) %% ' # VULNERABLE
177+
```
178+
179+
Git branch names may contain `%` characters. In zsh versions older than 5.8.1 with `setopt promptsubst` enabled, this is vulnerable to [CVE-2021-45444](https://www.cve.org/CVERecord?id=CVE-2021-45444). An attacker could craft a malicious branch name to execute arbitrary commands.
180+
181+
**Better pattern:**
182+
183+
```sh
184+
gitbranch() {
185+
local branch="$(command git symbolic-ref --short HEAD)"
186+
echo -n "${branch//\%/%%}" # Escape all % characters
187+
}
188+
PROMPT='myuser:$(gitbranch) %% ' # SECURE
189+
```
190+
191+
Functions that output untrusted content for use in `$PROMPT` or `$RPROMPT` must:
192+
1. Escape all `%` characters by replacing them with `%%`
193+
2. Be documented and named to indicate they are prompt-safe
194+
3. Be used only in prompt contexts where this escaping is appropriate

_Sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- **Contributing**
1919
- [[Volunteers]]
2020
- [[Design]]
21+
- [[Secure Code]]
2122
- [[Code Style Guide]]
2223
- [[Wiki Style Guide]]
2324
- [[Contribution Technical Practices]]

0 commit comments

Comments
 (0)