Communitygithub.com

perl-security

Perl 애플리케이션 보안: 테인트 모드, 입력 검증, 안전한 프로세스 실행, DBI 매개변수화 쿼리, 웹 보안(XSS/SQLi/CSRF), perlcritic 보안 정책 제공

perl-security란 무엇인가요?

이 스킬은 Perl 애플리케이션 개발 시 반드시 적용해야 할 포괄적인 보안 패턴을 제공합니다. 사용자 입력 처리, 데이터베이스 쿼리, 파일 및 프로세스 실행, 웹 애플리케이션 등 다양한 영역에서 발생할 수 있는 보안 취약점을 방지하기 위한 실용적인 가이드라인을 제시합니다. Perl의 테인트 모드(-T)를 활용한 입력 검증, DBI 플레이스홀더를 통한 SQL 인젝션 방지, 세 인수 열기를 통한 명령어 인젝션 차단, HTML/URL/JSON 컨텍스트별 출력 인코딩, CSRF 토큰 생성, 보안 HTTP 헤더 설정 등 12개 이상의 세부 보안 주제를 다룹니다. 각 주제별로 안전한 코드 예제와 함께 안티 패턴을 명확히 제시하여 개발자가 실수 없이 보안 코딩을 적용할 수 있도록 돕습니다. 또한 perlcritic 보안 정책 설정과 CI 통합 방법을 제공하여 코드 리뷰 및 품질 관리를 자동화할 수 있습니다. 이 스킬은 CGI, Mojolicious, Dancer2, Catalyst 등 주요 Perl 웹 프레임워크와 DBI, DBIx::Class ORM을 사용하는 모든 Perl 환경에서 활용 가능합니다.

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

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

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

문서

Perl 보안 패턴

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

활성화 시점

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

작동 방식

테인트 인식 입력 경계에서 시작하여 외부로 확장합니다. 입력을 검증하고 언테인트(untaint)하며, 파일 시스템과 프로세스 실행을 제한된 범위 내에서 유지하고, 모든 곳에서 매개변수화된 DBI 쿼리를 사용합니다. 아래 예제는 이 스킬이 사용자 입력, 셸 또는 네트워크를 다루는 Perl 코드를 배포하기 전에 적용해야 하는 안전한 기본값을 보여줍니다.

테인트 모드

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;
}

안전한 파일 작업

세 인수 열기

use v5.36;

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

# 나쁨: 사용자 데이터로 두 인수 열기(명령어 인젝션)
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

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

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

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

# 백틱 연산자 금지
[InputOutput::ProhibitBacktickOperators]
severity = 4

# CGI에서 테인트 검사 요구
[Modules::RequireTaintChecking]
severity = 5

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

안티 패턴

# 1. 사용자 데이터로 두 인수 열기 (명령어 인젝션)
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 플레이스홀더를 사용하고, 모든 출력을 컨텍스트에 맞게 인코딩하세요. 심층 방어(Defense in depth) — 단일 계층에 의존하지 마십시오.

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.

관련 스킬