@brunosps00/dev-workflow 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dev-workflow.js +6 -4
- package/lib/constants.js +11 -0
- package/lib/init.js +36 -12
- package/lib/wrappers.js +8 -2
- package/package.json +1 -1
- package/scaffold/pt-br/commands/dw-analyze-project.md +3 -3
- package/scaffold/pt-br/commands/dw-bugfix.md +6 -6
- package/scaffold/pt-br/commands/dw-code-review.md +2 -2
- package/scaffold/pt-br/commands/dw-create-tasks.md +4 -4
- package/scaffold/pt-br/commands/dw-generate-pr.md +3 -3
- package/scaffold/pt-br/commands/dw-help.md +50 -50
- package/scaffold/pt-br/commands/dw-review-implementation.md +3 -3
- package/scaffold/pt-br/commands/dw-run-plan.md +8 -8
- package/scaffold/pt-br/commands/dw-run-task.md +3 -3
- package/scaffold/pt-br/templates/tasks-template.md +2 -2
- package/scaffold/skills/agent-browser/SKILL.md +750 -0
- package/scaffold/skills/agent-browser/references/authentication.md +303 -0
- package/scaffold/skills/agent-browser/references/commands.md +295 -0
- package/scaffold/skills/agent-browser/references/profiling.md +120 -0
- package/scaffold/skills/agent-browser/references/proxy-support.md +194 -0
- package/scaffold/skills/agent-browser/references/session-management.md +193 -0
- package/scaffold/skills/agent-browser/references/snapshot-refs.md +219 -0
- package/scaffold/skills/agent-browser/references/video-recording.md +173 -0
- package/scaffold/skills/agent-browser/templates/authenticated-session.sh +105 -0
- package/scaffold/skills/agent-browser/templates/capture-workflow.sh +69 -0
- package/scaffold/skills/agent-browser/templates/form-automation.sh +62 -0
- package/scaffold/skills/humanizer/README.md +143 -0
- package/scaffold/skills/humanizer/SKILL.md +488 -0
- package/scaffold/skills/humanizer/WARP.md +53 -0
- package/scaffold/skills/remotion-best-practices/SKILL.md +61 -0
- package/scaffold/skills/remotion-best-practices/rules/3d.md +86 -0
- package/scaffold/skills/remotion-best-practices/rules/animations.md +27 -0
- package/scaffold/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/scaffold/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/scaffold/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
- package/scaffold/skills/remotion-best-practices/rules/assets.md +78 -0
- package/scaffold/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
- package/scaffold/skills/remotion-best-practices/rules/audio.md +169 -0
- package/scaffold/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
- package/scaffold/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/scaffold/skills/remotion-best-practices/rules/charts.md +120 -0
- package/scaffold/skills/remotion-best-practices/rules/compositions.md +154 -0
- package/scaffold/skills/remotion-best-practices/rules/display-captions.md +184 -0
- package/scaffold/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/scaffold/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
- package/scaffold/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/scaffold/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/scaffold/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/scaffold/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
- package/scaffold/skills/remotion-best-practices/rules/gifs.md +141 -0
- package/scaffold/skills/remotion-best-practices/rules/images.md +134 -0
- package/scaffold/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
- package/scaffold/skills/remotion-best-practices/rules/light-leaks.md +73 -0
- package/scaffold/skills/remotion-best-practices/rules/lottie.md +70 -0
- package/scaffold/skills/remotion-best-practices/rules/maps.md +412 -0
- package/scaffold/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
- package/scaffold/skills/remotion-best-practices/rules/measuring-text.md +140 -0
- package/scaffold/skills/remotion-best-practices/rules/parameters.md +109 -0
- package/scaffold/skills/remotion-best-practices/rules/sequencing.md +118 -0
- package/scaffold/skills/remotion-best-practices/rules/sfx.md +26 -0
- package/scaffold/skills/remotion-best-practices/rules/subtitles.md +36 -0
- package/scaffold/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/scaffold/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/scaffold/skills/remotion-best-practices/rules/timing.md +179 -0
- package/scaffold/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
- package/scaffold/skills/remotion-best-practices/rules/transitions.md +197 -0
- package/scaffold/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
- package/scaffold/skills/remotion-best-practices/rules/trimming.md +51 -0
- package/scaffold/skills/remotion-best-practices/rules/videos.md +171 -0
- package/scaffold/skills/remotion-best-practices/rules/voiceover.md +99 -0
- package/scaffold/skills/security-review/LICENSE +22 -0
- package/scaffold/skills/security-review/SKILL.md +312 -0
- package/scaffold/skills/security-review/infrastructure/docker.md +432 -0
- package/scaffold/skills/security-review/languages/javascript.md +388 -0
- package/scaffold/skills/security-review/languages/python.md +363 -0
- package/scaffold/skills/security-review/references/api-security.md +519 -0
- package/scaffold/skills/security-review/references/authentication.md +353 -0
- package/scaffold/skills/security-review/references/authorization.md +372 -0
- package/scaffold/skills/security-review/references/business-logic.md +443 -0
- package/scaffold/skills/security-review/references/cryptography.md +329 -0
- package/scaffold/skills/security-review/references/csrf.md +398 -0
- package/scaffold/skills/security-review/references/data-protection.md +378 -0
- package/scaffold/skills/security-review/references/deserialization.md +410 -0
- package/scaffold/skills/security-review/references/error-handling.md +436 -0
- package/scaffold/skills/security-review/references/file-security.md +457 -0
- package/scaffold/skills/security-review/references/injection.md +259 -0
- package/scaffold/skills/security-review/references/logging.md +433 -0
- package/scaffold/skills/security-review/references/misconfiguration.md +435 -0
- package/scaffold/skills/security-review/references/modern-threats.md +475 -0
- package/scaffold/skills/security-review/references/ssrf.md +415 -0
- package/scaffold/skills/security-review/references/supply-chain.md +405 -0
- package/scaffold/skills/security-review/references/xss.md +336 -0
- package/scaffold/skills/vercel-react-best-practices/AGENTS.md +3648 -0
- package/scaffold/skills/vercel-react-best-practices/README.md +123 -0
- package/scaffold/skills/vercel-react-best-practices/SKILL.md +146 -0
- package/scaffold/skills/vercel-react-best-practices/rules/_sections.md +46 -0
- package/scaffold/skills/vercel-react-best-practices/rules/_template.md +28 -0
- package/scaffold/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/scaffold/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/scaffold/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/scaffold/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/scaffold/skills/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/scaffold/skills/vercel-react-best-practices/rules/async-defer-await.md +82 -0
- package/scaffold/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/scaffold/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/scaffold/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/scaffold/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +60 -0
- package/scaffold/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/scaffold/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/scaffold/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/scaffold/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/scaffold/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/scaffold/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/scaffold/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/scaffold/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-request-idle-callback.md +105 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/scaffold/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/scaffold/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +149 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
- package/scaffold/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/scaffold/skills/webapp-testing/SKILL.md +133 -0
- package/scaffold/skills/webapp-testing/assets/test-helper.js +56 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Cryptographic Security Reference
|
|
2
|
+
|
|
3
|
+
## Core Principles
|
|
4
|
+
|
|
5
|
+
1. **Avoid storing sensitive data** when possible - the best protection is not having the data
|
|
6
|
+
2. **Use established libraries** - never implement cryptographic algorithms yourself
|
|
7
|
+
3. **Use modern algorithms** - avoid deprecated algorithms even if they seem convenient
|
|
8
|
+
4. **Manage keys securely** - key management is often harder than encryption itself
|
|
9
|
+
|
|
10
|
+
## Encryption Algorithms
|
|
11
|
+
|
|
12
|
+
### Symmetric Encryption
|
|
13
|
+
|
|
14
|
+
**Recommended:**
|
|
15
|
+
- **AES-256-GCM** (preferred) - Provides encryption + authentication
|
|
16
|
+
- **AES-128-GCM** - Acceptable minimum
|
|
17
|
+
- **ChaCha20-Poly1305** - Good alternative, especially on systems without AES hardware
|
|
18
|
+
|
|
19
|
+
**Avoid:**
|
|
20
|
+
- DES, 3DES - Deprecated, insufficient key length
|
|
21
|
+
- RC4 - Broken
|
|
22
|
+
- AES-ECB - Reveals patterns in data
|
|
23
|
+
- AES-CBC without authentication - Vulnerable to padding oracle attacks
|
|
24
|
+
|
|
25
|
+
### Cipher Modes
|
|
26
|
+
|
|
27
|
+
| Mode | Use Case | Notes |
|
|
28
|
+
|------|----------|-------|
|
|
29
|
+
| **GCM** | General purpose | Authenticated encryption (preferred) |
|
|
30
|
+
| **CCM** | Constrained environments | Authenticated encryption |
|
|
31
|
+
| **CTR + HMAC** | When GCM unavailable | Encrypt-then-MAC pattern |
|
|
32
|
+
| **CBC** | Legacy only | Requires separate MAC |
|
|
33
|
+
| **ECB** | Never for data | Reveals patterns |
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
# VULNERABLE: ECB mode
|
|
37
|
+
from Crypto.Cipher import AES
|
|
38
|
+
cipher = AES.new(key, AES.MODE_ECB)
|
|
39
|
+
|
|
40
|
+
# SAFE: GCM mode
|
|
41
|
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
|
42
|
+
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Asymmetric Encryption
|
|
46
|
+
|
|
47
|
+
**Recommended:**
|
|
48
|
+
- **ECC with Curve25519** (preferred for key exchange)
|
|
49
|
+
- **RSA-2048** minimum (RSA-4096 for long-term)
|
|
50
|
+
- **ECDSA with P-256** or Ed25519 for signatures
|
|
51
|
+
|
|
52
|
+
**Avoid:**
|
|
53
|
+
- RSA < 2048 bits
|
|
54
|
+
- DSA
|
|
55
|
+
- ECDSA with weak curves
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Secure Random Number Generation
|
|
60
|
+
|
|
61
|
+
### Cryptographically Secure PRNGs (CSPRNG)
|
|
62
|
+
|
|
63
|
+
| Language | Safe | Unsafe |
|
|
64
|
+
|----------|------|--------|
|
|
65
|
+
| **Python** | `secrets`, `os.urandom()` | `random` module |
|
|
66
|
+
| **JavaScript** | `crypto.randomBytes()`, `crypto.randomUUID()` | `Math.random()` |
|
|
67
|
+
| **Java** | `SecureRandom`, `UUID.randomUUID()` | `Math.random()`, `java.util.Random` |
|
|
68
|
+
| **PHP** | `random_bytes()`, `random_int()` | `rand()`, `mt_rand()`, `uniqid()` |
|
|
69
|
+
| **.NET** | `RandomNumberGenerator` | `Random()` |
|
|
70
|
+
| **Go** | `crypto/rand` | `math/rand` |
|
|
71
|
+
| **Ruby** | `SecureRandom` | `rand()` |
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# VULNERABLE: Predictable random
|
|
75
|
+
import random
|
|
76
|
+
token = ''.join(random.choices(string.ascii_letters, k=32))
|
|
77
|
+
|
|
78
|
+
# SAFE: Cryptographically secure
|
|
79
|
+
import secrets
|
|
80
|
+
token = secrets.token_urlsafe(32)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### UUID Considerations
|
|
84
|
+
|
|
85
|
+
- **UUID v1**: NOT random - contains timestamp and MAC address
|
|
86
|
+
- **UUID v4**: Depends on implementation - verify CSPRNG usage
|
|
87
|
+
- **ULID**: Time-sortable but predictable time component
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# Check if UUID v4 is actually random
|
|
91
|
+
import uuid
|
|
92
|
+
# uuid.uuid4() uses os.urandom() in Python - SAFE
|
|
93
|
+
token = str(uuid.uuid4())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Key Management
|
|
99
|
+
|
|
100
|
+
### Key Generation
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# VULNERABLE: Key from password directly
|
|
104
|
+
key = password.encode()
|
|
105
|
+
|
|
106
|
+
# SAFE: Key derivation function
|
|
107
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
108
|
+
kdf = PBKDF2HMAC(
|
|
109
|
+
algorithm=hashes.SHA256(),
|
|
110
|
+
length=32,
|
|
111
|
+
salt=salt,
|
|
112
|
+
iterations=600000,
|
|
113
|
+
)
|
|
114
|
+
key = kdf.derive(password.encode())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Key Storage
|
|
118
|
+
|
|
119
|
+
**Do:**
|
|
120
|
+
- Use Hardware Security Modules (HSM)
|
|
121
|
+
- Use cloud key management (AWS KMS, Azure Key Vault, GCP KMS)
|
|
122
|
+
- Use dedicated secrets managers (HashiCorp Vault)
|
|
123
|
+
- Store keys separately from encrypted data
|
|
124
|
+
|
|
125
|
+
**Don't:**
|
|
126
|
+
- Hardcode keys in source code
|
|
127
|
+
- Commit keys to version control
|
|
128
|
+
- Store keys in environment variables (can leak)
|
|
129
|
+
- Store keys in plaintext files
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# VULNERABLE: Hardcoded key
|
|
133
|
+
KEY = b'super_secret_key_12345'
|
|
134
|
+
|
|
135
|
+
# VULNERABLE: Key in code as base64
|
|
136
|
+
KEY = base64.b64decode('c3VwZXJfc2VjcmV0X2tleQ==')
|
|
137
|
+
|
|
138
|
+
# SAFE: Load from secure source
|
|
139
|
+
KEY = secrets_manager.get_secret('encryption_key')
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Key Rotation
|
|
143
|
+
|
|
144
|
+
**When to rotate:**
|
|
145
|
+
- Key compromise (immediate)
|
|
146
|
+
- Cryptoperiod expiration (time-based)
|
|
147
|
+
- After encrypting 2^35 bytes (for 64-bit block ciphers)
|
|
148
|
+
- Algorithm deprecation
|
|
149
|
+
|
|
150
|
+
**Rotation strategies:**
|
|
151
|
+
|
|
152
|
+
1. **Re-encryption** (preferred): Decrypt with old key, re-encrypt with new
|
|
153
|
+
2. **Versioning**: Tag encrypted items with key version, maintain multiple keys
|
|
154
|
+
|
|
155
|
+
### Envelope Encryption
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Two-key structure:
|
|
159
|
+
# - Data Encryption Key (DEK): Encrypts actual data
|
|
160
|
+
# - Key Encryption Key (KEK): Encrypts the DEK
|
|
161
|
+
|
|
162
|
+
def encrypt_with_envelope(plaintext, kek):
|
|
163
|
+
# Generate random DEK
|
|
164
|
+
dek = secrets.token_bytes(32)
|
|
165
|
+
|
|
166
|
+
# Encrypt data with DEK
|
|
167
|
+
cipher = AES.new(dek, AES.MODE_GCM)
|
|
168
|
+
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
|
|
169
|
+
|
|
170
|
+
# Encrypt DEK with KEK
|
|
171
|
+
kek_cipher = AES.new(kek, AES.MODE_GCM)
|
|
172
|
+
encrypted_dek, dek_tag = kek_cipher.encrypt_and_digest(dek)
|
|
173
|
+
|
|
174
|
+
# Store encrypted_dek with ciphertext
|
|
175
|
+
return {
|
|
176
|
+
'ciphertext': ciphertext,
|
|
177
|
+
'tag': tag,
|
|
178
|
+
'encrypted_dek': encrypted_dek,
|
|
179
|
+
'dek_tag': dek_tag,
|
|
180
|
+
'nonce': cipher.nonce,
|
|
181
|
+
'dek_nonce': kek_cipher.nonce
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Hashing
|
|
188
|
+
|
|
189
|
+
### Password Hashing
|
|
190
|
+
|
|
191
|
+
See `authentication.md` for password-specific hashing.
|
|
192
|
+
|
|
193
|
+
### General Purpose Hashing
|
|
194
|
+
|
|
195
|
+
| Use Case | Algorithm |
|
|
196
|
+
|----------|-----------|
|
|
197
|
+
| Integrity verification | SHA-256 or SHA-3 |
|
|
198
|
+
| HMAC | HMAC-SHA-256 |
|
|
199
|
+
| Key derivation | HKDF, PBKDF2 |
|
|
200
|
+
| Content addressing | SHA-256 |
|
|
201
|
+
|
|
202
|
+
**Avoid for new systems:**
|
|
203
|
+
- MD5 (broken)
|
|
204
|
+
- SHA-1 (deprecated)
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
# For integrity/checksums
|
|
208
|
+
import hashlib
|
|
209
|
+
digest = hashlib.sha256(data).hexdigest()
|
|
210
|
+
|
|
211
|
+
# For authentication (HMAC)
|
|
212
|
+
import hmac
|
|
213
|
+
mac = hmac.new(key, data, hashlib.sha256).digest()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Common Vulnerabilities
|
|
219
|
+
|
|
220
|
+
### Weak Algorithm Usage
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
# VULNERABLE: MD5 for security purposes
|
|
224
|
+
import hashlib
|
|
225
|
+
checksum = hashlib.md5(data).hexdigest()
|
|
226
|
+
|
|
227
|
+
# VULNERABLE: SHA1 for signatures
|
|
228
|
+
signature = hashlib.sha1(data + secret).hexdigest()
|
|
229
|
+
|
|
230
|
+
# SAFE: SHA-256
|
|
231
|
+
checksum = hashlib.sha256(data).hexdigest()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Insufficient Key Size
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# VULNERABLE: Short key
|
|
238
|
+
key = b'short_key' # 9 bytes
|
|
239
|
+
|
|
240
|
+
# SAFE: Adequate key length
|
|
241
|
+
key = secrets.token_bytes(32) # 256 bits
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Predictable IV/Nonce
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
# VULNERABLE: Reused or predictable nonce
|
|
248
|
+
nonce = b'\x00' * 12 # Static nonce
|
|
249
|
+
|
|
250
|
+
# VULNERABLE: Counter-based without persistence
|
|
251
|
+
nonce = counter.to_bytes(12, 'big')
|
|
252
|
+
|
|
253
|
+
# SAFE: Random nonce
|
|
254
|
+
nonce = secrets.token_bytes(12)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### ECB Mode Patterns
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# VULNERABLE: ECB reveals patterns
|
|
261
|
+
cipher = AES.new(key, AES.MODE_ECB)
|
|
262
|
+
|
|
263
|
+
# SAFE: GCM hides patterns
|
|
264
|
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Missing Authentication
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
# VULNERABLE: Encryption without authentication
|
|
271
|
+
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
|
|
272
|
+
ciphertext = cipher.encrypt(pad(plaintext, 16))
|
|
273
|
+
# Vulnerable to bit-flipping, padding oracle
|
|
274
|
+
|
|
275
|
+
# SAFE: Authenticated encryption
|
|
276
|
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
|
277
|
+
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Grep Patterns for Detection
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Weak algorithms
|
|
286
|
+
grep -rn "MD5\|md5\|SHA1\|sha1\|DES\|des\|RC4\|rc4" --include="*.py" --include="*.js"
|
|
287
|
+
grep -rn "MODE_ECB\|ecb" --include="*.py" --include="*.js"
|
|
288
|
+
|
|
289
|
+
# Insecure random
|
|
290
|
+
grep -rn "Math\.random\|random\.random\|random\.randint" --include="*.py" --include="*.js"
|
|
291
|
+
grep -rn "mt_rand\|rand()" --include="*.php"
|
|
292
|
+
|
|
293
|
+
# Hardcoded keys
|
|
294
|
+
grep -rn "key\s*=\s*['\"]" --include="*.py" --include="*.js"
|
|
295
|
+
grep -rn "secret\s*=\s*['\"]" --include="*.py" --include="*.js"
|
|
296
|
+
grep -rn "AES\.new.*b'" --include="*.py"
|
|
297
|
+
|
|
298
|
+
# Static IVs/nonces
|
|
299
|
+
grep -rn "iv\s*=\s*b'\|nonce\s*=\s*b'" --include="*.py"
|
|
300
|
+
grep -rn "\\x00.*\\x00.*\\x00" --include="*.py"
|
|
301
|
+
|
|
302
|
+
# CBC without HMAC
|
|
303
|
+
grep -rn "MODE_CBC" --include="*.py" | grep -v "hmac\|mac\|tag"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Testing Checklist
|
|
309
|
+
|
|
310
|
+
- [ ] No hardcoded keys/secrets in source code
|
|
311
|
+
- [ ] Keys not committed to version control
|
|
312
|
+
- [ ] Using modern algorithms (AES-GCM, RSA-2048+, SHA-256+)
|
|
313
|
+
- [ ] CSPRNG used for all security-sensitive randomness
|
|
314
|
+
- [ ] Keys stored securely (HSM, KMS, secrets manager)
|
|
315
|
+
- [ ] Key rotation mechanism exists
|
|
316
|
+
- [ ] No ECB mode usage
|
|
317
|
+
- [ ] Authenticated encryption used (GCM, or encrypt-then-MAC)
|
|
318
|
+
- [ ] Adequate key lengths (256-bit symmetric, 2048+ RSA)
|
|
319
|
+
- [ ] IVs/nonces are random and never reused with same key
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## References
|
|
324
|
+
|
|
325
|
+
- [OWASP Cryptographic Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html)
|
|
326
|
+
- [OWASP Key Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html)
|
|
327
|
+
- [CWE-327: Use of Broken Crypto Algorithm](https://cwe.mitre.org/data/definitions/327.html)
|
|
328
|
+
- [CWE-330: Insufficient Randomness](https://cwe.mitre.org/data/definitions/330.html)
|
|
329
|
+
- [CWE-321: Hard-coded Cryptographic Key](https://cwe.mitre.org/data/definitions/321.html)
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# Cross-Site Request Forgery (CSRF) Prevention Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
CSRF attacks trick authenticated users into performing unintended actions by exploiting the browser's automatic credential transmission. The attack works because browsers automatically include cookies with requests to a domain, regardless of the request's origin.
|
|
6
|
+
|
|
7
|
+
## Attack Scenario
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- Attacker's page -->
|
|
11
|
+
<img src="https://bank.com/transfer?to=attacker&amount=10000">
|
|
12
|
+
|
|
13
|
+
<!-- Or form submission -->
|
|
14
|
+
<form action="https://bank.com/transfer" method="POST" id="evil">
|
|
15
|
+
<input name="to" value="attacker">
|
|
16
|
+
<input name="amount" value="10000">
|
|
17
|
+
</form>
|
|
18
|
+
<script>document.getElementById('evil').submit();</script>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When a logged-in user visits the attacker's page, their browser makes the request with their session cookie.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Primary Defenses
|
|
26
|
+
|
|
27
|
+
### 1. Synchronizer Token Pattern
|
|
28
|
+
|
|
29
|
+
Generate and validate a unique token per session.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import secrets
|
|
33
|
+
|
|
34
|
+
# Generate token on session creation
|
|
35
|
+
def create_csrf_token(session_id):
|
|
36
|
+
token = secrets.token_urlsafe(32)
|
|
37
|
+
store_csrf_token(session_id, token)
|
|
38
|
+
return token
|
|
39
|
+
|
|
40
|
+
# Include in forms
|
|
41
|
+
def render_form():
|
|
42
|
+
token = get_csrf_token(session.id)
|
|
43
|
+
return f'''
|
|
44
|
+
<form method="POST">
|
|
45
|
+
<input type="hidden" name="csrf_token" value="{token}">
|
|
46
|
+
<!-- form fields -->
|
|
47
|
+
</form>
|
|
48
|
+
'''
|
|
49
|
+
|
|
50
|
+
# Validate on submission
|
|
51
|
+
def validate_csrf():
|
|
52
|
+
submitted_token = request.form.get('csrf_token')
|
|
53
|
+
stored_token = get_csrf_token(session.id)
|
|
54
|
+
|
|
55
|
+
if not submitted_token or not secrets.compare_digest(submitted_token, stored_token):
|
|
56
|
+
raise CSRFValidationError()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Double Submit Cookie Pattern (Stateless)
|
|
60
|
+
|
|
61
|
+
Use a cryptographically signed token that doesn't require server-side storage.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import hmac
|
|
65
|
+
import hashlib
|
|
66
|
+
import time
|
|
67
|
+
|
|
68
|
+
SECRET_KEY = os.environ['CSRF_SECRET']
|
|
69
|
+
|
|
70
|
+
def generate_csrf_token(session_id):
|
|
71
|
+
"""Generate signed token tied to session."""
|
|
72
|
+
timestamp = int(time.time())
|
|
73
|
+
message = f"{session_id}:{timestamp}"
|
|
74
|
+
signature = hmac.new(
|
|
75
|
+
SECRET_KEY.encode(),
|
|
76
|
+
message.encode(),
|
|
77
|
+
hashlib.sha256
|
|
78
|
+
).hexdigest()
|
|
79
|
+
return f"{timestamp}:{signature}"
|
|
80
|
+
|
|
81
|
+
def validate_csrf_token(token, session_id):
|
|
82
|
+
"""Validate token matches session and isn't expired."""
|
|
83
|
+
try:
|
|
84
|
+
timestamp, signature = token.split(':')
|
|
85
|
+
timestamp = int(timestamp)
|
|
86
|
+
|
|
87
|
+
# Check expiry (1 hour)
|
|
88
|
+
if time.time() - timestamp > 3600:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# Verify signature
|
|
92
|
+
message = f"{session_id}:{timestamp}"
|
|
93
|
+
expected = hmac.new(
|
|
94
|
+
SECRET_KEY.encode(),
|
|
95
|
+
message.encode(),
|
|
96
|
+
hashlib.sha256
|
|
97
|
+
).hexdigest()
|
|
98
|
+
|
|
99
|
+
return secrets.compare_digest(signature, expected)
|
|
100
|
+
except:
|
|
101
|
+
return False
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. SameSite Cookie Attribute
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Modern browsers respect SameSite attribute
|
|
108
|
+
response.set_cookie(
|
|
109
|
+
'session_id',
|
|
110
|
+
value=session_id,
|
|
111
|
+
samesite='Lax', # Or 'Strict' for maximum protection
|
|
112
|
+
secure=True,
|
|
113
|
+
httponly=True
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**SameSite Values:**
|
|
118
|
+
|
|
119
|
+
| Value | Behavior |
|
|
120
|
+
|-------|----------|
|
|
121
|
+
| **Strict** | Never sent cross-site |
|
|
122
|
+
| **Lax** | Sent only with safe methods (GET) on top-level navigation |
|
|
123
|
+
| **None** | Always sent (requires Secure) |
|
|
124
|
+
|
|
125
|
+
### 4. Custom Request Headers
|
|
126
|
+
|
|
127
|
+
For AJAX/API requests, require a custom header that can't be set cross-origin without CORS.
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// Client
|
|
131
|
+
fetch('/api/transfer', {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'X-CSRF-Token': getCSRFToken() // Or any custom header
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify(data)
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# Server
|
|
143
|
+
@app.before_request
|
|
144
|
+
def verify_csrf_header():
|
|
145
|
+
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
|
|
146
|
+
token = request.headers.get('X-CSRF-Token')
|
|
147
|
+
if not validate_csrf_token(token):
|
|
148
|
+
return jsonify({'error': 'CSRF validation failed'}), 403
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Framework Implementations
|
|
154
|
+
|
|
155
|
+
### Django
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Enabled by default via middleware
|
|
159
|
+
MIDDLEWARE = [
|
|
160
|
+
'django.middleware.csrf.CsrfViewMiddleware',
|
|
161
|
+
...
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
# In templates
|
|
165
|
+
<form method="POST">
|
|
166
|
+
{% csrf_token %}
|
|
167
|
+
...
|
|
168
|
+
</form>
|
|
169
|
+
|
|
170
|
+
# For AJAX
|
|
171
|
+
<script>
|
|
172
|
+
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
|
173
|
+
fetch('/api/endpoint', {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: {'X-CSRFToken': csrftoken},
|
|
176
|
+
...
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Flask
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from flask_wtf.csrf import CSRFProtect
|
|
185
|
+
|
|
186
|
+
csrf = CSRFProtect(app)
|
|
187
|
+
|
|
188
|
+
# In templates
|
|
189
|
+
<form method="POST">
|
|
190
|
+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
191
|
+
...
|
|
192
|
+
</form>
|
|
193
|
+
|
|
194
|
+
# Exempt specific routes if needed (be careful!)
|
|
195
|
+
@csrf.exempt
|
|
196
|
+
@app.route('/webhook', methods=['POST'])
|
|
197
|
+
def webhook():
|
|
198
|
+
pass
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Express.js
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
const csrf = require('csurf');
|
|
205
|
+
const csrfProtection = csrf({ cookie: true });
|
|
206
|
+
|
|
207
|
+
app.use(csrfProtection);
|
|
208
|
+
|
|
209
|
+
app.get('/form', (req, res) => {
|
|
210
|
+
res.render('form', { csrfToken: req.csrfToken() });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// In template
|
|
214
|
+
<form method="POST">
|
|
215
|
+
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
|
216
|
+
...
|
|
217
|
+
</form>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Origin and Referer Validation
|
|
223
|
+
|
|
224
|
+
As a supplementary defense:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
def verify_origin():
|
|
228
|
+
"""Verify request origin matches expected domain."""
|
|
229
|
+
origin = request.headers.get('Origin')
|
|
230
|
+
referer = request.headers.get('Referer')
|
|
231
|
+
|
|
232
|
+
# Prefer Origin header
|
|
233
|
+
if origin:
|
|
234
|
+
if not is_trusted_origin(origin):
|
|
235
|
+
return False
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
# Fall back to Referer
|
|
239
|
+
if referer:
|
|
240
|
+
parsed = urlparse(referer)
|
|
241
|
+
if not is_trusted_origin(f"{parsed.scheme}://{parsed.netloc}"):
|
|
242
|
+
return False
|
|
243
|
+
return True
|
|
244
|
+
|
|
245
|
+
# No origin info - could be same-origin or direct request
|
|
246
|
+
# Decision depends on security requirements
|
|
247
|
+
return True # Or False for strict validation
|
|
248
|
+
|
|
249
|
+
def is_trusted_origin(origin):
|
|
250
|
+
TRUSTED = {'https://example.com', 'https://admin.example.com'}
|
|
251
|
+
return origin in TRUSTED
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Fetch Metadata Headers
|
|
257
|
+
|
|
258
|
+
Modern browsers send additional headers that indicate request context:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
def check_fetch_metadata():
|
|
262
|
+
"""Use Fetch Metadata headers for CSRF protection."""
|
|
263
|
+
sec_fetch_site = request.headers.get('Sec-Fetch-Site')
|
|
264
|
+
sec_fetch_mode = request.headers.get('Sec-Fetch-Mode')
|
|
265
|
+
|
|
266
|
+
# Allow same-origin requests
|
|
267
|
+
if sec_fetch_site == 'same-origin':
|
|
268
|
+
return True
|
|
269
|
+
|
|
270
|
+
# Allow navigation requests (clicking links)
|
|
271
|
+
if sec_fetch_site == 'none' and sec_fetch_mode == 'navigate':
|
|
272
|
+
return True
|
|
273
|
+
|
|
274
|
+
# Block cross-origin state-changing requests
|
|
275
|
+
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
|
|
276
|
+
if sec_fetch_site in ('cross-site', 'same-site'):
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
return True
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Client-Side CSRF
|
|
285
|
+
|
|
286
|
+
Modern variant where JavaScript code uses attacker-controlled input:
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// VULNERABLE: URL fragment used in request
|
|
290
|
+
const param = window.location.hash.substring(1);
|
|
291
|
+
fetch(`/api/action?${param}`, { method: 'POST' });
|
|
292
|
+
|
|
293
|
+
// Attack: https://example.com#action=delete&target=all
|
|
294
|
+
|
|
295
|
+
// SAFE: Validate before use
|
|
296
|
+
const allowedActions = ['view', 'refresh'];
|
|
297
|
+
const param = window.location.hash.substring(1);
|
|
298
|
+
const parsed = new URLSearchParams(param);
|
|
299
|
+
if (allowedActions.includes(parsed.get('action'))) {
|
|
300
|
+
fetch(`/api/action?${param}`, { method: 'POST' });
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Common Mistakes
|
|
307
|
+
|
|
308
|
+
### 1. GET Requests for State Changes
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
# VULNERABLE: State change via GET
|
|
312
|
+
@app.route('/delete/<id>')
|
|
313
|
+
def delete_item(id):
|
|
314
|
+
Item.delete(id) # Attacker: <img src="/delete/123">
|
|
315
|
+
|
|
316
|
+
# SAFE: Use POST for state changes
|
|
317
|
+
@app.route('/delete/<id>', methods=['POST'])
|
|
318
|
+
@csrf_required
|
|
319
|
+
def delete_item(id):
|
|
320
|
+
Item.delete(id)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 2. CORS Misconfiguration
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
# VULNERABLE: Allows any origin with credentials
|
|
327
|
+
@app.after_request
|
|
328
|
+
def add_cors(response):
|
|
329
|
+
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin')
|
|
330
|
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
331
|
+
return response
|
|
332
|
+
|
|
333
|
+
# SAFE: Explicit allowlist
|
|
334
|
+
ALLOWED_ORIGINS = {'https://trusted.com'}
|
|
335
|
+
|
|
336
|
+
@app.after_request
|
|
337
|
+
def add_cors(response):
|
|
338
|
+
origin = request.headers.get('Origin')
|
|
339
|
+
if origin in ALLOWED_ORIGINS:
|
|
340
|
+
response.headers['Access-Control-Allow-Origin'] = origin
|
|
341
|
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
342
|
+
return response
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 3. Token in URL
|
|
346
|
+
|
|
347
|
+
```html
|
|
348
|
+
<!-- VULNERABLE: Token exposed in URL (logged, cached, referer) -->
|
|
349
|
+
<a href="/action?csrf_token=abc123">Do Action</a>
|
|
350
|
+
|
|
351
|
+
<!-- SAFE: Token in form -->
|
|
352
|
+
<form method="POST" action="/action">
|
|
353
|
+
<input type="hidden" name="csrf_token" value="abc123">
|
|
354
|
+
<button type="submit">Do Action</button>
|
|
355
|
+
</form>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Grep Patterns for Detection
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Missing CSRF protection
|
|
364
|
+
grep -rn "@app\.route.*POST\|@router\.post" --include="*.py" | grep -v "csrf"
|
|
365
|
+
|
|
366
|
+
# State-changing GET requests
|
|
367
|
+
grep -rn "\.delete\|\.update\|\.create" --include="*.py" | grep "GET"
|
|
368
|
+
|
|
369
|
+
# CORS wildcards
|
|
370
|
+
grep -rn "Access-Control-Allow-Origin.*\*" --include="*.py"
|
|
371
|
+
|
|
372
|
+
# Framework CSRF disabled
|
|
373
|
+
grep -rn "csrf_exempt\|WTF_CSRF_ENABLED.*False\|csrf.*disable" --include="*.py"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Testing Checklist
|
|
379
|
+
|
|
380
|
+
- [ ] All state-changing requests require POST/PUT/DELETE
|
|
381
|
+
- [ ] CSRF tokens included in all forms
|
|
382
|
+
- [ ] CSRF tokens validated on submission
|
|
383
|
+
- [ ] SameSite cookie attribute set (Lax or Strict)
|
|
384
|
+
- [ ] Custom headers required for API requests
|
|
385
|
+
- [ ] Origin/Referer validated as secondary defense
|
|
386
|
+
- [ ] Fetch Metadata headers checked where supported
|
|
387
|
+
- [ ] CORS properly configured (no wildcard with credentials)
|
|
388
|
+
- [ ] Token not exposed in URL/logs
|
|
389
|
+
- [ ] GET requests never change state
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## References
|
|
394
|
+
|
|
395
|
+
- [OWASP CSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
|
|
396
|
+
- [CWE-352: Cross-Site Request Forgery](https://cwe.mitre.org/data/definitions/352.html)
|
|
397
|
+
- [Fetch Metadata Headers](https://web.dev/fetch-metadata/)
|
|
398
|
+
- [SameSite Cookies Explained](https://web.dev/samesite-cookies-explained/)
|