Communitygithub.com

perl-security

Perl 애플리케이션을 위한 포괄적인 보안 패턴 가이드입니다. 오염 모드, 입력 검증, 안전한 프로세스 실행, DBI 매개변수화된 쿼리, 웹 보안(XSS/SQLi/CSRF) 및 perlcritic 보안 정책을 다룹니다. Perl 입력 처리, 프로세스 실행, DBI 쿼리 또는 웹 노출 코드를 검토할 때 활성화하세요.

perl-security란 무엇인가요?

이 스킬은 Perl 애플리케이션 개발 및 보안 검토를 위한 포괄적인 보안 패턴 가이드입니다. 주된 목표는 오염 모드(taint mode), 입력 검증, 안전한 파일 및 프로세스 처리, SQL 인젝션 방지, XSS/CSRF와 같은 웹 취약점 방어, 그리고 perlcritic을 활용한 정적 분석까지 모든 보안 계층을 다루는 것입니다. CGI, Mojolicious, Dancer2, Catalyst 등 다양한 Perl 웹 프레임워크를 지원하며, DBI와 DBIx::Class를 사용한 안전한 데이터베이스 쿼리 패턴도 포함합니다. SKILL.md 문서는 실제 코드 예제를 통해 안전한 코딩 방법과 일반적인 안티패턴을 명확히 대비하여 제시하므로, 개발자가 보안 취약점을 쉽게 이해하고 수정할 수 있도록 돕습니다.

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

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

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

문서

Perl 보안 패턴

Perl 애플리케이션을 위한 포괄적인 보안 지침입니다. 입력 검증, 인젝션 방지 및 안전한 코딩 관행을 다룹니다.

활성화 시점

  • Perl 애플리케이션에서 사용자 입력을 처리할 때
  • Perl 웹 애플리케이션(CGI, Mojolicious, Dancer2, Catalyst)을 구축할 때
  • 보안 취약점을 찾기 위해 Perl 코드를 검토할 때
  • 사용자가 제공한 경로로 파일 작업을 수행할 때
  • Perl에서 시스템 명령을 실행할 때
  • DBI 데이터베이스 쿼리를 작성할 때

작동 방식

오염 인식 입력 경계로 시작한 다음 바깥쪽으로 확장합니다: 입력을 검증하고 오염을 제거하며, 파일시스템과 프로세스 실행을 제한된 상태로 유지하고, 모든 곳에서 매개변수화된 DBI 쿼리를 사용합니다. 아래 예제는 이 스킬이 사용자 입력, 셸 또는 네트워크에 닿는 Perl 코드를 배포하기 전에 적용해야 하는 안전한 기본값을 보여줍니다.

오염 모드 (Taint Mode)

Perl의 오염 모드(-T)는 외부 소스의 데이터를 추적하고 명시적인 검증 없이 안전하지 않은 작업에 사용되는 것을 방지합니다.

오염 모드 활성화

#!/usr/bin/perl -T
use v5.36;

# 오염됨: 프로그램 외부에서 온 모든 것
my $input    = $ARGV[0];        # 오염됨
my $env_path = $ENV{PATH};      # 오염됨
my $form     = <STDIN>;         # 오염됨
my $query    = $ENV{QUERY_STRING}; # 오염됨

# PATH를 일찍 정리 (오염 모드에서 필수)
$ENV{PATH} = '/usr/local/bin:/usr/bin:/bin';
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};

오염 제거 패턴

use v5.36;

# 좋음: 특정 정규식으로 검증 및 오염 제거
sub untaint_username($input) {
    if ($input =~ /^([a-zA-Z0-9_]{3,30})$/) {
        return $1;  # $1은 오염이 제거됨
    }
    die "올바르지 않은 사용자 이름: 3-30자의 영숫자여야 합니다\n";
}

# 좋음: 파일 경로 검증 및 오염 제거
sub untaint_filename($input) {
    if ($input =~ m{^([a-zA-Z0-9._-]+)$}) {
        return $1;
    }
    die "올바르지 않은 파일 이름: 안전하지 않은 문자가 포함됨\n";
}

# 나쁨: 지나치게 관대한 오염 제거 (목적을 무효화)
sub bad_untaint($input) {
    $input =~ /^(.*)$/s;
    return $1;  # 무엇이든 허용 — 무의미함
}

입력 검증

차단 목록보다 허용 목록

use v5.36;

# 좋음: 허용 목록 — 정확히 무엇이 허용되는지 정의
sub validate_sort_field($field) {
    my %allowed = map { $_ => 1 } qw(name email created_at updated_at);
    die "올바르지 않은 정렬 필드: $field\n" unless $allowed{$field};
    return $field;
}

# 좋음: 특정 패턴으로 검증
sub validate_email($email) {
    if ($email =~ /^([a-zA-Z0-9._%+-]+\@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/) {
        return $1;
    }
    die "올바르지 않은 이메일 주소\n";
}

sub validate_integer($input) {
    if ($input =~ /^(-?\d{1,10})$/) {
        return $1 + 0;  # 숫자로 강제 변환
    }
    die "올바르지 않은 정수\n";
}

# 나쁨: 차단 목록 — 항상 불완전함
sub bad_validate($input) {
    die "올바르지 않음" if $input =~ /[<>"';&|]/;  # 인코딩된 공격을 놓침
    return $input;
}

길이 제약

use v5.36;

sub validate_comment($text) {
    die "댓글은 필수입니다\n"        unless length($text) > 0;
    die "댓글이 10000자를 초과했습니다\n" if length($text) > 10_000;
    return $text;
}

안전한 정규 표현식

ReDoS 방지

중첩된 수량자가 중복되는 패턴에서 재앙적인 역추적이 발생합니다.

use v5.36;

# 나쁨: ReDoS에 취약함 (지수적 역추적)
my $bad_re = qr/^(a+)++$/;           # 중첩된 수량자
my $bad_re2 = qr/^([a-zA-Z]+)*$/;     # 클래스의 중첩된 수량자
my $bad_re3 = qr/^(.*?,){10,}$/;     # 반복된 탐욕/게으름 조합

# 좋음: 중첩 없이 다시 작성
my $good_re = qr/^a+$/;             # 단일 수량자
my $good_re2 = qr/^[a-zA-Z]+$/;     # 클래스의 단일 수량자

# 좋음: 소유적 수량자 또는 원자 그룹을 사용하여 역추적 방지
my $safe_re = qr/^[a-zA-Z]++$/;             # 소유적 (5.10+)
my $safe_re2 = qr/^(?>a+)$/;                # 원자 그룹

# 좋음: 신뢰할 수 없는 패턴에 타임아웃 적용
use POSIX qw(alarm);
sub safe_match($string, $pattern, $timeout = 2) {
    my $matched;
    eval {
        local $SIG{ALRM} = sub { die "정규식 타임아웃\n" };
        alarm($timeout);
        $matched = $string =~ $pattern;
        alarm(0);
    };
    alarm(0);
    die $@ if $@;
    return $matched;
}

안전한 파일 작업

3-인수 열기

use v5.36;

# 좋음: 3-인수 열기, 어휘 파일핸들, 반환값 확인
sub read_file($path) {
    open my $fh, '<:encoding(UTF-8)', $path
        or die "'$path'을(를) 열 수 없습니다: $!\n";
    local $/;
    my $content = <$fh>;
    close $fh;
    return $content;
}

# 나쁨: 사용자 데이터로 2-인수 열기 (명령 주입)
sub bad_read($path) {
    open my $fh, $path;        # $path = "|rm -rf /"이면 명령 실행!
    open my $fh, "< $path";   # 셸 메타문자 주입
}

TOCTOU 방지 및 경로 탐색

use v5.36;
use Fcntl qw(:DEFAULT :flock);
use File::Spec;
use Cwd qw(realpath);

# 원자적 파일 생성
sub create_file_safe($path) {
    sysopen(my $fh, $path, O_WRONLY | O_CREAT | O_EXCL, 0600)
        or die "'$path'을(를) 생성할 수 없습니다: $!\n";
    return $fh;
}

# 경로가 허용된 디렉토리 내에 머무르는지 검증
sub safe_path($base_dir, $user_path) {
    my $real = realpath(File::Spec->catfile($base_dir, $user_path))
        // die "경로가 존재하지 않습니다\n";
    my $base_real = realpath($base_dir)
        // die "기본 디렉토리가 존재하지 않습니다\n";
    die "경로 탐색이 차단되었습니다\n" unless $real =~ /^\Q$base_real\E(?:\/|\z)/;
    return $real;
}

임시 파일에는 File::Temp를 사용하고(tempfile(UNLINK => 1)), 경쟁 조건을 방지하려면 flock(LOCK_EX)를 사용하세요.

안전한 프로세스 실행

목록 형태의 system과 exec

use v5.36;

# 좋음: 목록 형태 — 셸 보간 없음
sub run_command(@cmd) {
    system(@cmd) == 0
        or die "명령 실패: @cmd\n";
}

run_command('grep', '-r', $user_pattern, '/var/log/app/');

# 좋음: IPC::Run3로 출력을 안전하게 캡처
use IPC::Run3;
sub capture_output(@cmd) {
    my ($stdout, $stderr);
    run3(\@cmd, \undef, \$stdout, \$stderr);
    if ($?) {
        die "명령 실패 (종료 코드 $?): $stderr\n";
    }
    return $stdout;
}

# 나쁨: 문자열 형태 — 셸 주입!
sub bad_search($pattern) {
    system("grep -r '$pattern' /var/log/app/");  # $pattern = "'; rm -rf / #"인 경우
}

# 나쁨: 보간이 포함된 역따옴표
my $output = `ls $user_dir`;   # 셸 주입 위험

또한 외부 명령에서 stdout/stderr를 안전하게 캡처하려면 Capture::Tiny를 사용하세요.

SQL 인젝션 방지

DBI 플레이스홀더

use v5.36;
use DBI;

my $dbh = DBI->connect($dsn, $user, $pass, {
    RaiseError => 1,
    PrintError => 0,
    AutoCommit => 1,
});

# 좋음: 매개변수화된 쿼리 — 항상 플레이스홀더 사용
sub find_user($dbh, $email) {
    my $sth = $dbh->prepare('SELECT * FROM users WHERE email = ?');
    $sth->execute($email);
    return $sth->fetchrow_hashref;
}

sub search_users($dbh, $name, $status) {
    my $sth = $dbh->prepare(
        'SELECT * FROM users WHERE name LIKE ? AND status = ? ORDER BY name'
    );
    $sth->execute("%$name%", $status);
    return $sth->fetchall_arrayref({});
}

# 나쁨: SQL에서 문자열 보간 (SQLi 취약점!)
sub bad_find($dbh, $email) {
    my $sth = $dbh->prepare("SELECT * FROM users WHERE email = '$email'");
    # $email = "' OR 1=1 --"이면 모든 사용자 반환
    $sth->execute;
    return $sth->fetchrow_hashref;
}

동적 컬럼 허용 목록

use v5.36;

# 좋음: 허용 목록에 대해 컬럼 이름 검증
sub order_by($dbh, $column, $direction) {
    my %allowed_cols = map { $_ => 1 } qw(name email created_at);
    my %allowed_dirs = map { $_ => 1 } qw(ASC DESC);

    die "올바르지 않은 컬럼: $column\n"    unless $allowed_cols{$column};
    die "올바르지 않은 방향: $direction\n" unless $allowed_dirs{uc $direction};

    my $sth = $dbh->prepare("SELECT * FROM users ORDER BY $column $direction");
    $sth->execute;
    return $sth->fetchall_arrayref({});
}

# 나쁨: 사용자가 선택한 컬럼을 직접 보간
sub bad_order($dbh, $column) {
    $dbh->prepare("SELECT * FROM users ORDER BY $column");  # SQLi!
}

DBIx::Class (ORM 안전성)

use v5.36;

# DBIx::Class는 안전한 매개변수화된 쿼리를 생성합니다
my @users = $schema->resultset('User')->search({
    status => 'active',
    email  => { -like => '%@example.com' },
}, {
    order_by => { -asc => 'name' },
    rows     => 50,
});

웹 보안

XSS 방지

use v5.36;
use HTML::Entities qw(encode_entities);
use URI::Escape qw(uri_escape_utf8);

# 좋음: HTML 컨텍스트에 맞게 출력 인코딩
sub safe_html($user_input) {
    return encode_entities($user_input);
}

# 좋음: URL 컨텍스트에 맞게 인코딩
sub safe_url_param($value) {
    return uri_escape_utf8($value);
}

# 좋음: JSON 컨텍스트에 맞게 인코딩
use JSON::MaybeXS qw(encode_json);
sub safe_json($data) {
    return encode_json($data);  # 이스케이프 처리
}

# 템플릿 자동 이스케이프 (Mojolicious)
# <%= $user_input %>   — 자동 이스케이프됨 (안전)
# <%== $raw_html %>    — 원시 출력 (위험, 신뢰할 수 있는 콘텐츠에만 사용)

# 템플릿 자동 이스케이프 (Template Toolkit)
# [% user_input | html %]  — 명시적 HTML 인코딩

# 나쁨: HTML에서 원시 출력
sub bad_html($input) {
    print "<div>$input</div>";  # $input에 <script>가 포함되면 XSS
}

CSRF 보호

use v5.36;
use Crypt::URandom qw(urandom);
use MIME::Base64 qw(encode_base64url);

sub generate_csrf_token() {
    return encode_base64url(urandom(32));
}

토큰을 확인할 때는 상수 시간 비교를 사용하세요. 대부분의 웹 프레임워크(Mojolicious, Dancer2, Catalyst)는 내장 CSRF 보호 기능을 제공하므로 직접 구현하는 것보다 선호하세요.

세션 및 헤더 보안

use v5.36;

# Mojolicious 세션 + 헤더
$app->secrets(['길고-임의적인-비밀번호-정기적으로-교체']);
$app->sessions->secure(1);          # HTTPS 전용
$app->sessions->samesite('Lax');

$app->hook(after_dispatch => sub ($c) {
    $c->res->headers->header('X-Content-Type-Options' => 'nosniff');
    $c->res->headers->header('X-Frame-Options'        => 'DENY');
    $c->res->headers->header('Content-Security-Policy' => "default-src 'self'");
    $c->res->headers->header('Strict-Transport-Security' => 'max-age=31536000; includeSubDomains');
});

출력 인코딩

항상 컨텍스트에 맞게 출력을 인코딩하세요: HTML에는 HTML::Entities::encode_entities(), URL에는 URI::Escape::uri_escape_utf8(), JSON에는 JSON::MaybeXS::encode_json().

CPAN 모듈 보안

  • 버전 고정: cpanfile에서 requires 'DBI', '== 1.643';
  • 유지 관리되는 모듈 선호: MetaCPAN에서 최근 릴리스 확인
  • 의존성 최소화: 각 의존성은 공격 표면입니다

보안 도구

perlcritic 보안 정책

# .perlcriticrc — 보안 중심 구성
severity = 3
theme = security + core

# 3-인수 열기 요구
[InputOutput::RequireThreeArgOpen]
severity = 5

# 확인된 시스템 호출 요구
[InputOutput::RequireCheckedSyscalls]
functions = :builtins
severity = 4

# 문자열 eval 금지
[BuiltinFunctions::ProhibitStringyEval]
severity = 5

# 역따옴표 연산자 금지
[InputOutput::ProhibitBacktickOperators]
severity = 4

# CGI에서 오염 검사 요구
[Modules::RequireTaintChecking]
severity = 5

# 2-인수 열기 금지
[InputOutput::ProhibitTwoArgOpen]
severity = 5

# 베어워드 파일핸들 금지
[InputOutput::ProhibitBarewordFileHandles]
severity = 5

perlcritic 실행

# 파일 검사
perlcritic --severity 3 --theme security lib/MyApp/Handler.pm

# 전체 프로젝트 검사
perlcritic --severity 3 --theme security lib/

# CI 통합
perlcritic --severity 4 --theme security --quiet lib/ || exit 1

빠른 보안 체크리스트

확인 항목검증 내용
오염 모드CGI/웹 스크립트의 -T 플래그
입력 검증허용 목록 패턴, 길이 제한
파일 작업3-인수 열기, 경로 탐색 검사
프로세스 실행목록 형태 system, 셸 보간 금지
SQL 쿼리DBI 플레이스홀더, 절대 보간 금지
HTML 출력encode_entities(), 템플릿 자동 이스케이프
CSRF 토큰상태 변경 요청 시 생성 및 확인
세션 구성Secure, HttpOnly, SameSite 쿠키
HTTP 헤더CSP, X-Frame-Options, HSTS
의존성버전 고정, 감사된 모듈
정규식 안전성중첩된 수량자 없음, 앵커 패턴
오류 메시지사용자에게 스택 추적 또는 경로 노출 금지

안티패턴

# 1. 사용자 데이터로 2-인수 열기 (명령 주입)
open my $fh, $user_input;               # 치명적 취약점

# 2. 문자열 형태 system (셸 주입)
system("convert $user_file output.png"); # 치명적 취약점

# 3. SQL 문자열 보간
$dbh->do("DELETE FROM users WHERE id = $id");  # SQLi

# 4. 사용자 입력으로 eval (코드 주입)
eval $user_code;                         # 원격 코드 실행

# 5. 정리 없이 $ENV 신뢰
my $path = $ENV{UPLOAD_DIR};             # 조작 가능
system("ls $path");                      # 이중 취약점

# 6. 검증 없이 오염 모드 비활성화
($input) = $input =~ /(.*)/s;           # 게으른 오염 제거 — 목적 상실

# 7. HTML에서 원시 사용자 데이터
print "<div>Welcome, $username!</div>";  # XSS

# 8. 검증되지 않은 리디렉션
print $cgi->redirect($user_url);         # 오픈 리디렉션

기억하세요: Perl의 유연성은 강력하지만 규율이 필요합니다. 웹에 노출되는 코드에는 오염 모드를 사용하고, 모든 입력을 허용 목록으로 검증하고, 모든 쿼리에 DBI 플레이스홀더를 사용하고, 모든 출력을 해당 컨텍스트에 맞게 인코딩하세요. 심층 방어 — 단일 계층에 절대 의존하지 마세요.

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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

관련 스킬