boxlang-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited boxlang-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
BoxLang inherits both the power and the historical attack surface of ColdFusion/CFML and the JVM. This skill documents security patterns and runtime controls to help build secure applications from the ground up.
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"
]
}NEVER build SQL with string concatenation. Always use 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" }
}
)Encode all user-supplied output in HTML context:
// 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>Use the right encoding function per context:
| Context | Function |
|---|---|
| HTML content | encodeForHTML() |
| HTML attributes | encodeForHTMLAttribute() |
| JavaScript strings | encodeForJavaScript() |
| URL parameters | encodeForURL() |
| CSS values | encodeForCSS() |
// 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
}Always validate file uploads before processing:
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
}Never hardcode secrets. Use environment variables via the BoxLang config:
// 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
}
}In code, access via environment (not hardcoded):
// 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 )// 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" )
}
}Configure session settings in 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
}
}Validate all user-supplied file paths:
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( ", " ) )
}
}When exposing class functions as web-accessible endpoints, explicitly control access:
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 unless neededdisallowedBifs to remove dangerous BIFs (systemExecute, createObject if not needed)disallowedImports for Java class access restrictionsdisallowedComponents: ["execute"] to prevent OS command executionqueryParam)trustedCache: true and classResolverCache: true in productionbox outdated)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.