Communitygithub.com

django-security

Django 보안 모범 사례: 인증, 권한 부여, CSRF 보호, SQL 인젝션 방지, XSS 방지 및 안전한 배포 설정. Django 인증, 권한 부여, 입력 처리 또는 배포 설정을 검토할 때 사용하세요.

django-security란 무엇인가요?

이 Django 보안 스킬은 Django 애플리케이션에서 일반적인 취약점으로부터 보호하기 위한 포괄적인 보안 가이드라인을 제공합니다. 주로 Django 인증 및 권한 부여 설정, 사용자 권한 및 역할 구현, 프로덕션 보안 설정 구성, Django 애플리케이션 보안 검토, 그리고 프로덕션 환경으로의 배포 시 활성화됩니다. 핵심 보안 설정으로는 프로덕션 설정에서 `DEBUG = False` 및 `ALLOWED_HOSTS` 설정, SSL 리디렉션, 보안 쿠키 및 HSTS 헤더를 포함합니다. 또한 SQL 인젝션 방지를 위해 Django ORM의 자동 이스케이프 기능과 raw SQL 사용 시 파라미터 바인딩을 강조하며, XSS 방지를 위해 템플릿 이스케이프와 안전한 문자열 처리를 다룹니다. CSRF 보호는 기본적으로 활성화되어 있으며, 파일 업로드 보안을 위한 MIME 타입 및 확장자 검증, API 보안을 위한 속도 제한과 JWT 인증 설정을 포함합니다. 이 스킬은 Django 프로젝트의 전반적인 보안 수준을 향상시키는 데 유용합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/django-security

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Django 보안 모범 사례

일반적인 취약점으로부터 Django 애플리케이션을 보호하기 위한 포괄적인 보안 가이드라인입니다.

활성화 시점

  • Django 인증 및 권한 부여 설정
  • 사용자 권한 및 역할 구현
  • 프로덕션 보안 설정 구성
  • Django 애플리케이션 보안 문제 검토
  • Django 애플리케이션을 프로덕션 환경에 배포

핵심 보안 설정

프로덕션 설정 구성

# settings/production.py
import os

DEBUG = False  # 중요: 프로덕션에서는 절대 True로 설정하지 마세요

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# 보안 헤더
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000  # 1년
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'

# HTTPS 및 쿠키
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'

# 비밀 키 (환경 변수로 설정해야 함)
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
    raise ImproperlyConfigured('DJANGO_SECRET_KEY 환경 변수가 필요합니다')

# 비밀번호 검증
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 12,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

인증

사용자 정의 모델

# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    """향상된 보안을 위한 사용자 정의 모델입니다."""

    email = models.EmailField(unique=True)
    phone = models.CharField(max_length=20, blank=True)

    USERNAME_FIELD = 'email'  # 이메일을 사용자 이름으로 사용
    REQUIRED_FIELDS = ['username']

    class Meta:
        db_table = 'users'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'

    def __str__(self):
        return self.email

# settings/base.py
AUTH_USER_MODEL = 'users.User'

비밀번호 해싱

# Django는 기본적으로 PBKDF2를 사용합니다. 더 강력한 보안을 위해:
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

세션 관리

# 세션 구성
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 또는 'db'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 3600 * 24 * 7  # 1주일
SESSION_SAVE_EVERY_REQUEST = False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 더 나은 UX, 하지만 덜 안전함

권한 부여

권한

# models.py
from django.db import models
from django.contrib.auth.models import Permission

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        permissions = [
            ('can_publish', '게시물 발행 가능'),
            ('can_edit_others', '다른 사람의 게시물 편집 가능'),
        ]

    def user_can_edit(self, user):
        """사용자가 이 게시물을 편집할 수 있는지 확인합니다."""
        return self.author == user or user.has_perm('app.can_edit_others')

# views.py
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView

class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
    model = Post
    permission_required = 'app.can_edit_others'
    raise_exception = True  # 리디렉션 대신 403 반환

    def get_queryset(self):
        """사용자가 자신의 게시물만 편집할 수 있도록 합니다."""
        return Post.objects.filter(author=self.request.user)

사용자 정의 권한

# permissions.py
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    """소유자만 객체를 편집할 수 있도록 허용합니다."""

    def has_object_permission(self, request, view, obj):
        # 읽기 권한은 모든 요청에 허용
        if request.method in permissions.SAFE_METHODS:
            return True

        # 쓰기 권한은 소유자만
        return obj.author == request.user

class IsAdminOrReadOnly(permissions.BasePermission):
    """관리자는 모든 작업 가능, 그 외는 읽기 전용입니다."""

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user and request.user.is_staff

class IsVerifiedUser(permissions.BasePermission):
    """인증된 사용자만 허용합니다."""

    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated and request.user.is_verified

역할 기반 접근 제어 (RBAC)

# models.py
from django.contrib.auth.models import AbstractUser, Group

class User(AbstractUser):
    ROLE_CHOICES = [
        ('admin', '관리자'),
        ('moderator', '중재자'),
        ('user', '일반 사용자'),
    ]
    role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')

    def is_admin(self):
        return self.role == 'admin' or self.is_superuser

    def is_moderator(self):
        return self.role in ['admin', 'moderator']

# Mixins
class AdminRequiredMixin:
    """관리자 역할을 요구하는 Mixin입니다."""

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.is_admin():
            from django.core.exceptions import PermissionDenied
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

SQL 인젝션 방지

Django ORM 보호

# 좋은 예: Django ORM은 매개변수를 자동으로 이스케이프합니다
def get_user(username):
    return User.objects.get(username=username)  # 안전

# 좋은 예: raw()와 매개변수 사용
def search_users(query):
    return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])

# 나쁜 예: 사용자 입력을 직접 보간하지 마세요
def get_user_bad(username):
    return User.objects.raw(f'SELECT * FROM users WHERE username = {username}')  # 취약!

# 좋은 예: 적절한 이스케이프를 사용한 filter
def get_users_by_email(email):
    return User.objects.filter(email__iexact=email)  # 안전

# 좋은 예: 복잡한 쿼리에 Q 객체 사용
from django.db.models import Q
def search_users_complex(query):
    return User.objects.filter(
        Q(username__icontains=query) |
        Q(email__icontains=query)
    )  # 안전

raw() 사용 시 추가 보안

# raw SQL을 사용해야 한다면 항상 매개변수를 사용하세요
User.objects.raw(
    'SELECT * FROM users WHERE email = %s AND status = %s',
    [user_input_email, status]
)

XSS 방지

템플릿 이스케이프

{# Django는 기본적으로 변수를 자동 이스케이프합니다 - 안전 #}
{{ user_input }}  {# 이스케이프된 HTML #}

{# 신뢰할 수 있는 콘텐츠에만 명시적으로 safe 표시 #}
{{ trusted_html|safe }}  {# 이스케이프되지 않음 #}

{# 안전한 HTML을 위한 템플릿 필터 사용 #}
{{ user_input|escape }}  {# 기본값과 동일 #}
{{ user_input|striptags }}  {# 모든 HTML 태그 제거 #}

{# JavaScript 이스케이프 #}
<script>
    var username = {{ username|escapejs }};
</script>

안전한 문자열 처리

from django.utils.safestring import mark_safe
from django.utils.html import escape

# 나쁜 예: 이스케이프 없이 사용자 입력을 safe로 표시하지 마세요
def render_bad(user_input):
    return mark_safe(user_input)  # 취약!

# 좋은 예: 먼저 이스케이프한 후 safe로 표시
def render_good(user_input):
    return mark_safe(escape(user_input))

# 좋은 예: 변수가 있는 HTML에 format_html 사용
from django.utils.html import format_html

def greet_user(username):
    return format_html('<span class="user">{}</span>', escape(username))

HTTP 헤더

# settings.py
SECURE_CONTENT_TYPE_NOSNIFF = True  # MIME 스니핑 방지
SECURE_BROWSER_XSS_FILTER = True  # XSS 필터 활성화
X_FRAME_OPTIONS = 'DENY'  # 클릭재킹 방지

# 사용자 정의 미들웨어
from django.conf import settings

class SecurityHeaderMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response['X-Content-Type-Options'] = 'nosniff'
        response['X-Frame-Options'] = 'DENY'
        response['X-XSS-Protection'] = '1; mode=block'
        response['Content-Security-Policy'] = "default-src 'self'"
        return response

CSRF 보호

기본 CSRF 보호

# settings.py - CSRF는 기본적으로 활성화되어 있습니다
CSRF_COOKIE_SECURE = True  # HTTPS를 통해서만 전송
CSRF_COOKIE_HTTPONLY = True  # JavaScript 접근 방지
CSRF_COOKIE_SAMESITE = 'Lax'  # 일부 경우 CSRF 방지
CSRF_TRUSTED_ORIGINS = ['https://example.com']  # 신뢰할 수 있는 도메인

# 템플릿 사용법
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">제출</button>
</form>

# AJAX 요청
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

fetch('/api/endpoint/', {
    method: 'POST',
    headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(data)
});

뷰 면제 (주의해서 사용)

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # 반드시 필요한 경우에만 사용!
def webhook_view(request):
    # 외부 서비스의 웹훅
    pass

파일 업로드 보안

파일 검증

import os
import magic  # pip install python-magic
from django.core.exceptions import ValidationError

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """매직 바이트를 사용하여 파일 유형을 검증하고 확장자를 교차 확인합니다."""
    mime = magic.from_buffer(value.read(2048), mime=True)
    value.seek(0)

    if mime not in ALLOWED_MIMES:
        raise ValidationError('지원되지 않는 파일 유형입니다.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(mime, set()):
        raise ValidationError('파일 확장자가 파일 내용과 일치하지 않습니다.')

def validate_file_size(value):
    """파일 크기 검증 (최대 5MB)."""
    if value.size > 5 * 1024 * 1024:
        raise ValidationError('파일이 너무 큽니다. 최대 크기는 5MB입니다.')

# models.py
class Document(models.Model):
    file = models.FileField(
        upload_to='documents/',
        validators=[validate_file_type, validate_file_size]
    )

libmagic 설치가 어려운 환경(예: 최소 컨테이너)의 경우, 대안으로 순수 Python filetype 패키지를 사용하세요:

import os
from django.core.exceptions import ValidationError

import filetype  # pip install filetype

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """매직 바이트를 사용하여 파일 유형을 검증합니다."""
    kind = filetype.guess(value.read(2048))
    value.seek(0)

    if kind is None or kind.mime not in ALLOWED_MIMES:
        raise ValidationError('지원되지 않는 파일 유형입니다.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(kind.mime, set()):
        raise ValidationError('파일 확장자가 파일 내용과 일치하지 않습니다.')

안전한 파일 저장소

# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'

# 프로덕션에서는 미디어용 별도 도메인 사용
MEDIA_DOMAIN = 'https://media.example.com'

# 사용자 업로드를 직접 제공하지 마세요
# 정적 파일에는 whitenoise 또는 CDN 사용
# 미디어 파일에는 별도 서버 또는 S3 사용

API 보안

속도 제한

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day',
        'upload': '10/hour',
    }
}

# 사용자 정의 스로틀
from rest_framework.throttling import UserRateThrottle

class BurstRateThrottle(UserRateThrottle):
    scope = 'burst'
    rate = '60/min'

class SustainedRateThrottle(UserRateThrottle):
    scope = 'sustained'
    rate = '1000/day'

API 인증

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated

@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def secure_api_view(request):
    pass

안전한 배포

환경 변수

# .env.production
DJANGO_SECRET_KEY=your-very-long-secret-key-here
ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgres://user:password@localhost:5432/dbname
REDIS_URL=redis://localhost:6379/0
[email protected]
EMAIL_HOST_PASSWORD=your-email-password

Nginx 구성 예시

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # 보안 헤더
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header Content-Security-Policy "default-src 'self'" always;

    # 프록시 설정
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 정적 파일
    location /static/ {
        alias /var/www/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 미디어 파일
    location /media/ {
        alias /var/www/media/;
        expires 30d;
        add_header Cache-Control "public, immutable";
        # 사용자 업로드에는 X-Content-Type-Options 적용
        add_header X-Content-Type-Options "nosniff" always;
    }
}

Dockerfile (프로덕션)

FROM python:3.11-slim

WORKDIR /app

# 시스템 종속성 설치
RUN apt-get update && apt-get install -y \
    gcc \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Python 종속성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 애플리케이션 코드 복사
COPY . .

# 비루트 사용자로 실행
RUN addgroup --system app && adduser --system --ingroup app app
USER app

EXPOSE 8000

CMD ["gunicorn", "config.wsgi", "--bind", "0.0.0.0:8000", "--workers", "4"]

보안 체크리스트

프로덕션 전 확인 사항

  • DEBUG = False로 설정되어 있습니까?
  • ALLOWED_HOSTS가 올바르게 구성되었습니까?
  • SECRET_KEY가 안전하고 환경 변수로 설정되었습니까?
  • HTTPS가 활성화되었습니까 (SSL 리디렉션)?
  • 보안 쿠키가 설정되었습니까 (SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE)?
  • HSTS가 활성화되었습니까?
  • 비밀번호 검증기가 구성되었습니까?
  • SQL 인젝션에 대한 ORM 사용이 검토되었습니까?
  • XSS에 대한 템플릿 이스케이프가 구성되었습니까?
  • CSRF 보호가 활성화되었습니까?
  • 파일 업로드 검증이 설정되었습니까?
  • API 속도 제한이 구성되었습니까?
  • 미디어 파일이 안전하게 제공되고 있습니까?
  • 데이터베이스 연결이 안전합니까?
  • 모든 의존성이 최신입니까?
  • 불필요한 Django 앱이 제거되었습니까?
  • 로깅 및 모니터링이 설정되었습니까?

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬