boxlang-security について
boxlang-securityは、BoxLangアプリケーションを保護する開発者向けのスキルです。ランタイム制御や一般的な脆弱性への対策に加え、シークレット管理と本番環境の強化方法を扱います。
主な機能
- 01ランタイムのセキュリティ設定
- 02SQLインジェクション対策
- 03コンテキストに応じた出力エンコード
- 04CSRFとアップロードの保護
- 05シークレット管理と認証の実践
- 06本番環境の強化チェックリスト
活用シーン
- 01BoxLangアプリケーションの安全性確認
- 02フォーム、アップロード、リモート関数の保護
- 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を確認する)
コメント
Markdown 対応