mcpField
B
boxlang-security

boxlang-security

github.com/ortus-boxlangセキュリティ045 回インストール6 日前に更新
リポジトリ

boxlang-security について

boxlang-securityは、BoxLangアプリケーションを保護する開発者向けのスキルです。ランタイム制御や一般的な脆弱性への対策に加え、シークレット管理と本番環境の強化方法を扱います。

主な機能

  1. 01ランタイムのセキュリティ設定
  2. 02SQLインジェクション対策
  3. 03コンテキストに応じた出力エンコード
  4. 04CSRFとアップロードの保護
  5. 05シークレット管理と認証の実践
  6. 06本番環境の強化チェックリスト

活用シーン

  1. 01BoxLangアプリケーションの安全性確認
  2. 02フォーム、アップロード、リモート関数の保護
  3. 03BoxLangアプリケーションの本番環境への準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ortus-boxlang/skills --skill boxlang-security

インストール後、Claude Code で/boxlang-securityを入力して読み込まれたか確認します。

詳細

BoxLang のセキュリティ

BoxLang は、開発者と AI エージェントが協力してアプリケーションを構築、モダナイズ、実行するための、AI ネイティブなソフトウェア生産性プラットフォームです。

概要

BoxLang は、ColdFusion/CFML と JVM が持つ強力な機能だけでなく、歴史的に蓄積された攻撃対象領域も引き継いでいます。 このスキルでは、安全なアプリケーションをゼロから構築するためのセキュリティパターンとランタイム制御について説明します。


ランタイムのセキュリティ設定(boxlang.json)

"security": {
    // Regex patterns — prevent dangerous Java class access
    "disallowedImports": [
        "java\\.lang\\.(ProcessBuilder|Runtime)",
        "java\\.io\\.(FileWriter|PrintWriter)",
        "java\\.lang\\.reflect\\."
    ],
    // BIFs that should be disabled in production web apps
    "disallowedBifs": [
        "createObject",
        "systemExecute",
        "getSystemInfo"
    ],
    // Components that should be disabled in production
    "disallowedComponents": [
        "execute"
    ],
    // Prevent system properties from leaking into server scope
    "populateServerSystemScope": false,
    // Explicit upload whitelist (overrides disallowed list)
    "allowedFileOperationExtensions": [ "jpg", "png", "pdf", "docx" ],
    // Dangerous executable extensions blocked on file upload and copy/move
    "disallowedFileOperationExtensions": [
        "exe", "bat", "sh", "bx", "bxm", "bxs", "php", "jsp", "jar", "dll"
    ]
}

インジェクションの防止

SQL インジェクション

SQL を文字列連結で絶対に構築しないでください。常に QueryParam を使用してください。

// BAD — SQL injection vulnerability
var result = queryExecute(
    "SELECT * FROM users WHERE email = '#email#'"
)

// GOOD — parameterized query (prevents SQL injection)
var result = queryExecute(
    "SELECT * FROM users WHERE email = :email",
    { email: { value: arguments.email, cfsqltype: "cf_sql_varchar" } }
)

// GOOD — struct shorthand
var result = queryExecute(
    "SELECT * FROM users WHERE id = :id AND status = :status",
    {
        id:     { value: arguments.id,     cfsqltype: "cf_sql_integer" },
        status: { value: arguments.status, cfsqltype: "cf_sql_varchar" }
    }
)
クロスサイトスクリプティング(XSS)

HTML コンテキストでは、ユーザー入力による出力をすべてエンコードしてください。

// BAD — raw user input rendered in HTML
<bx:output>#userComment#</bx:output>

// GOOD — HTML encode before output
<bx:output>#encodeForHTML( userComment )#</bx:output>

// For HTML attribute context
<input value="#encodeForHTMLAttribute( userInput )#">

// For JavaScript context
<script>var name = "#encodeForJavaScript( userName )#";</script>

// For URL context
<a href="/search?q=#encodeForURL( searchTerm )#">Search</a>

コンテキストごとに適切なエンコード関数を使用してください。

コンテキスト関数
HTML コンテンツencodeForHTML()
HTML 属性encodeForHTMLAttribute()
JavaScript 文字列encodeForJavaScript()
URL パラメーターencodeForURL()
CSS 値encodeForCSS()
クロスサイトリクエストフォージェリ(CSRF)
// Generate a token per session/form
var token = generateSecureToken()
session.csrfToken = token

// In the form template
<input type="hidden" name="csrfToken" value="#session.csrfToken#">

// Validate on POST
function onRequestStart( required string targetPage ) {
    if ( cgi.request_method == "POST" ) {
        if ( !structKeyExists( form, "csrfToken" ) ||
             form.csrfToken != session.csrfToken ) {
            throw( type="SecurityException", message="CSRF validation failed" )
        }
    }
    return true
}

ファイルアップロードのセキュリティ

ファイルを処理する前に、必ずアップロード内容を検証してください。

function handleUpload( required string fieldName ) {
    // Restrict to explicit allowed types and extensions
    var upload = fileUpload(
        destination    = getTempDirectory(),
        filefield      = arguments.fieldName,
        accept         = "image/jpeg,image/png,application/pdf",
        nameconflict   = "makeunique"
    )

    // Validate extension explicitly regardless of MIME type
    var allowedExtensions = [ "jpg", "jpeg", "png", "pdf" ]
    if ( !allowedExtensions.contains( lCase( upload.serverFileExt ) ) ) {
        fileDelete( upload.serverDirectory & "/" & upload.serverFile )
        throw( type="SecurityException", message="Disallowed file type" )
    }

    // Validate file size
    var maxSizeBytes = 5 * 1024 * 1024  // 5 MB
    if ( upload.fileSize > maxSizeBytes ) {
        fileDelete( upload.serverDirectory & "/" & upload.serverFile )
        throw( type="SecurityException", message="File too large" )
    }

    // Store outside the webroot, serve via handler — never serve raw uploads
    var safePath = expandPath( "/private/uploads/" ) & createUUID() & "." & upload.serverFileExt
    fileMove( upload.serverDirectory & "/" & upload.serverFile, safePath )

    return safePath
}

シークレットの管理

シークレットをハードコードしないでください。BoxLang の設定で環境変数を使用してください。

// boxlang.json — uses env var substitution
"datasources": {
    "mainDB": {
        "driver":   "postgresql",
        "host":     "${env.DB_HOST:localhost}",
        "username": "${env.DB_USERNAME:app}",
        "password": "${env.DB_PASSWORD}"   // No default — fail fast if missing
    }
}

コード内では、ハードコードされた値ではなく環境変数を参照してください。

// GOOD — read from environment
var apiKey    = server.system.environment.PAYMENT_API_KEY ?: ""
var jwtSecret = server.system.environment.JWT_SECRET ?: ""

if ( apiKey.isEmpty() ) {
    throw( type="ConfigException", message="PAYMENT_API_KEY is not configured" )
}

認証パターン

パスワードのハッシュ化
// GOOD — bcrypt-style hash (use bx-bcrypt module or GeneratePBKDFKey)
var salt   = generateSecureToken( 32 )
var hashed = hash( arguments.password & salt, "SHA-512" )

// Even better — use a dedicated BCrypt module
// install bx-bcrypt
var bcrypt  = new BCrypt()
var hashed  = bcrypt.hashpw( plainPassword, bcrypt.gensalt() )

// Verify
var isMatch = bcrypt.checkpw( loginPassword, storedHash )
JWT/トークンの検証
// Always validate before trusting any token data
function validateToken( required string token ) {
    try {
        // Use the bx-jwt module
        var claims = jwtService.decode( arguments.token )

        // Validate expiry
        if ( claims.exp < epochSecond() ) {
            throw( type="AuthException", message="Token expired" )
        }

        return claims
    } catch ( "JWT" e ) {
        throw( type="AuthException", message="Invalid token" )
    }
}

セッションのセキュリティ

Application.bx でセッション設定を行ってください。

class {
    this.name              = "MyApp"
    this.sessionManagement = true
    this.sessionTimeout    = createTimeSpan( 0, 2, 0, 0 )  // 2 hours

    // Rotate session ID after login (session fixation prevention)
    function onLogin( required struct user ) {
        var oldData = duplicate( session )
        sessionRotate()  // BoxLang BIF to regenerate session ID
        structDelete( session, "ALL" )
        structAppend( session, oldData )
        session.userId    = user.id
        session.isLoggedIn = true
    }
}

パストラバーサルの防止

ユーザー入力によるファイルパスをすべて検証してください。

function readFile( required string filename ) {
    var safeBase   = expandPath( "/app/uploads/" )
    var fullPath   = safeBase & arguments.filename
    var cleanPath  = createObject( "java", "java.io.File" ).init( fullPath ).getCanonicalPath()

    // Ensure the resolved path is within the allowed directory
    if ( !cleanPath.startsWith( safeBase ) ) {
        throw( type="SecurityException", message="Path traversal detected" )
    }

    return fileRead( cleanPath )
}

入力の検証

// Validate and sanitize input at the boundary
function processContactForm( required struct form ) {
    var errors = []

    // Length limits
    if ( form.name.len() > 100 ) errors.append( "Name too long" )

    // Email format
    if ( !isValid( "email", form.email ) ) errors.append( "Invalid email" )

    // Numeric ranges
    if ( !isNumeric( form.age ) || form.age < 0 || form.age > 150 ) {
        errors.append( "Invalid age" )
    }

    // Allowlist for enum-style fields
    var validTopics = [ "support", "sales", "billing" ]
    if ( !validTopics.contains( lCase( form.topic ) ) ) {
        errors.append( "Invalid topic" )
    }

    if ( !errors.isEmpty() ) {
        throw( type="ValidationException", message=errors.toList( ", " ) )
    }
}

リモート関数の公開(Web サービス)

クラス関数を Web からアクセス可能なエンドポイントとして公開する場合は、アクセスを明示的に制御してください。

class {
    remote struct function getUser( required numeric id ) returnformat="json" {
        // Always authenticate remote calls
        if ( !isAuthenticated() ) {
            httpSetResponseStatus( 401 )
            return { error: "Unauthorized" }
        }

        // Validate and sanitize the id parameter
        if ( !isValid( "integer", arguments.id ) || arguments.id < 1 ) {
            httpSetResponseStatus( 400 )
            return { error: "Invalid user ID" }
        }

        return userService.getById( arguments.id )
    }
}

本番環境のセキュリティ強化チェックリスト

  • 必要な場合を除き、populateServerSystemScope: false を設定する
  • 危険な BIF(systemExecute、必要でない場合は createObject)を削除するため、disallowedBifs を設定する
  • Java クラスへのアクセスを制限するため、disallowedImports を設定する
  • OS コマンドの実行を防ぐため、disallowedComponents: ["execute"] を設定する
  • すべてのシークレットを環境変数で管理する。コードやコミット済みの設定ファイルには含めない
  • すべての SQL でパラメーター化クエリ(queryParam)を使用する
  • すべてのユーザー出力を、コンテキストに適したエンコード関数で HTML エンコードする
  • 状態を変更するすべてのフォームに CSRF トークンを設ける
  • ファイルアップロードを Web ルート外に保存し、拡張子とサイズを検証する
  • セッションタイムアウトを設定し、権限昇格時にセッションをローテーションする
  • 本番環境で trustedCache: true と classResolverCache: true を有効にする
  • リバースプロキシまたはコンテナーのレベルで HTTPS を強制する
  • 依存モジュールを最新の状態に保つ(box outdated を確認する)

コメント