security-authz
Load when reviewing code that handles permissions, access control, RBAC, IDOR, JWT validation, session management, or protected resources.
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
Load when reviewing code that handles permissions, access control, RBAC, IDOR, JWT validation, session management, or protected resources.
Agent definition
security-authz.mdAuthorization Security Patterns
Load when reviewing code that handles permissions, access control, RBAC, IDOR, JWT validation, session management, or protected resources.
Authorization answers: is this principal permitted to perform this action on this resource?
---
Scope Querysets to the Authenticated User
Every database query for a user-owned resource must filter by the authenticated principal. The query is the authorization boundary.
Correct Pattern
**Django/DRF:**
class OrderViewSet(ModelViewSet):
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(organization=self.request.user.organization)**Express/Prisma:**
router.get('/orders/:id', requireAuth, async (req, res) => {
const order = await db.order.findFirst({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.sendStatus(404);
res.json(order);
});**FastAPI:**
@router.get("/orders/{order_id}")
async def get_order(order_id: int, user: User = Depends(get_current_user)):
order = await Order.filter(id=order_id, owner=user).first()
if not order:
raise HTTPException(status_code=404)
return order**Go:**
func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
userID := auth.UserIDFromContext(r.Context())
orderID := chi.URLParam(r, "orderID")
order, err := h.store.GetOrderForUser(r.Context(), orderID, userID)
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(order)
}Why This Matters
Unscoped queries are the most common authorization vulnerability. `Order.objects.get(id=order_id)` without a user filter lets any authenticated user read any order by enumerating IDs (IDOR).
Real incidents: Shopify HackerOne #2207248, SingleStore HackerOne #3219944.
**CVEs:** OWASP A01:2021 Broken Access Control — #1 web vulnerability category.
Detection
rg -n 'class \w+ViewSet.*ModelViewSet' --type py -l | xargs rg -L 'def get_queryset'
rg -n 'objects\.(get|filter)\(id=.*kwargs\[|id=.*request\.(GET|POST|data)' --type py
rg -n 'findUnique\(\{.*where:.*req\.params' --type ts
rg -n 'GetOrder|FindOrder|FetchOrder' --type go -l | xargs rg -n 'func.*http\.Request'---
Close Permission Functions with Explicit Default Deny
Every permission function must end with `return False` (or equivalent). Unrecognized roles must deny access.
Correct Pattern
**Python:**
def can_edit(user, resource):
if user.role == "admin":
return True
if user.role == "editor" and resource.owner_id == user.id:
return True
return False**TypeScript:**
function canEdit(user: User, resource: Resource): boolean {
if (user.role === 'admin') return true;
if (user.role === 'editor' && resource.ownerId === user.id) return true;
return false;
}**Go:**
func canEdit(user *User, resource *Resource) bool {
switch user.Role {
case "admin":
return true
case "editor":
return resource.OwnerID == user.ID
default:
return false
}
}Why This Matters
Missing `return False` causes `None`/`undefined` returns, which are falsy but leak through callers checking truthiness differently. Apollo Router CVE-2025-64347: a renamed directive bypassed authorization because the default path allowed the request.
Detection
rg -n 'def (can_|has_|check_|is_allowed|is_authorized)' --type py
rg -n 'function (can|has|check|isAllowed|isAuthorized)' --type ts
---
Restrict Writable Fields with Explicit Allowlist
Serializers and update handlers must declare exactly which fields a client can write. Spreading request body into DB operations allows mass assignment.
Correct Pattern
**Django/DRF:**
class UserProfileSerializer(ModelSerializer):
class Meta:
model = User
fields = ['display_name', 'avatar_url', 'timezone']**Express/Zod:**
const ProfileUpdate = z.object({
displayName: z.string().max(80).optional(),
avatarUrl: z.string().url().optional(),
timezone: z.string().optional(),
});
router.patch('/me', requireAuth, async (req, res) => {
const data = ProfileUpdate.parse(req.body);
const user = await db.user.update({ where: { id: req.user.id }, data });
res.json(user);
});**FastAPI/Pydantic:**
class ProfileUpdate(BaseModel):
display_name: str | None = None
avatar_url: HttpUrl | None = None
timezone: str | None = None
class Config:
extra = "forbid"Why This Matters
DRF's `fields = '__all__'` on a write endpoint exposes every model field including `is_staff`, `is_superuser`. Spreading `req.body` into Prisma's `data` does the same.
**CVEs:** OWASP A01:2021, Rails CVE-2012-2661, GitHub 2012 mass-assignment incident.
Detection
rg -n "fields = '__all__'" --type py
rg -n 'data: req\.body|\.create\(req\.body\)|\.update\(.*req\.body' --type ts
rg -n "extra = .allow." --type py
---
Apply Authorization Guards at the Router Level
Mount permission middleware on the router or controller, not per-handler. A new endpoint without the decorator is silently unprotected.
Correct Pattern
**FastAPI:**
admin_router = APIRouter(
prefix="/admin",
dependencies=[Depends(require_admin)]
)**Express:**
app.use('/admin', requireAuth, requireAdmin, adminRouter);**NestJS:**
@UseGuards(AuthGuard, AdminGuard)
@Controller('admin')
export class AdminController {
@Get('users')
findAll() { return this.userService.findAll(); }
}**Django:**
MIDDLEWARE = [
...
'django.contrib.auth.middleware.LoginRequiredMiddleware',
]Why This Matters
MLflow ajax-api endpoints shipped without `Depends()` — the guard existed but was never attached. Every unguarded route is a potential privi
Read more
Authorization Security Patterns
Load when reviewing code that handles permissions, access control, RBAC, IDOR, JWT validation, session management, or protected resources.
Authorization answers: is this principal permitted to perform this action on this resource?
---
Scope Querysets to the Authenticated User
Every database query for a user-owned resource must filter by the authenticated principal. The query is the authorization boundary.
Correct Pattern
**Django/DRF:**
class OrderViewSet(ModelViewSet):
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(organization=self.request.user.organization)**Express/Prisma:**
router.get('/orders/:id', requireAuth, async (req, res) => {
const order = await db.order.findFirst({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.sendStatus(404);
res.json(order);
});**FastAPI:**
@router.get("/orders/{order_id}")
async def get_order(order_id: int, user: User = Depends(get_current_user)):
order = await Order.filter(id=order_id, owner=user).first()
if not order:
raise HTTPException(status_code=404)
return order**Go:**
func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
userID := auth.UserIDFromContext(r.Context())
orderID := chi.URLParam(r, "orderID")
order, err := h.store.GetOrderForUser(r.Context(), orderID, userID)
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(order)
}Why This Matters
Unscoped queries are the most common authorization vulnerability. `Order.objects.get(id=order_id)` without a user filter lets any authenticated user read any order by enumerating IDs (IDOR).
Real incidents: Shopify HackerOne #2207248, SingleStore HackerOne #3219944.
**CVEs:** OWASP A01:2021 Broken Access Control — #1 web vulnerability category.
Detection
rg -n 'class \w+ViewSet.*ModelViewSet' --type py -l | xargs rg -L 'def get_queryset'
rg -n 'objects\.(get|filter)\(id=.*kwargs\[|id=.*request\.(GET|POST|data)' --type py
rg -n 'findUnique\(\{.*where:.*req\.params' --type ts
rg -n 'GetOrder|FindOrder|FetchOrder' --type go -l | xargs rg -n 'func.*http\.Request'---
Close Permission Functions with Explicit Default Deny
Every permission function must end with `return False` (or equivalent). Unrecognized roles must deny access.
Correct Pattern
**Python:**
def can_edit(user, resource):
if user.role == "admin":
return True
if user.role == "editor" and resource.owner_id == user.id:
return True
return False**TypeScript:**
function canEdit(user: User, resource: Resource): boolean {
if (user.role === 'admin') return true;
if (user.role === 'editor' && resource.ownerId === user.id) return true;
return false;
}**Go:**
func canEdit(user *User, resource *Resource) bool {
switch user.Role {
case "admin":
return true
case "editor":
return resource.OwnerID == user.ID
default:
return false
}
}Why This Matters
Missing `return False` causes `None`/`undefined` returns, which are falsy but leak through callers checking truthiness differently. Apollo Router CVE-2025-64347: a renamed directive bypassed authorization because the default path allowed the request.
Detection
rg -n 'def (can_|has_|check_|is_allowed|is_authorized)' --type py rg -n 'function (can|has|check|isAllowed|isAuthorized)' --type ts
---
Restrict Writable Fields with Explicit Allowlist
Serializers and update handlers must declare exactly which fields a client can write. Spreading request body into DB operations allows mass assignment.
Correct Pattern
**Django/DRF:**
class UserProfileSerializer(ModelSerializer):
class Meta:
model = User
fields = ['display_name', 'avatar_url', 'timezone']**Express/Zod:**
const ProfileUpdate = z.object({
displayName: z.string().max(80).optional(),
avatarUrl: z.string().url().optional(),
timezone: z.string().optional(),
});
router.patch('/me', requireAuth, async (req, res) => {
const data = ProfileUpdate.parse(req.body);
const user = await db.user.update({ where: { id: req.user.id }, data });
res.json(user);
});**FastAPI/Pydantic:**
class ProfileUpdate(BaseModel):
display_name: str | None = None
avatar_url: HttpUrl | None = None
timezone: str | None = None
class Config:
extra = "forbid"Why This Matters
DRF's `fields = '__all__'` on a write endpoint exposes every model field including `is_staff`, `is_superuser`. Spreading `req.body` into Prisma's `data` does the same.
**CVEs:** OWASP A01:2021, Rails CVE-2012-2661, GitHub 2012 mass-assignment incident.
Detection
rg -n "fields = '__all__'" --type py rg -n 'data: req\.body|\.create\(req\.body\)|\.update\(.*req\.body' --type ts rg -n "extra = .allow." --type py
---
Apply Authorization Guards at the Router Level
Mount permission middleware on the router or controller, not per-handler. A new endpoint without the decorator is silently unprotected.
Correct Pattern
**FastAPI:**
admin_router = APIRouter(
prefix="/admin",
dependencies=[Depends(require_admin)]
)**Express:**
app.use('/admin', requireAuth, requireAdmin, adminRouter);**NestJS:**
@UseGuards(AuthGuard, AdminGuard)
@Controller('admin')
export class AdminController {
@Get('users')
findAll() { return this.userService.findAll(); }
}**Django:**
MIDDLEWARE = [
...
'django.contrib.auth.middleware.LoginRequiredMiddleware',
]Why This Matters
MLflow ajax-api endpoints shipped without `Depends()` — the guard existed but was never attached. Every unguarded route is a potential privi
Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

