Skip to content

상수와 오류

라이브러리에 정의된 상수, 오류 타입, 센티널 오류 및 사전 정의된 변수입니다.

보안 제한 상수

기본 제한

go
const (
    // DefaultMaxFileSize - 단일 파일 최대 바이트 수
    DefaultMaxFileSize int64 = 2 * 1024 * 1024  // 2 MB

    // DefaultMaxLineLength - 줄당 최대 길이
    DefaultMaxLineLength int = 1024  // 1 KB

    // DefaultMaxKeyLength - 키 이름 최대 길이
    DefaultMaxKeyLength int = 64

    // DefaultMaxValueLength - 값 최대 길이
    DefaultMaxValueLength int = 4096  // 4 KB

    // DefaultMaxVariables - 파일당 최대 변수 수
    DefaultMaxVariables int = 500

    // DefaultMaxExpansionDepth - 변수 확장 최대 깊이
    DefaultMaxExpansionDepth int = 5
)

하드 상한

경고

다음은 라이브러리 내부 하드 상한(내보내지 않음)으로, Config.Validate() 내부 검사에 사용됩니다. 사용자가 이 상수들을 직접 참조할 수는 없지만, cfg.Validate()가 구성이 이 제한을 초과하는지 자동으로 검사합니다.

상수설명
HardMaxFileSize100 MB파일 크기 하드 상한
HardMaxLineLength64 KB줄 길이 하드 상한
HardMaxKeyLength1024키 길이 하드 상한
HardMaxValueLength1 MB값 길이 하드 상한
HardMaxVariables10000변수 수 하드 상한
HardMaxExpansionDepth20확장 깊이 하드 상한

구성 검증은 하드 제한을 초과하는지 검사합니다:

go
cfg := env.DefaultConfig()
cfg.MaxFileSize = 200 * 1024 * 1024  // 100MB 상한 초과

if err := cfg.Validate(); err != nil {
    // 오류 반환: MaxFileSize exceeds hard limit
}

센티널 오류

경고

다음 센티널들은 모두 미리 정의된 기호이지만, 현재 구현에서 일부 시나리오는 이 센티널들을 errors.Is로 매칭하지 않습니다: 금지 키는 *SecurityError를 반환하고(errors.Is(err, ErrSecurityViolation)로 매칭), 키 형식 위반과 필수 키 누락은 *ValidationError를 반환합니다(errors.As로 추출). 자세한 내용은 각 오류 타입 섹션을 참조하세요.

파일 오류

go
var ErrFileNotFound = errors.New("file not found")
var ErrFileTooLarge = errors.New("file exceeds maximum size limit")

확인 방법:

go
err := loader.LoadFiles(".env")
if errors.Is(err, env.ErrFileNotFound) {
    // 파일이 존재하지 않음
}
if errors.Is(err, env.ErrFileTooLarge) {
    // 파일이 너무 큼
}

파싱 오류

go
var ErrLineTooLong = errors.New("line exceeds maximum length limit")
var ErrInvalidKey = errors.New("invalid key format")
var ErrDuplicateKey = errors.New("duplicate key encountered")

보안 오류

go
var ErrForbiddenKey = errors.New("key is forbidden for security reasons")
var ErrSecurityViolation = errors.New("security policy violation")
var ErrInvalidValue = errors.New("invalid value content")

금지 키 확인:

go
err := loader.Set("PATH", "value")
if errors.Is(err, env.ErrSecurityViolation) {
    // 금지 키 설정 시도는 *SecurityError 반환
}

확장 오류

go
var ErrExpansionDepth = errors.New("variable expansion depth exceeded")

제한 오류

go
var ErrMaxVariables = errors.New("maximum number of variables exceeded")

상태 오류

go
var ErrClosed = errors.New("loader has been closed")
var ErrInvalidConfig = errors.New("invalid configuration")
var ErrAlreadyInitialized = errors.New("default loader already initialized")
var ErrNotInitialized = errors.New("default loader not initialized; call Load() first")
var ErrMissingRequired = errors.New("required key is missing")

확인 방법:

go
// 로더가 닫혔는지 확인
if errors.Is(err, env.ErrClosed) {
    // 로더가 닫힘
}

// 기본 로더가 초기화되었는지 확인
if errors.Is(err, env.ErrAlreadyInitialized) {
    // 기본 로더가 이미 존재하여 Load()를 반복 호출할 수 없음
}

// 기본 로더가 초기화되지 않았는지 확인
if errors.Is(err, env.ErrNotInitialized) {
    // 먼저 env.Load() 또는 env.LoadWithConfig()를 호출해야 함
}

// 필수 키 누락 여부 확인(실제로는 *ValidationError{Rule:"required"} 반환)
var valErr *env.ValidationError
if errors.As(err, &valErr) && valErr.Rule == "required" {
    // 필수 키 누락
}

어댑터 오류

go
var ErrValidateRequiredUnsupported = errors.New(
    "custom validator does not implement ValidateRequired; " +
    "implement Validator interface for required key validation",
)

커스텀 검증기가 KeyValidator 인터페이스만 구현하고 완전한 Validator 인터페이스를 구현하지 않은 경우, ValidateRequired 호출 시 이 오류가 반환됩니다.

확인 방법:

go
if errors.Is(err, env.ErrValidateRequiredUnsupported) {
    // 커스텀 검증기가 필수 키 검증을 지원하지 않음
    // 완전한 Validator 인터페이스를 구현해야 함
}

해결 방법

KeyValidator만 구현하지 말고 Validator 인터페이스(ValidateKey, ValidateValue, ValidateRequired 세 가지 메서드 포함)를 구현하세요.

오류 타입

ParseError

파싱 오류, 위치 정보 포함:

go
type ParseError struct {
    File    string  // 파일 이름
    Line    int     // 줄 번호
    Content string  // 오류 내용(마스크됨)
    Err     error   // 원본 오류
}

사용 예제:

go
err := loader.LoadFiles(".env")
var parseErr *env.ParseError
if errors.As(err, &parseErr) {
    fmt.Printf("파싱 오류 %s:%d: %v\n",
        parseErr.File, parseErr.Line, parseErr.Err)
}

ValidationError

검증 오류:

go
type ValidationError struct {
    Field   string  // 필드 이름
    Value   string  // 값(마스크됨)
    Rule    string  // 규칙
    Message string  // 메시지
}

SecurityError

보안 오류:

go
type SecurityError struct {
    Action  string  // 작업
    Reason  string  // 원인
    Key     string  // 키 이름(마스크됨)
    Details string  // 추가 상세 정보
}

사용 예제:

go
var secErr *env.SecurityError
if errors.As(err, &secErr) {
    fmt.Printf("보안 오류: %s - %s\n", secErr.Action, secErr.Reason)
}

FileError

파일 작업 오류:

go
type FileError struct {
    Path  string  // 파일 경로
    Op    string  // 작업(open, stat, size_check)
    Err   error   // 원본 오류
    Size  int64   // 파일 크기(Size 검사 시)
    Limit int64   // 제한(Size 검사 시)
}

사용 예제:

go
var fileErr *env.FileError
if errors.As(err, &fileErr) {
    fmt.Printf("파일 %s 크기 %d가 제한 %d 초과\n",
        fileErr.Path, fileErr.Size, fileErr.Limit)
}

ExpansionError

변수 확장 오류:

go
type ExpansionError struct {
    Key   string             // 키 이름
    Depth int                // 현재 깊이
    Limit int                // 제한
    Chain string             // 확장 체인(마스킹됨)
    Kind  ExpansionErrorKind // 오류 원인 카테고리(제로 값 = 깊이/순환)
}

오류 분류(Kind 필드):

go
type ExpansionErrorKind int

const (
    // ExpansionDepthKind는 확장이 재귀 깊이 제한에 도달했거나 변수 순환을 감지했음을 나타냅니다.
    // 제로 값이므로 일반적인 깊이/순환 오류는 명시적으로 분류할 필요가 없습니다.
    // errors.Is(err, ErrExpansionDepth)로 이 클래스 오류를 매칭할 수 있습니다.
    ExpansionDepthKind ExpansionErrorKind = iota

    // ExpansionRequiredKind는 필수 변수(${VAR:?message})가 설정되지 않았거나 비어 있음을 나타냅니다.
    // 깊이 오버플로우가 아니므로 ErrExpansionDepth에 매칭되지 않습니다.
    ExpansionRequiredKind
)

errors.Is 동작: *ExpansionErrorKind != ExpansionRequiredKind일 때만 ErrExpansionDepth에 매칭됩니다. 필수 변수 오류는 별도의 실패 모드로, ErrExpansionDepth를 통해 매칭되지 않습니다.

사용 예제:

go
var expErr *env.ExpansionError
if errors.As(err, &expErr) {
    switch expErr.Kind {
    case env.ExpansionDepthKind:
        // 깊이 오버플로우 또는 순환: errors.Is(err, env.ErrExpansionDepth) == true
        fmt.Printf("깊이 %d/%d, 체인: %s\n", expErr.Depth, expErr.Limit, expErr.Chain)
    case env.ExpansionRequiredKind:
        // 필수 변수 미설정: errors.Is(err, env.ErrExpansionDepth) == false
        fmt.Printf("필수 변수 %s 미설정\n", expErr.Key)
    }
}

JSONError

JSON 파싱 오류:

go
type JSONError struct {
    Path    string  // 파일 경로
    Message string  // 오류 메시지
    Err     error   // 원본 오류
}

YAMLError

YAML 파싱 오류:

go
type YAMLError struct {
    Path    string  // 파일 경로
    Line    int     // 줄 번호
    Column  int     // 열 번호
    Message string  // 오류 메시지
    Err     error   // 원본 오류
}

MarshalError

직렬화 오류:

go
type MarshalError struct {
    Field   string  // 필드 이름
    Message string  // 오류 메시지
}

func IsMarshalError(err error) bool  // 확인 함수

사전 정의 변수

DefaultForbiddenKeys

내장 금지 키 목록, 시스템 핵심 변수 수정 방지:

경고

defaultForbiddenKeys는 라이브러리 내부 변수(내보내지 않음)로, env.DefaultForbiddenKeys를 통해 직접 액세스할 수 없습니다. 다음은 참고용 내부 사용 전체 목록입니다.

카테고리금지 키
시스템 경로PATH
동적 링커(Linux)LD_PRELOAD, LD_PRELOAD_32, LD_PRELOAD_64, LD_LIBRARY_PATH, LD_LIBRARY_PATH_32, LD_LIBRARY_PATH_64, LD_AUDIT, LD_DEBUG
macOSDYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH
WindowsCOMSPEC, PATHEXT, SYSTEMROOT, WINDIR
ShellSHELL, ENV, BASH_ENV, IFS
언어 런타임PYTHONPATH, NODE_PATH, PERL5OPT, RUBYLIB

위험 설명:

위험 유형설명
PATH명령 하이재킹명령 검색 경로 수정
LD_PRELOAD라이브러리 주입악성 동적 라이브러리 사전 로드
LD_LIBRARY_PATH라이브러리 하이재킹라이브러리 검색 경로 수정
DYLD_INSERT_LIBRARIES라이브러리 주입macOS 라이브러리 주입
COMSPEC명령 하이재킹Windows 명령 인터프리터 경로 덮어쓰기
PATHEXT명령 하이재킹Windows 실행 파일 확장자 변조
SYSTEMROOT시스템 파괴Windows 시스템 루트 디렉터리 변조
WINDIR시스템 파괴Windows 디렉터리 변조
PYTHONPATH모듈 하이재킹Python 모듈 검색 경로
IFS파싱 공격필드 구분자 수정

사용 예제:

go
// 금지 키 설정 시 *SecurityError 반환
err := loader.Set("PATH", "/malicious/path")
if errors.Is(err, env.ErrSecurityViolation) {
    // 키가 금지됨
}

// 추가 금지 키 추가
cfg := env.DefaultConfig()
cfg.ForbiddenKeys = []string{"MY_SENSITIVE_VAR"}

SensitiveKeyPatterns

민감 키 패턴 목록, 민감 구성 자동 감지에 사용. 키 이름이 이 패턴(대소문자 구분 안 함)을 포함하면 민감한 것으로 식별됩니다:

경고

sensitiveKeyPatterns는 라이브러리 내부 변수(내보내지 않음)로, IsSensitiveKey() 함수를 통해 간접적으로 액세스합니다. 다음은 참고용 주요 민감 패턴 카테고리입니다.

주요 민감 패턴 카테고리:

카테고리패턴 예시
인증 및 권한PASSWORD, SECRET, TOKEN, AUTH, CREDENTIAL, PASSPHRASE, SESSION, COOKIE
API 및 키API_KEY, APIKEY, ACCESS_KEY, SECRET_KEY, PRIVATE_KEY, PUBLIC_KEY
암호화 및 보안PRIVATE, ENCRYPTION_KEY, ENCRYPT_KEY, DECRYPT_KEY, SIGNING_KEY, SIGN_KEY, VERIFY_KEY
금융 및 PIISSN, SOCIAL_SECURITY, CREDIT_CARD, CARD_NUMBER, CVV, CVC, CCV, PAN
암호화폐MNEMONIC, SEED, RECOVERY, WALLET, PRIVATE_ADDRESS
데이터베이스CONNECTION_STRING, CONN_STRING, DATABASE_URL, DB_PASSWORD
클라우드 서비스AWS_SECRET, AZURE_KEY, GCP_KEY, SERVICE_ACCOUNT

매칭 규칙:

  • 대소문자 구분 안 함
  • 키 이름이 어느 하나의 패턴이라도 포함하면 민감으로 식별

사용 예제:

go
// 키가 민감한지 확인
if env.IsSensitiveKey("DB_PASSWORD") {
    // 안전한 방식으로 처리
    secret := env.GetSecure("DB_PASSWORD")
    if secret != nil {
        defer secret.Release()
    }
}

DefaultKeyPattern

기본 키 이름 검증 패턴:

go
var DefaultKeyPattern *regexp.Regexp = nil

성능 최적화

nil 값은 빠른 바이트 수준 검증을 활성화합니다(약 10배 성능 향상). 기본 검증 규칙: 문자로 시작, 문자, 숫자, 밑줄만 포함.

커스텀 패턴:

go
import "regexp"

cfg := env.DefaultConfig()
// 대문자로 시작하는 것만 허용
cfg.KeyPattern = regexp.MustCompile(`^[A-Z][A-Z0-9_]{1,63}$`)

보안 도구 함수

완전한 문서

IsSensitiveKey, MaskValue, SanitizeForLog 등의 보안 도구 함수에 대한 완전한 문서는 SecureValue API에서도 확인할 수 있습니다.

IsSensitiveKey

go
func IsSensitiveKey(key string) bool

키 이름이 민감 패턴에 매칭되는지 확인합니다.

go
if env.IsSensitiveKey("DB_PASSWORD") {
    // 민감 키, 안전한 방식으로 처리
    secret := env.GetSecure("DB_PASSWORD")
    defer secret.Release()
}

MaskValue

go
func MaskValue(key, value string) string

키의 민감도에 따라 마스크된 값을 반환합니다.

go
// 민감 키 - [MASKED:N chars] 형식 반환
masked := env.MaskValue("API_KEY", "secret123")
// 반환: [MASKED:9 chars]

// 비민감 키 - 원래 값 반환(20자 초과 시 잘림)
masked := env.MaskValue("APP_NAME", "myapp")
// 반환: myapp
masked := env.MaskValue("DESCRIPTION", "this is a very long description text")
// 반환: this is a very lo...

MaskKey

go
func MaskKey(key string) string

로그용으로 키 이름을 마스크합니다.

go
masked := env.MaskKey("DB_PASSWORD")
// 반환: DB***

MaskSensitiveInString

go
func MaskSensitiveInString(s string) string

문자열에서 잠재적 민감 콘텐츠를 마스크합니다. 50자를 초과하는 문자열은 잘립니다.

매개변수:

  • s - 원래 문자열

반환값:

  • string - 마스크된 문자열
go
// 긴 문자열은 잘림
log := "This is a very long log message that exceeds 50 characters and will be truncated"
clean := env.MaskSensitiveInString(log)
// 반환: "This is a very long log message that exceeds 50..."

// 짧은 문자열은 그대로 유지
short := "Short message"
clean := env.MaskSensitiveInString(short)
// 반환: "Short message"

경고

이 함수는 주로 긴 문자열을 자르는 데 사용됩니다. 민감 키-값 쌍을 자동으로 마스크하려면 SanitizeForLog를 사용하세요.

SanitizeForLog

go
func SanitizeForLog(s string) string

문자열에서 민감 키-값 쌍 정보를 정리합니다. key=value 형식의 민감 값을 자동으로 감지하고 마스크합니다.

매개변수:

  • s - 원래 문자열

반환값:

  • string - 정리된 문자열

감지하는 민감 키 패턴:

  • password=, secret=, token=, auth=, credential=, passphrase=, session=, cookie=
  • api_key=, apikey=, access_key=, secret_key=, private_key=, public_key=
  • encrypt_key=, decrypt_key=, signing_key=
  • ssn=, credit_card=, card_number=, cvv=, cvc=
  • mnemonic=, seed=, recovery=, wallet=
  • connection_string=, database_url=, db_password=
go
// 민감 키-값 쌍 자동 마스크
msg := "Connected with password=secret123 api_key=abc123"
clean := env.SanitizeForLog(msg)
// 반환: "Connected with password=[MASKED] api_key=[MASKED]"

// 비민감 키-값 쌍은 그대로 유지
msg := "Config loaded: app_name=myapp port=8080"
clean := env.SanitizeForLog(msg)
// 반환: "Config loaded: app_name=myapp port=8080"

사용 시나리오

로그 출력, 오류 메시지, 디버그 정보 등 민감 키-값 쌍을 자동으로 필터링해야 하는 시나리오에 적합합니다.

ClearBytes

go
func ClearBytes(b []byte)

바이트 슬라이스를 안전하게 제로화합니다.

go
sensitive := []byte("secret-data")
// 사용...
env.ClearBytes(sensitive)
// sensitive은 이제 모두 0

FileFormat 상수

파일 형식 타입:

go
type FileFormat int

const (
    FormatAuto  FileFormat = iota  // 자동 감지
    FormatEnv                      // .env 형식
    FormatJSON                     // JSON 형식
    FormatYAML                     // YAML 형식
)

사용 예제:

go
// 형식 감지
format := env.DetectFormat("config.json")  // FormatJSON

// 지정 형식으로 직렬화
data, _ := env.Marshal(cfg, env.FormatJSON)

// 형식 문자열
fmt.Println(format.String())  // "json"

오류 확인 패턴

errors.Is 패턴

센티널 오류 확인:

go
err := loader.LoadFiles(".env")

switch {
case errors.Is(err, env.ErrFileNotFound):
    // 파일이 존재하지 않음
case errors.Is(err, env.ErrFileTooLarge):
    // 파일이 너무 큼
case errors.Is(err, env.ErrSecurityViolation):
    // 금지 키
case errors.Is(err, env.ErrClosed):
    // 로더가 닫힘
}

errors.As 패턴

상세 오류 정보 추출:

go
err := loader.LoadFiles(".env")

var parseErr *env.ParseError
if errors.As(err, &parseErr) {
    fmt.Printf("파싱 오류 %s %d번 줄\n", parseErr.File, parseErr.Line)
}

var fileErr *env.FileError
if errors.As(err, &fileErr) {
    fmt.Printf("파일 %s 크기 %d가 제한 %d 초과\n",
        fileErr.Path, fileErr.Size, fileErr.Limit)
}

var secErr *env.SecurityError
if errors.As(err, &secErr) {
    fmt.Printf("보안 오류: %s - %s\n", secErr.Action, secErr.Reason)
}

완전한 오류 처리 예제

go
package main

import (
    "errors"
    "log"

    "github.com/cybergodev/env"
)

func main() {
    cfg := env.ProductionConfig()
    cfg.FailOnMissingFile = true

    loader, err := env.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer loader.Close()

    err = loader.LoadFiles(".env")
    if err != nil {
        switch {
        case errors.Is(err, env.ErrFileNotFound):
            log.Fatal("구성 파일이 존재하지 않음")

        case errors.Is(err, env.ErrFileTooLarge):
            log.Fatal("구성 파일이 너무 큼")

        case errors.Is(err, env.ErrClosed):
            log.Fatal("로더가 닫힘")

        default:
            var parseErr *env.ParseError
            if errors.As(err, &parseErr) {
                log.Fatalf("파싱 오류 %s:%d - %v",
                    parseErr.File, parseErr.Line, parseErr.Err)
            }

            var fileErr *env.FileError
            if errors.As(err, &fileErr) {
                log.Fatalf("파일 오류 %s - %v", fileErr.Path, fileErr.Err)
            }

            var secErr *env.SecurityError
            if errors.As(err, &secErr) {
                log.Fatalf("보안 오류: %s - %s", secErr.Action, secErr.Reason)
            }

            var jsonErr *env.JSONError
            if errors.As(err, &jsonErr) {
                log.Fatalf("JSON 오류 %s: %s", jsonErr.Path, jsonErr.Message)
            }

            var yamlErr *env.YAMLError
            if errors.As(err, &yamlErr) {
                log.Fatalf("YAML 오류 %s:%d:%d - %s",
                    yamlErr.Path, yamlErr.Line, yamlErr.Column, yamlErr.Message)
            }

            log.Fatal(err)
        }
    }

    // 필수 키 검증
    if err := loader.Validate(); err != nil {
        var valErr *env.ValidationError
        if errors.As(err, &valErr) {
            log.Fatalf("검증 실패: %s - %s", valErr.Field, valErr.Message)
        }
        log.Fatal(err)
    }
}

관련 문서