[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7ACKKAS1WFP2BN5Z7JDZXPS":3,"skill-same:01M7ACKKAS1WFP2BN5Z7JDZXPS":67},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":46,"commitSha":47,"skillDirectory":48,"forksCount":14,"repositoryHtmlUrl":49,"repositoryHomepageUrl":6,"ownerHtmlUrl":50,"ownerBlogUrl":51,"ownerDescription":52,"updatedAt":53,"install":54,"resources":66},"01M7ACKKAS1WFP2BN5Z7JDZXPS","Security",null,"Provides expert knowledge for implementing and reviewing application security following OWASP Top 10 (2021), with stack-specific patterns for Spring Boot, Django, Next.js, and Astro.","lgzarturo\u002Ffeedback_widget","lgzarturo","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F128883?v=4","01M720TSS2BWM10Y93J765VZ8E","algforge","01M7ACJRYQY4Z9SAB0Z7HQA0M0",0,"MIT","https:\u002F\u002Fgithub.com\u002Flgzarturo\u002Ffeedback_widget\u002Fblob\u002Fmain\u002F.cursor\u002Fskills\u002Fsecurity\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nid: security\nversion: 1.0.0\nname: Security\ndescription: >\n  Provides expert knowledge for implementing and reviewing application security following OWASP Top 10 (2021), with stack-specific patterns for Spring Boot, Django, Next.js, and Astro.\n\nuser-invokable: true\nlicense: MIT\nmetadata:\n  author: lgzarturo\n  category: security\n\ncompatibility:\n  tools: [claude, codex, gemini, agy, opencode]\n  stacks:\n    languages: []\n    frameworks: []\n\nrisk:\n  level: high\n  can_execute_shell: false\n  can_modify_files: true\n  requires_network: false\n\ninputs: []\n\noutputs: []\n\nquality:\n  reviewed_by: codeconductor-core\n  version: 0.1.0\n---\n\n\n\n# Security\n\n## OWASP Top 10 (2021) — Quick Reference\n\n| # | Category | What to check |\n|---|----------|--------------|\n| A01 | Broken Access Control | Is authorization enforced on every endpoint? |\n| A02 | Cryptographic Failures | Are secrets encrypted at rest and in transit? |\n| A03 | Injection | Is all input sanitized or parameterized? |\n| A04 | Insecure Design | Is the threat model documented? |\n| A05 | Security Misconfiguration | Are defaults changed? Debug off in prod? |\n| A06 | Vulnerable Components | Are dependencies up to date? |\n| A07 | Auth & Session Failures | Are sessions invalidated on logout? Token expiry set? |\n| A08 | Software Integrity Failures | Are supply-chain dependencies verified? |\n| A09 | Logging & Monitoring Failures | Are security events logged without leaking PII? |\n| A10 | SSRF | Are outbound URLs allowlisted? |\n\n## Input Validation\n\nValidate all input at system boundaries. Never trust data from: HTTP request\nbody, query parameters, headers, cookies, or environment variables passed by\nexternal systems.\n\n**Rules:**\n\n- Validate type, format, length, and range\n- Reject unknown fields — do not silently ignore them\n- Use allowlists (accept known-good), not blocklists (reject known-bad)\n- Validate at the API layer before it reaches the service layer\n\n```kotlin\n\u002F\u002F Spring Boot — Bean Validation\ndata class CreateUserRequest(\n    @field:NotBlank @field:Email val email: String,\n    @field:Size(min = 8, max = 72) val password: String,\n    @field:Size(max = 100) val name: String\n)\n\n@PostMapping\nfun createUser(@Valid @RequestBody request: CreateUserRequest): ResponseEntity\u003CUserResponse> {\n    \u002F\u002F request is guaranteed valid here\n}\n```\n\n```python\n# Django REST Framework — serializer validation\nclass CreateUserSerializer(serializers.Serializer):\n    email = serializers.EmailField(max_length=254)\n    password = serializers.CharField(min_length=8, max_length=72, write_only=True)\n    name = serializers.CharField(max_length=100)\n\n    def validate_email(self, value):\n        if User.objects.filter(email=value).exists():\n            raise serializers.ValidationError(\"Email already registered.\")\n        return value.lower()\n```\n\n```typescript\n\u002F\u002F Next.js \u002F Astro — Zod\nimport { z } from 'zod';\n\nconst CreateUserSchema = z.object({\n  email: z.string().email().max(254),\n  password: z.string().min(8).max(72),\n  name: z.string().max(100),\n});\n\nconst result = CreateUserSchema.safeParse(body);\nif (!result.success) {\n  return Response.json({ error: result.error.flatten() }, { status: 400 });\n}\n```\n\n## Authentication\n\n### JWT (Stateless)\n\nUse JWTs for stateless API authentication. Keep access tokens short-lived.\n\n**Token lifetimes:**\n\n| Token | Lifetime | Storage |\n|-------|----------|---------|\n| Access token | 15 minutes | Memory (never localStorage) |\n| Refresh token | 7–30 days | HttpOnly cookie |\n\nNever store access tokens in `localStorage` — XSS can read them. Store in\nmemory (React state, module variable) and refresh via the HttpOnly cookie.\n\n```kotlin\n\u002F\u002F Spring Security 6 + JWT\n@Configuration\n@EnableWebSecurity\nclass SecurityConfig(private val jwtFilter: JwtAuthenticationFilter) {\n\n    @Bean\n    fun filterChain(http: HttpSecurity): SecurityFilterChain = http\n        .csrf { it.disable() }                         \u002F\u002F stateless API — CSRF not needed\n        .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }\n        .authorizeHttpRequests { auth ->\n            auth\n                .requestMatchers(\"\u002Fapi\u002Fauth\u002F**\").permitAll()\n                .requestMatchers(\"\u002Fapi\u002Fadmin\u002F**\").hasRole(\"ADMIN\")\n                .anyRequest().authenticated()\n        }\n        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java)\n        .build()\n}\n```\n\n```python\n# Django — djangorestframework-simplejwt\n# settings.py\nSIMPLE_JWT = {\n    \"ACCESS_TOKEN_LIFETIME\": timedelta(minutes=15),\n    \"REFRESH_TOKEN_LIFETIME\": timedelta(days=7),\n    \"ROTATE_REFRESH_TOKENS\": True,\n    \"BLACKLIST_AFTER_ROTATION\": True,\n    \"ALGORITHM\": \"HS256\",\n    \"AUTH_HEADER_TYPES\": (\"Bearer\",),\n}\n```\n\n```typescript\n\u002F\u002F Next.js — Auth.js (NextAuth v5)\n\u002F\u002F auth.ts\nimport NextAuth from 'next-auth';\nimport Credentials from 'next-auth\u002Fproviders\u002Fcredentials';\n\nexport const { auth, signIn, signOut } = NextAuth({\n  providers: [\n    Credentials({\n      authorize: async (credentials) => {\n        \u002F\u002F validate and return user or null\n      },\n    }),\n  ],\n  session: { strategy: 'jwt', maxAge: 7 * 24 * 60 * 60 },\n});\n```\n\n### Password Hashing\n\nNever store plaintext passwords. Never use MD5 or SHA-1 for password hashing.\n\n| Algorithm | Use | Work factor |\n|-----------|-----|-------------|\n| bcrypt | default | cost=12 |\n| Argon2id | preferred when available | m=65536, t=3 |\n| PBKDF2 | FIPS environments | iterations=600000 |\n\n```kotlin\n\u002F\u002F Spring Security uses bcrypt by default\n@Bean\nfun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder(12)\n```\n\n```python\n# Django uses PBKDF2 by default; switch to Argon2 for stronger hashing\n# pip install django[argon2]\nPASSWORD_HASHERS = [\n    \"django.contrib.auth.hashers.Argon2PasswordHasher\",\n    \"django.contrib.auth.hashers.PBKDF2PasswordHasher\",  # fallback for existing hashes\n]\n```\n\n## Authorization\n\n**Principle of least privilege:** every endpoint must explicitly declare who\ncan access it. Deny by default.\n\n```kotlin\n\u002F\u002F Spring — method-level security\n@PreAuthorize(\"hasRole('ADMIN') or #userId == authentication.name\")\nfun getUser(userId: String): UserResponse { ... }\n```\n\n```python\n# Django REST Framework\nclass UserDetailView(RetrieveUpdateAPIView):\n    permission_classes = [IsAuthenticated, IsOwnerOrAdmin]\n    queryset = User.objects.all()\n\nclass IsOwnerOrAdmin(BasePermission):\n    def has_object_permission(self, request, view, obj):\n        return request.user.is_staff or obj == request.user\n```\n\nAlways check authorization at the **object level**, not just the endpoint\nlevel. An authenticated user should not be able to access another user's data\nby changing an ID in the URL.\n\n## CORS\n\n```kotlin\n\u002F\u002F Spring Boot — explicit allowlist; never use \"*\" in production\n@Bean\nfun corsConfigurationSource(): CorsConfigurationSource {\n    val config = CorsConfiguration().apply {\n        allowedOrigins = listOf(\"https:\u002F\u002Fapp.example.com\")\n        allowedMethods = listOf(\"GET\", \"POST\", \"PUT\", \"DELETE\")\n        allowedHeaders = listOf(\"Authorization\", \"Content-Type\")\n        allowCredentials = true\n        maxAge = 3600L\n    }\n    return UrlBasedCorsConfigurationSource().apply {\n        registerCorsConfiguration(\"\u002Fapi\u002F**\", config)\n    }\n}\n```\n\n```python\n# Django — django-cors-headers\nCORS_ALLOWED_ORIGINS = [\n    \"https:\u002F\u002Fapp.example.com\",\n]\nCORS_ALLOW_CREDENTIALS = True\nCORS_ALLOW_ALL_ORIGINS = False  # never True in production\n```\n\n## Security Headers\n\nSet these headers on every response. They defend against XSS, clickjacking,\nand MIME sniffing.\n\n```text\nStrict-Transport-Security: max-age=63072000; includeSubDomains; preload\nContent-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'\nX-Frame-Options: DENY\nX-Content-Type-Options: nosniff\nReferrer-Policy: strict-origin-when-cross-origin\nPermissions-Policy: camera=(), microphone=(), geolocation=()\n```\n\n```typescript\n\u002F\u002F Next.js — next.config.ts\nconst securityHeaders = [\n  { key: 'X-Frame-Options', value: 'DENY' },\n  { key: 'X-Content-Type-Options', value: 'nosniff' },\n  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n  {\n    key: 'Strict-Transport-Security',\n    value: 'max-age=63072000; includeSubDomains; preload',\n  },\n];\n\nexport default {\n  headers: async () => [\n    { source: '\u002F(.*)', headers: securityHeaders },\n  ],\n};\n```\n\n## Secrets Management\n\n**Never commit secrets to source control.** No exceptions.\n\n```text\n✗ DATABASE_URL = \"postgres:\u002F\u002Fuser:password@prod.example.com\u002Fdb\"  # in source\n✓ DATABASE_URL = env(\"DATABASE_URL\")                              # from environment\n```\n\nRules:\n\n- Load secrets from environment variables or a secrets manager (AWS Secrets\n  Manager, Vault, GCP Secret Manager)\n- Rotate credentials on any suspected exposure\n- Use separate credentials per environment (dev, staging, prod)\n- Add `.env` to `.gitignore` and never commit it\n\n```python\n# Django — django-environ\nimport environ\nenv = environ.Env()\nenviron.Env.read_env()  # reads .env in development only\n\nSECRET_KEY = env(\"SECRET_KEY\")\nDATABASE_URL = env.db(\"DATABASE_URL\")\n```\n\n## Rate Limiting\n\nProtect authentication endpoints. Brute-force without rate limiting can\ncompromise any account with a weak password.\n\n```kotlin\n\u002F\u002F Spring Boot — Bucket4j\n@Component\nclass RateLimitFilter(private val rateLimiter: RateLimiter) : OncePerRequestFilter() {\n\n    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {\n        if (request.requestURI.startsWith(\"\u002Fapi\u002Fauth\u002F\")) {\n            val probe = rateLimiter.tryConsumeAndReturnRemaining(1)\n            if (!probe.isConsumed) {\n                response.status = HttpStatus.TOO_MANY_REQUESTS.value()\n                return\n            }\n        }\n        chain.doFilter(request, response)\n    }\n}\n```\n\nMinimum rate limits for auth endpoints: 5 requests per minute per IP. Stricter\nlimits (e.g., 3 per 15 minutes) are better for login endpoints.\n\n## SQL Injection Prevention\n\nNever concatenate user input into SQL strings. Always use parameterized queries\nor an ORM.\n\n```kotlin\n\u002F\u002F bad — SQL injection risk\nval user = jdbcTemplate.queryForObject(\"SELECT * FROM users WHERE email = '$email'\")\n\n\u002F\u002F good — parameterized\nval user = jdbcTemplate.queryForObject(\n    \"SELECT * FROM users WHERE email = ?\",\n    UserRowMapper(),\n    email\n)\n```\n\n```python\n# bad\nUser.objects.raw(f\"SELECT * FROM users WHERE email = '{email}'\")\n\n# good — Django ORM handles parameterization automatically\nUser.objects.filter(email=email)\n```\n\n## Logging Security Events\n\nLog authentication events without including sensitive data.\n\n```text\nLog:   login success\u002Ffailure, permission denied, password reset, token refresh\nNever: passwords, tokens, PII (full email in prod logs), credit card numbers\n```\n\n```kotlin\n\u002F\u002F Log security events at INFO level; failures at WARN\nlog.info(\"Authentication successful for user={}\", userId)\nlog.warn(\"Authentication failed: invalid credentials for user={}\", maskedEmail)\nlog.warn(\"Authorization denied: user={} attempted access to resource={}\", userId, resourceId)\n```\n","# Security\n\n## OWASP Top 10 (2021) — Quick Reference\n\n| # | Category | What to check |\n|---|----------|--------------|\n| A01 | Broken Access Control | Is authorization enforced on every endpoint? |\n| A02 | Cryptographic Failures | Are secrets encrypted at rest and in transit? |\n| A03 | Injection | Is all input sanitized or parameterized? |\n| A04 | Insecure Design | Is the threat model documented? |\n| A05 | Security Misconfiguration | Are defaults changed? Debug off in prod? |\n| A06 | Vulnerable Components | Are dependencies up to date? |\n| A07 | Auth & Session Failures | Are sessions invalidated on logout? Token expiry set? |\n| A08 | Software Integrity Failures | Are supply-chain dependencies verified? |\n| A09 | Logging & Monitoring Failures | Are security events logged without leaking PII? |\n| A10 | SSRF | Are outbound URLs allowlisted? |\n\n## Input Validation\n\nValidate all input at system boundaries. Never trust data from: HTTP request\nbody, query parameters, headers, cookies, or environment variables passed by\nexternal systems.\n\n**Rules:**\n\n- Validate type, format, length, and range\n- Reject unknown fields — do not silently ignore them\n- Use allowlists (accept known-good), not blocklists (reject known-bad)\n- Validate at the API layer before it reaches the service layer\n\n```kotlin\n\u002F\u002F Spring Boot — Bean Validation\ndata class CreateUserRequest(\n    @field:NotBlank @field:Email val email: String,\n    @field:Size(min = 8, max = 72) val password: String,\n    @field:Size(max = 100) val name: String\n)\n\n@PostMapping\nfun createUser(@Valid @RequestBody request: CreateUserRequest): ResponseEntity\u003CUserResponse> {\n    \u002F\u002F request is guaranteed valid here\n}\n```\n\n```python\n# Django REST Framework — serializer validation\nclass CreateUserSerializer(serializers.Serializer):\n    email = serializers.EmailField(max_length=254)\n    password = serializers.CharField(min_length=8, max_length=72, write_only=True)\n    name = serializers.CharField(max_length=100)\n\n    def validate_email(self, value):\n        if User.objects.filter(email=value).exists():\n            raise serializers.ValidationError(\"Email already registered.\")\n        return value.lower()\n```\n\n```typescript\n\u002F\u002F Next.js \u002F Astro — Zod\nimport { z } from 'zod';\n\nconst CreateUserSchema = z.object({\n  email: z.string().email().max(254),\n  password: z.string().min(8).max(72),\n  name: z.string().max(100),\n});\n\nconst result = CreateUserSchema.safeParse(body);\nif (!result.success) {\n  return Response.json({ error: result.error.flatten() }, { status: 400 });\n}\n```\n\n## Authentication\n\n### JWT (Stateless)\n\nUse JWTs for stateless API authentication. Keep access tokens short-lived.\n\n**Token lifetimes:**\n\n| Token | Lifetime | Storage |\n|-------|----------|---------|\n| Access token | 15 minutes | Memory (never localStorage) |\n| Refresh token | 7–30 days | HttpOnly cookie |\n\nNever store access tokens in `localStorage` — XSS can read them. Store in\nmemory (React state, module variable) and refresh via the HttpOnly cookie.\n\n```kotlin\n\u002F\u002F Spring Security 6 + JWT\n@Configuration\n@EnableWebSecurity\nclass SecurityConfig(private val jwtFilter: JwtAuthenticationFilter) {\n\n    @Bean\n    fun filterChain(http: HttpSecurity): SecurityFilterChain = http\n        .csrf { it.disable() }                         \u002F\u002F stateless API — CSRF not needed\n        .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }\n        .authorizeHttpRequests { auth ->\n            auth\n                .requestMatchers(\"\u002Fapi\u002Fauth\u002F**\").permitAll()\n                .requestMatchers(\"\u002Fapi\u002Fadmin\u002F**\").hasRole(\"ADMIN\")\n                .anyRequest().authenticated()\n        }\n        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java)\n        .build()\n}\n```\n\n```python\n# Django — djangorestframework-simplejwt\n# settings.py\nSIMPLE_JWT = {\n    \"ACCESS_TOKEN_LIFETIME\": timedelta(minutes=15),\n    \"REFRESH_TOKEN_LIFETIME\": timedelta(days=7),\n    \"ROTATE_REFRESH_TOKENS\": True,\n    \"BLACKLIST_AFTER_ROTATION\": True,\n    \"ALGORITHM\": \"HS256\",\n    \"AUTH_HEADER_TYPES\": (\"Bearer\",),\n}\n```\n\n```typescript\n\u002F\u002F Next.js — Auth.js (NextAuth v5)\n\u002F\u002F auth.ts\nimport NextAuth from 'next-auth';\nimport Credentials from 'next-auth\u002Fproviders\u002Fcredentials';\n\nexport const { auth, signIn, signOut } = NextAuth({\n  providers: [\n    Credentials({\n      authorize: async (credentials) => {\n        \u002F\u002F validate and return user or null\n      },\n    }),\n  ],\n  session: { strategy: 'jwt', maxAge: 7 * 24 * 60 * 60 },\n});\n```\n\n### Password Hashing\n\nNever store plaintext passwords. Never use MD5 or SHA-1 for password hashing.\n\n| Algorithm | Use | Work factor |\n|-----------|-----|-------------|\n| bcrypt | default | cost=12 |\n| Argon2id | preferred when available | m=65536, t=3 |\n| PBKDF2 | FIPS environments | iterations=600000 |\n\n```kotlin\n\u002F\u002F Spring Security uses bcrypt by default\n@Bean\nfun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder(12)\n```\n\n```python\n# Django uses PBKDF2 by default; switch to Argon2 for stronger hashing\n# pip install django[argon2]\nPASSWORD_HASHERS = [\n    \"django.contrib.auth.hashers.Argon2PasswordHasher\",\n    \"django.contrib.auth.hashers.PBKDF2PasswordHasher\",  # fallback for existing hashes\n]\n```\n\n## Authorization\n\n**Principle of least privilege:** every endpoint must explicitly declare who\ncan access it. Deny by default.\n\n```kotlin\n\u002F\u002F Spring — method-level security\n@PreAuthorize(\"hasRole('ADMIN') or #userId == authentication.name\")\nfun getUser(userId: String): UserResponse { ... }\n```\n\n```python\n# Django REST Framework\nclass UserDetailView(RetrieveUpdateAPIView):\n    permission_classes = [IsAuthenticated, IsOwnerOrAdmin]\n    queryset = User.objects.all()\n\nclass IsOwnerOrAdmin(BasePermission):\n    def has_object_permission(self, request, view, obj):\n        return request.user.is_staff or obj == request.user\n```\n\nAlways check authorization at the **object level**, not just the endpoint\nlevel. An authenticated user should not be able to access another user's data\nby changing an ID in the URL.\n\n## CORS\n\n```kotlin\n\u002F\u002F Spring Boot — explicit allowlist; never use \"*\" in production\n@Bean\nfun corsConfigurationSource(): CorsConfigurationSource {\n    val config = CorsConfiguration().apply {\n        allowedOrigins = listOf(\"https:\u002F\u002Fapp.example.com\")\n        allowedMethods = listOf(\"GET\", \"POST\", \"PUT\", \"DELETE\")\n        allowedHeaders = listOf(\"Authorization\", \"Content-Type\")\n        allowCredentials = true\n        maxAge = 3600L\n    }\n    return UrlBasedCorsConfigurationSource().apply {\n        registerCorsConfiguration(\"\u002Fapi\u002F**\", config)\n    }\n}\n```\n\n```python\n# Django — django-cors-headers\nCORS_ALLOWED_ORIGINS = [\n    \"https:\u002F\u002Fapp.example.com\",\n]\nCORS_ALLOW_CREDENTIALS = True\nCORS_ALLOW_ALL_ORIGINS = False  # never True in production\n```\n\n## Security Headers\n\nSet these headers on every response. They defend against XSS, clickjacking,\nand MIME sniffing.\n\n```text\nStrict-Transport-Security: max-age=63072000; includeSubDomains; preload\nContent-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'\nX-Frame-Options: DENY\nX-Content-Type-Options: nosniff\nReferrer-Policy: strict-origin-when-cross-origin\nPermissions-Policy: camera=(), microphone=(), geolocation=()\n```\n\n```typescript\n\u002F\u002F Next.js — next.config.ts\nconst securityHeaders = [\n  { key: 'X-Frame-Options', value: 'DENY' },\n  { key: 'X-Content-Type-Options', value: 'nosniff' },\n  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n  {\n    key: 'Strict-Transport-Security',\n    value: 'max-age=63072000; includeSubDomains; preload',\n  },\n];\n\nexport default {\n  headers: async () => [\n    { source: '\u002F(.*)', headers: securityHeaders },\n  ],\n};\n```\n\n## Secrets Management\n\n**Never commit secrets to source control.** No exceptions.\n\n```text\n✗ DATABASE_URL = \"postgres:\u002F\u002Fuser:password@prod.example.com\u002Fdb\"  # in source\n✓ DATABASE_URL = env(\"DATABASE_URL\")                              # from environment\n```\n\nRules:\n\n- Load secrets from environment variables or a secrets manager (AWS Secrets\n  Manager, Vault, GCP Secret Manager)\n- Rotate credentials on any suspected exposure\n- Use separate credentials per environment (dev, staging, prod)\n- Add `.env` to `.gitignore` and never commit it\n\n```python\n# Django — django-environ\nimport environ\nenv = environ.Env()\nenviron.Env.read_env()  # reads .env in development only\n\nSECRET_KEY = env(\"SECRET_KEY\")\nDATABASE_URL = env.db(\"DATABASE_URL\")\n```\n\n## Rate Limiting\n\nProtect authentication endpoints. Brute-force without rate limiting can\ncompromise any account with a weak password.\n\n```kotlin\n\u002F\u002F Spring Boot — Bucket4j\n@Component\nclass RateLimitFilter(private val rateLimiter: RateLimiter) : OncePerRequestFilter() {\n\n    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {\n        if (request.requestURI.startsWith(\"\u002Fapi\u002Fauth\u002F\")) {\n            val probe = rateLimiter.tryConsumeAndReturnRemaining(1)\n            if (!probe.isConsumed) {\n                response.status = HttpStatus.TOO_MANY_REQUESTS.value()\n                return\n            }\n        }\n        chain.doFilter(request, response)\n    }\n}\n```\n\nMinimum rate limits for auth endpoints: 5 requests per minute per IP. Stricter\nlimits (e.g., 3 per 15 minutes) are better for login endpoints.\n\n## SQL Injection Prevention\n\nNever concatenate user input into SQL strings. Always use parameterized queries\nor an ORM.\n\n```kotlin\n\u002F\u002F bad — SQL injection risk\nval user = jdbcTemplate.queryForObject(\"SELECT * FROM users WHERE email = '$email'\")\n\n\u002F\u002F good — parameterized\nval user = jdbcTemplate.queryForObject(\n    \"SELECT * FROM users WHERE email = ?\",\n    UserRowMapper(),\n    email\n)\n```\n\n```python\n# bad\nUser.objects.raw(f\"SELECT * FROM users WHERE email = '{email}'\")\n\n# good — Django ORM handles parameterization automatically\nUser.objects.filter(email=email)\n```\n\n## Logging Security Events\n\nLog authentication events without including sensitive data.\n\n```text\nLog:   login success\u002Ffailure, permission denied, password reset, token refresh\nNever: passwords, tokens, PII (full email in prod logs), credit card numbers\n```\n\n```kotlin\n\u002F\u002F Log security events at INFO level; failures at WARN\nlog.info(\"Authentication successful for user={}\", userId)\nlog.warn(\"Authentication failed: invalid credentials for user={}\", maskedEmail)\nlog.warn(\"Authorization denied: user={} attempted access to resource={}\", userId, resourceId)\n```\n",{"id":24,"name":5,"risk":25,"inputs":28,"license":15,"outputs":29,"quality":30,"version":33,"metadata":34,"description":35,"compatibility":36,"user-invokable":27},"security",{"level":26,"can_modify_files":27,"requires_network":20,"can_execute_shell":20},"high",true,[],[],{"version":31,"reviewed_by":32},"0.1.0","codeconductor-core","1.0.0",{"author":9,"category":24},"Provides expert knowledge for implementing and reviewing application security following OWASP Top 10 (2021), with stack-specific patterns for Spring Boot, Django, Next.js, and Astro.\n",{"tools":37,"stacks":43},[38,39,40,41,42],"claude","codex","gemini","agy","opencode",{"languages":44,"frameworks":45},[],[],"main","ef000917622cb4063da0d721445f2caf2cfb21a4",".cursor\u002Fskills\u002Fsecurity","https:\u002F\u002Fgithub.com\u002Flgzarturo\u002Ffeedback_widget","https:\u002F\u002Fgithub.com\u002Flgzarturo","https:\u002F\u002Fwww.lgzarturo.com","Building products, not just software.\r\n\r\nTechnical Leadership • Software Architecture • AI","2026-10-24 07:43:49",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":27},[56,59,62],{"tool":57,"command":58},"npx","npx skills add lgzarturo\u002Ffeedback_widget --skill Security",{"tool":60,"command":61},"bunx","bunx skills add lgzarturo\u002Ffeedback_widget --skill Security",{"tool":63,"command":64},"pnpm","pnpm dlx skills add lgzarturo\u002Ffeedback_widget --skill Security","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Flgzarturo\u002Ffeedback_widget\u002Fblob\u002Fmain\u002F.cursor\u002Fskills\u002Fsecurity\u002FSKILL.md",[],[68],{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":72,"categories":73,"keywords":74,"publishedAt":19},"01M7ACKPRPKNASERXZ82H18VCG","Test-Driven Development","Provides expert knowledge for applying the Red-Green-Refactor cycle, designing tests at the right pyramid layer, and implementing TDD across Spring Boot + Kotlin, Python (pytest), and Next.js \u002F Astro (Vitest + Playwright) stacks.","https:\u002F\u002Fgithub.com\u002Flgzarturo\u002Ffeedback_widget\u002Fblob\u002Fmain\u002F.cursor\u002Fskills\u002Ftesting-tdd\u002FSKILL.md",[],[]]