Skip to content

Commit ddc19af

Browse files
author
Vibhuti Patel
committed
Improve code-security rules with safer examples and updated guidance
1 parent df4b533 commit ddc19af

19 files changed

Lines changed: 288 additions & 158 deletions

skills/code-security.zip

2.61 KB
Binary file not shown.

skills/code-security/rules/authentication-jwt.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,13 @@ function getUserData(token) {
2424
}
2525
```
2626

27-
**Correct (JavaScript jsonwebtoken - verify before decode):**
27+
**Correct (JavaScript jsonwebtoken - use verify which returns decoded payload):**
2828

2929
```javascript
3030
const jwt = require('jsonwebtoken');
3131

3232
function getUserData(token, secretKey) {
33-
jwt.verify(token, secretKey);
34-
const decoded = jwt.decode(token, true);
33+
const decoded = jwt.verify(token, secretKey);
3534
if (decoded.isAdmin) {
3635
return getAdminData();
3736
}

skills/code-security/rules/code-injection.md

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ def unsafe(request):
1717
eval(code)
1818
```
1919

20-
**Correct (Python - static eval with hardcoded strings):**
20+
**Correct (Python - avoid eval entirely, use safe alternatives):**
2121

2222
```python
23-
eval("x = 1; x = x + 2")
23+
import ast
2424

25-
blah = "import requests; r = requests.get('https://example.com')"
26-
eval(blah)
25+
def safe_parse(user_expr):
26+
# ast.literal_eval only allows literals (strings, numbers, tuples, lists, dicts, booleans, None)
27+
return ast.literal_eval(user_expr)
28+
29+
# For math expressions, use a purpose-built parser instead of eval
2730
```
2831

32+
> **Note:** Avoid `eval()`/`exec()` entirely. Even with hardcoded strings, it normalizes a dangerous pattern. Use `ast.literal_eval()` for parsing data literals, or purpose-built parsers for expressions.
33+
2934
**Incorrect (JavaScript - eval with dynamic content):**
3035

3136
```javascript
@@ -38,15 +43,20 @@ function evalSomething(something) {
3843
}
3944
```
4045

41-
**Correct (JavaScript - static eval strings):**
46+
**Correct (JavaScript - avoid eval, use safe alternatives):**
4247

4348
```javascript
44-
eval('var x = "static strings are okay";');
49+
// Instead of eval for JSON parsing:
50+
const data = JSON.parse(jsonString);
51+
52+
// Instead of eval for dynamic property access:
53+
const value = obj[propertyName];
4554

46-
const constVar = "function staticStrings() { return 'static strings are okay';}";
47-
eval(constVar);
55+
// Instead of eval for math: use a sandboxed expression parser
4856
```
4957

58+
> **Note:** There is almost never a legitimate reason to use `eval()`. Use `JSON.parse()`, computed property access, or a sandboxed parser. Avoid `new Function()` as well — it executes arbitrary code just like `eval()`.
59+
5060
**Incorrect (Java - ScriptEngine injection):**
5161

5262
```java
@@ -94,34 +104,35 @@ a = %q{def hello() "Hello there!" end}
94104
Thing.module_eval(a)
95105
```
96106

97-
**Incorrect (PHP - dangerous exec functions with user input):**
107+
**Incorrect (PHP - code injection via eval/assert):**
98108

99109
```php
100-
exec($user_input);
101-
passthru($user_input);
102-
$output = shell_exec($user_input);
103-
$output = system($user_input, $retval);
110+
$code = $_GET['code'];
111+
eval($code);
104112

105-
$username = $_COOKIE['username'];
106-
exec("wto -n \"$username\" -g", $ret);
113+
$input = $_POST['input'];
114+
assert($input); // assert() evaluates strings as code in PHP < 8.0
107115
```
108116

109-
**Correct (PHP - static commands with escapeshellarg):**
117+
**Correct (PHP - avoid eval, use structured alternatives):**
110118

111119
```php
112-
exec('whoami');
120+
// Instead of eval for dynamic config, use a data format:
121+
$config = json_decode(file_get_contents('config.json'), true);
113122

114-
$fullpath = $_POST['fullpath'];
115-
$filesize = trim(shell_exec('stat -c %s ' . escapeshellarg($fullpath)));
123+
// Instead of eval for templates, use a template engine (Twig, Blade)
116124
```
117125

126+
> **Note:** `exec()`/`shell_exec()`/`system()` are OS command execution — see the command-injection rule for those. This rule covers code evaluation via `eval()`, `assert()`, `preg_replace` with `/e`, and similar.
127+
118128
## Key Prevention Patterns
119129

120-
1. **Never pass user input to eval/exec functions** - Treat all user input as untrusted
121-
2. **Use hardcoded strings** - Static strings in eval/exec calls are safe
122-
3. **Validate and sanitize** - If dynamic code execution is unavoidable, validate against a strict whitelist
123-
4. **Use parameterized alternatives** - Many languages offer safer alternatives to eval
124-
5. **Escape shell arguments** - Use escapeshellarg() in PHP or equivalent functions
130+
1. **Avoid eval/exec entirely** - Use safer alternatives (`JSON.parse`, `ast.literal_eval`, template engines, computed property access)
131+
2. **Never pass user input to code evaluation functions** - Treat all user input as untrusted
132+
3. **If dynamic code execution is unavoidable** - Validate against a strict allowlist and sandbox the execution
133+
4. **Use parameterized alternatives** - Most languages offer structured APIs that eliminate the need for eval
134+
135+
> For OS command execution (`exec`, `shell_exec`, `system`) and shell-escaping (`escapeshellarg`), see the **command-injection** rule.
125136
126137
## References
127138

skills/code-security/rules/correctness.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ finally:
5858
break # Suppresses the exception!
5959
```
6060

61+
**CORRECT** - Let the exception propagate; use finally only for cleanup:
62+
```python
63+
try:
64+
raise ValueError()
65+
finally:
66+
cleanup() # Cleanup runs, exception still propagates
67+
```
68+
6169
### Raising Non-Exceptions
6270

6371
**INCORRECT**:
@@ -106,7 +114,9 @@ return `value is ${x}`
106114

107115
### Loop Pointer Export
108116

109-
Loop variables are shared across iterations.
117+
> **Note:** Go 1.22+ scopes loop variables per-iteration, fixing this issue. The pattern below applies to Go < 1.22.
118+
119+
Loop variables are shared across iterations (Go < 1.22).
110120

111121
**INCORRECT**:
112122
```go
@@ -135,7 +145,15 @@ bigValue, _ := strconv.Atoi("2147483648")
135145
value := int16(bigValue) // Overflow!
136146
```
137147

138-
**CORRECT**: Use `strconv.ParseInt` with correct bit size.
148+
**CORRECT**:
149+
```go
150+
parsed, err := strconv.ParseInt("2147483648", 10, 32)
151+
if err != nil {
152+
// handles out-of-range and invalid syntax
153+
log.Fatal(err)
154+
}
155+
value := int32(parsed)
156+
```
139157

140158
---
141159

@@ -180,7 +198,12 @@ int i = atoi(buf);
180198

181199
**CORRECT**:
182200
```c
183-
long l = strtol(buf, NULL, 10);
201+
char *endptr;
202+
errno = 0;
203+
long l = strtol(buf, &endptr, 10);
204+
if (errno != 0 || endptr == buf || *endptr != '\0') {
205+
// handle conversion error
206+
}
184207
```
185208

186209
---

skills/code-security/rules/csrf.md

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,64 @@ def my_view(request):
4242

4343
#### Missing CSRF Middleware
4444

45-
**Incorrect (Express app without csurf middleware):**
45+
> **⚠ Deprecation Notice:** The `csurf` npm package is **deprecated** and should not be used in new projects. Use a maintained alternative such as `csrf-csrf` (Double-Submit Cookie pattern) or `csrf-sync` (Synchronizer Token pattern).
46+
47+
**Incorrect (Express app without CSRF protection):**
4648
```javascript
47-
var express = require('express')
48-
var bodyParser = require('body-parser')
49+
const express = require('express')
50+
const bodyParser = require('body-parser')
4951

50-
var app = express()
52+
const app = express()
5153

5254
app.post('/process', bodyParser.urlencoded({ extended: false }), function(req, res) {
5355
res.send('data is being processed')
5456
})
5557
```
5658

57-
**Correct (include csurf middleware):**
59+
**Correct — Option A: `csrf-csrf` (Double-Submit Cookie pattern):**
60+
```javascript
61+
const express = require('express')
62+
const cookieParser = require('cookie-parser')
63+
const { doubleCsrf } = require('csrf-csrf')
64+
65+
const { doubleCsrfProtection, generateToken } = doubleCsrf({
66+
getSecret: () => process.env.CSRF_SECRET,
67+
cookieName: '__Host-psifi.x-csrf-token',
68+
cookieOptions: { sameSite: 'strict', secure: true },
69+
})
70+
71+
const app = express()
72+
app.use(cookieParser())
73+
app.use(doubleCsrfProtection)
74+
75+
// Generate a token for forms/SPA clients
76+
app.get('/csrf-token', (req, res) => {
77+
res.json({ token: generateToken(req, res) })
78+
})
79+
```
80+
81+
**Correct — Option B: `csrf-sync` (Synchronizer Token pattern):**
5882
```javascript
59-
var csrf = require('csurf')
60-
var express = require('express')
83+
const express = require('express')
84+
const { csrfSync } = require('csrf-sync')
6185

62-
var app = express()
63-
app.use(csrf({ cookie: true }))
86+
const { csrfSynchronisedProtection, generateToken } = csrfSync()
87+
88+
const app = express()
89+
app.use(csrfSynchronisedProtection)
6490
```
6591

92+
**Additional defenses (complement token-based CSRF protection):**
93+
- Set `SameSite=Strict` or `SameSite=Lax` on session cookies.
94+
- Validate `Sec-Fetch-Site` / `Origin` headers (Fetch Metadata) to reject cross-origin requests at the edge.
95+
6696
**References:**
67-
- [csurf npm package](https://www.npmjs.com/package/csurf)
97+
- [csrf-csrf (Double-Submit Cookie)](https://www.npmjs.com/package/csrf-csrf)
98+
- [csrf-sync (Synchronizer Token)](https://www.npmjs.com/package/csrf-sync)
99+
- [csurf — deprecated](https://www.npmjs.com/package/csurf) *(do not use in new projects)*
68100
- [OWASP CSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
101+
- [OWASP Fetch Metadata / Resource Isolation Policy](https://web.dev/articles/fetch-metadata)
102+
- [MDN SameSite Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
69103

70104
---
71105

skills/code-security/rules/docker.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The last user in the container should not be 'root'. If an attacker gains contro
1616
**Incorrect:**
1717

1818
```dockerfile
19-
FROM busybox
19+
FROM debian:bookworm
2020
RUN apt-get update && apt-get install -y some-package
2121
USER appuser
2222
USER root
@@ -25,7 +25,7 @@ USER root
2525
**Correct:**
2626

2727
```dockerfile
28-
FROM busybox
28+
FROM debian:bookworm
2929
USER root
3030
RUN apt-get update && apt-get install -y some-package
3131
USER appuser
@@ -102,15 +102,17 @@ services:
102102
- /var/run/docker.sock:/var/run/docker.sock
103103
```
104104
105-
**Correct:**
105+
**Correct (use a named volume instead of host mounts):**
106106
107107
```yaml
108108
version: "3.9"
109109
services:
110110
worker:
111111
image: my-worker-image:1.0
112112
volumes:
113-
- /tmp/data:/tmp/data
113+
- worker-data:/app/data
114+
volumes:
115+
worker-data:
114116
```
115117
116118
### Arbitrary Container Run (Python Docker SDK)

skills/code-security/rules/insecure-crypto.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,22 @@ function hashPassword(pwtext) {
6868
}
6969
```
7070

71-
**Correct (SHA256 hashing):**
71+
**Correct (bcrypt for password hashing):**
7272

7373
```javascript
74-
const crypto = require("crypto");
74+
const bcrypt = require("bcrypt");
7575

76-
function hashPassword(pwtext) {
77-
return crypto.createHash("sha256").update(pwtext).digest("hex");
76+
async function hashPassword(pwtext) {
77+
return bcrypt.hash(pwtext, 12);
78+
}
79+
80+
async function verifyPassword(pwtext, hash) {
81+
return bcrypt.compare(pwtext, hash);
7882
}
7983
```
8084

85+
> **Note:** SHA-256/SHA-512 are fine for data integrity but too fast for password hashing. Use bcrypt, scrypt, or Argon2 for passwords.
86+
8187
---
8288

8389
### Java
@@ -94,21 +100,23 @@ byte[] hash = md5.digest();
94100
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
95101
```
96102

97-
**Correct (SHA-512 hashing):**
103+
**Correct (BCrypt for password hashing):**
98104

99105
```java
100-
import java.security.MessageDigest;
106+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
101107

102-
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
103-
sha512.update(password.getBytes());
104-
byte[] hash = sha512.digest();
108+
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
109+
String hash = encoder.encode(password);
110+
boolean matches = encoder.matches(password, hash);
105111
```
106112

113+
> **Note:** `MessageDigest` (SHA-256/SHA-512) is appropriate for data integrity checks but not for password storage. Use BCrypt, scrypt, or Argon2 for passwords.
114+
107115
**Incorrect (DES cipher):**
108116

109117
```java
110118
Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
111-
c.init(Cipher.ENCRYPT_MODE, k, iv);
119+
c.init(Cipher.ENCRYPT_MODE, k);
112120
```
113121

114122
**Correct (AES with GCM):**

0 commit comments

Comments
 (0)