first commit
This commit is contained in:
89
software-development/csp-form-action-debugging/SKILL.md
Normal file
89
software-development/csp-form-action-debugging/SKILL.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: csp-form-action-debugging
|
||||
description: "Debug CSP form-action blocking issues — Chrome silently blocks forms when action URLs contain certain patterns in query params."
|
||||
tags: [csp, security, debugging, form-action, chrome]
|
||||
triggers:
|
||||
- "form submission fails silently"
|
||||
- "CSP form-action violation"
|
||||
- "login redirect not working"
|
||||
- "form POST blocked by browser"
|
||||
- "ERR_ABORTED on form submit"
|
||||
---
|
||||
|
||||
# CSP form-action Debugging
|
||||
|
||||
## The Core Problem
|
||||
|
||||
Chrome's CSP `form-action 'self'` implementation has a known behavior: when a form's `action` URL contains `//` in the **query string** (e.g., `?redirect=https%3A//example.com/`), the CSP evaluator may decode `%3A` → `:` internally, interpret `://` as a scheme separator, and block the submission as cross-origin — even though the action URL itself is same-origin.
|
||||
|
||||
**Symptom:** Form POST returns `net::ERR_ABORTED` in DevTools network tab. Cookie may still be set (server responded), but browser doesn't follow the redirect.
|
||||
|
||||
**Console error:**
|
||||
```
|
||||
Sending form data to 'https://same-origin.example/api?redirect=https%3A//other.example/'
|
||||
violates the following Content Security Policy directive: "form-action 'self'".
|
||||
The request has been blocked.
|
||||
```
|
||||
|
||||
## Diagnosis Steps
|
||||
|
||||
1. **Check CSP headers** on the page:
|
||||
```
|
||||
curl -sI "https://page-url" | grep -i content-security-policy
|
||||
```
|
||||
Look for `form-action` directive.
|
||||
|
||||
2. **Check form action** — is it a clean URL or does it carry redirect/return params in query string?
|
||||
```js
|
||||
document.querySelector('form').action
|
||||
```
|
||||
|
||||
3. **Test without query params** — if form works without the redirect param in action URL, CSP is the culprit.
|
||||
|
||||
4. **Verify with Playwright console listener:**
|
||||
```python
|
||||
page.on("console", lambda msg: print(f"[{msg.type}] {msg.text}"))
|
||||
# CSP violations appear as [error] messages
|
||||
```
|
||||
|
||||
## Fix Pattern
|
||||
|
||||
**Move redirect/return_url from query string to hidden form field:**
|
||||
|
||||
Before (broken):
|
||||
```html
|
||||
<form method="POST" action="/api/login?redirect=https%3A//example.com/">
|
||||
```
|
||||
|
||||
After (fixed):
|
||||
```html
|
||||
<form method="POST" action="/api/login">
|
||||
<input type="hidden" name="redirect" value="https://example.com/">
|
||||
```
|
||||
|
||||
Backend change — read from Form body instead of Query params:
|
||||
```python
|
||||
# Before
|
||||
async def login(redirect: str | None = Query(default=None)):
|
||||
# After
|
||||
async def login(redirect: str | None = Form(default=None)):
|
||||
```
|
||||
|
||||
## Why This Happens
|
||||
|
||||
Chrome's CSP parser normalizes URLs before checking against `form-action`. The normalization decodes percent-encoded characters in the query string, turning `%3A//` into `://`. The parser then treats the substring after `://` as a different-origin host.
|
||||
|
||||
This does NOT affect:
|
||||
- Relative paths in query params (`?redirect=/dashboard`)
|
||||
- Same-origin absolute URLs in query params (`?redirect=https://same-origin.example/page`)
|
||||
- Cross-origin URLs that don't contain `//` (rare)
|
||||
|
||||
It DOES affect:
|
||||
- Cross-origin URLs with `//` in query params (the common case for redirect parameters)
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Don't just remove `form-action` from CSP** — it's a valuable XSS mitigation. The hidden field fix is better.
|
||||
- **`redirect: 'manual'` in fetch** shows the response as `opaqueredirect` with status 0 — this confirms the server IS responding correctly; the block is client-side.
|
||||
- **Rate limiting can confuse diagnosis** — if you're testing login repeatedly, 429 responses may appear alongside CSP errors. Wait 60s between tests or use fresh browser contexts.
|
||||
- **This affects ALL forms**, not just login. Any form that passes URLs in query parameters (e.g., `?return_to=`, `?next=`, `?callback=`) is vulnerable.
|
||||
Reference in New Issue
Block a user