Skip to content
Development
Agent

security-authz

Load when reviewing code that handles permissions, access control, RBAC, IDOR, JWT validation, session management, or protected resources.

From plugin
vexjoy-agent
449198 skills198 agents12 commands83 hooks

Claude Code

$ npx -y skills add notque/vexjoy-agent --agent claude-code

Agent definition

security-authz.md

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

Read more
@notque
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.

44964 skills198 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
449
Star history of notque/vexjoy-agent on GitHub, a chart by star-history.com

Other agents on vexjoy-agent.

modules

modules

Agent

**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes…

@notque@notqueInstall Agent
testing

testing

Agent

**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**:…

@notque@notqueInstall Agent
communication-patterns

communication-patterns

Agent

**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what…

@notque@notqueInstall Agent

More agents from other plugins.

a11y-architect
Auto-invoked

a11y-architect

Agent

Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI…

ecc13d293k
@affaan-m@affaan-mInstall Agent
analyze-pass-impact

analyze-pass-impact

Agent

Analyzes how a specific topic affects a group of compiler passes. Used by the /plan-update skill to parallelize research across…

react2d252k
@facebook@facebookInstall Agent
orchestrator

orchestrator

Agent

- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as…

codex2d135k
@openai@openaiInstall Agent
cavecrew-builder

cavecrew-builder

Agent

Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical renames, comment removal, format-preserving tweaks. Hard…

caveman3d119k
@juliusbrussee@juliusbrusseeInstall Agent

Questions about security-authz.

Which coding agents can use security-authz?
security-authz installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.