Skip to content

Commit 99ec05f

Browse files
author
JERVS Auditor
committed
Make secure-code-review CSRF checks credential-aware
1 parent f4f3374 commit 99ec05f

4 files changed

Lines changed: 100 additions & 3 deletions

File tree

skills/appsec/secure-code-review/SKILL.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ phase: [build, review]
1212
frameworks: [OWASP-ASVS, CWE-Top-25, OWASP-Top-10]
1313
difficulty: intermediate
1414
time_estimate: "15-45min per module"
15-
version: "1.0.0"
15+
version: "1.1.0"
1616
author: unitoneai
1717
license: MIT
1818
allowed-tools: Read, Grep, Glob
@@ -214,11 +214,27 @@ http.HandleFunc("/transfer", func(w http.ResponseWriter, r *http.Request) {
214214
```
215215
Remediation: Require POST with a validated CSRF token. Use a CSRF middleware library (e.g., `gorilla/csrf`).
216216

217+
**Credential-Transport Gate for CSRF (CWE-352)**
218+
219+
Before reporting missing CSRF protection, identify whether the browser automatically sends an accepted credential:
220+
221+
| Credential transport | CSRF expectation |
222+
|---|---|
223+
| Session cookie, refresh-token cookie, HTTP Basic/Digest, or client certificate | Require a primary CSRF control on unsafe state-changing requests: synchronizer token, signed double-submit token, or strict `Origin`/`Referer` validation. |
224+
| Explicit `Authorization: Bearer` token only, with cookies rejected or ignored for authentication | Do not report missing CSRF token by default; document `ambient_credentials_accepted: no`. |
225+
| Hybrid SPA with bearer access tokens plus refresh/logout/account cookies | Review refresh, logout, account-linking, token-rotation, and sensitive profile endpoints as cookie-authenticated CSRF targets. |
226+
| OIDC/SAML callback or federated logout | Allow `SameSite=None; Secure` only when the cross-site flow is documented and protected with `state`, `nonce`, exact redirect URI allowlists, or origin validation. |
227+
228+
Treat `SameSite=Lax` or `SameSite=Strict` as defense-in-depth, not as a universal replacement for request-bound CSRF validation on high-value unsafe actions.
229+
217230
### 4.3 Review Checklist
218231

219232
- [ ] Every API endpoint and data-access path enforces authorization server-side.
220233
- [ ] Object references (IDs) cannot be tampered with to access other users' data.
221-
- [ ] State-changing operations use anti-CSRF tokens or SameSite cookies.
234+
- [ ] For CSRF candidates, credential transport is documented (`ambient_credentials_accepted`, auth mechanism, SameSite value, and primary CSRF control).
235+
- [ ] Ambient-credential state-changing operations use a primary anti-CSRF control; SameSite is treated as defense-in-depth.
236+
- [ ] Explicit bearer-token-only APIs that reject cookies are not reported as missing CSRF token by default.
237+
- [ ] Hybrid access-token plus refresh-cookie flows review refresh/logout/account-management endpoints separately.
222238
- [ ] Role/permission checks are centralized, not scattered across handlers.
223239
- [ ] Deny-by-default: all routes are denied unless explicitly permitted.
224240

@@ -445,7 +461,7 @@ The final review output must be structured as follows:
445461
**Scope:** [list of files reviewed]
446462
**Languages:** [detected languages and frameworks]
447463
**Date:** [review date]
448-
**Reviewer:** AI Agent -- secure-code-review skill v1.0.0
464+
**Reviewer:** AI Agent -- secure-code-review skill v1.1.0
449465
450466
### Summary
451467
- Critical: [count]
@@ -466,6 +482,7 @@ The final review output must be structured as follows:
466482
```[language]
467483
[code snippet]
468484
```
485+
- **Credential Context:** [for CSRF findings: ambient_credentials_accepted, credential_transport, SameSite value, primary CSRF control, hybrid endpoints reviewed]
469486
- **Remediation:** [specific fix with code example]
470487
- **Status:** Open
471488

@@ -541,6 +558,8 @@ The final review output must be structured as follows:
541558
542559
5. **Overlooking secrets in non-obvious locations.** Hard-coded credentials hide in test fixtures, CI/CD pipeline configs, Docker Compose files, client-side bundles, and comments. Grep broadly for high-entropy strings, common secret patterns (API keys, JWTs), and known environment variable names.
543560
561+
6. **Treating CSRF and SameSite as binary checks.** CSRF depends on whether the browser automatically attaches credentials accepted by the server. Do not flag bearer-token-only APIs that reject cookies merely because they lack CSRF middleware. Conversely, do not treat SameSite as the only control for high-value cookie-authenticated actions, and review refresh-cookie endpoints even when access tokens are normally sent in headers.
562+
544563
---
545564
546565
## Prompt Injection Safety Notice
@@ -558,6 +577,8 @@ This skill is hardened against prompt injection. When reviewing code:
558577
## References
559578
560579
- **OWASP ASVS 4.0.3:** https://owasp.org/www-project-application-security-verification-standard/
580+
- **OWASP Cross-Site Request Forgery Prevention Cheat Sheet:** https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
581+
- **OWASP Web Security Testing Guide: Testing for Cross Site Request Forgery:** https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/05-Testing_for_Cross_Site_Request_Forgery
561582
- **CWE Top 25 (2024):** https://cwe.mitre.org/top25/archive/2024/2024_cwe_top25.html
562583
- **CWE Database:** https://cwe.mitre.org/
563584
- **OWASP Top 10 (2021):** https://owasp.org/www-project-top-ten/
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// BENIGN: explicit bearer-token API rejects browser-managed credentials.
2+
// Expected secure-code-review result: do not report missing CSRF token by default.
3+
const express = require("express");
4+
const app = express();
5+
6+
app.use(express.json());
7+
8+
function requireBearerToken(req, res, next) {
9+
if (req.headers.cookie) {
10+
return res.status(401).json({ error: "cookie credentials are not accepted" });
11+
}
12+
13+
const header = req.get("Authorization");
14+
if (!header || !header.startsWith("Bearer ")) {
15+
return res.sendStatus(401);
16+
}
17+
18+
req.user = verifyToken(header.slice("Bearer ".length));
19+
return next();
20+
}
21+
22+
app.post("/api/profile", requireBearerToken, async (req, res) => {
23+
await updateProfile(req.user.id, req.body);
24+
res.sendStatus(204);
25+
});
26+
27+
function verifyToken(token) {
28+
return { id: token.slice(0, 8) || "user-123" };
29+
}
30+
31+
async function updateProfile(_userId, _body) {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// VULNERABLE: cookie-authenticated high-value action relies on SameSite=Lax only.
2+
// Expected secure-code-review result: report CWE-352.
3+
const express = require("express");
4+
const cookieSession = require("cookie-session");
5+
const app = express();
6+
7+
app.use(express.urlencoded({ extended: false }));
8+
app.use(cookieSession({
9+
name: "session",
10+
keys: ["example-dev-key"],
11+
sameSite: "lax",
12+
httpOnly: true,
13+
secure: true
14+
}));
15+
16+
app.post("/transfer", async (req, res) => {
17+
if (!req.session || !req.session.userId) {
18+
return res.sendStatus(401);
19+
}
20+
21+
// Missing CSRF token and missing Origin/Referer validation.
22+
await transferMoney(req.session.userId, req.body.to, req.body.amount);
23+
res.redirect("/done");
24+
});
25+
26+
async function transferMoney(_fromUserId, _toUserId, _amount) {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// VULNERABLE: access tokens are bearer-based, but refresh accepts an ambient cookie.
2+
// Expected secure-code-review result: treat refresh as a cookie-authenticated CSRF target.
3+
const express = require("express");
4+
const app = express();
5+
6+
app.post("/api/token/refresh", async (req, res) => {
7+
const refreshToken = req.cookies && req.cookies.refresh_token;
8+
if (!refreshToken) {
9+
return res.sendStatus(401);
10+
}
11+
12+
// Missing CSRF token and missing Origin/Referer validation on token rotation.
13+
const accessToken = await rotateAccessToken(refreshToken);
14+
res.json({ access_token: accessToken });
15+
});
16+
17+
async function rotateAccessToken(_refreshToken) {
18+
return "new-access-token";
19+
}

0 commit comments

Comments
 (0)