boxlang-runtime-google-cloud-functions — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited boxlang-runtime-google-cloud-functions (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 is the AI-native software productivity platform for building, modernizing and running applications, with developers and AI agents working together.
BoxLang runs on Google Cloud Functions Gen 2 via the Java 21 runtime. The FunctionRunner bridge handles HTTP request mapping, handler routing, class caching, and response serialization - so you focus on writing BoxLang handler files.
Starter template: https://github.com/ortus-boxlang/boxlang-starter-google-functions
Runtime entry point:
ortus.boxlang.runtime.gcp.FunctionRunnerHTTP Request
→ GCF Gen 2 (java21 runtime)
→ FunctionRunner (HttpFunction)
→ RequestMapper (HttpRequest → BoxLang event struct)
→ Route resolution (manifest.json / handlers/ scan → .bx file)
→ Handler compilation + class cache (warm invocations)
→ Method dispatch (run() or x-bx-function header)
→ BoxLang handler execution
→ ResponseMapper (response struct → HttpResponse)git clone https://github.com/ortus-boxlang/boxlang-starter-google-functions.git
cd boxlang-starter-google-functions
# Verify environment
./gradlew clean testLambda.bx at the project root is the default handler - it runs for / and for any URI that doesn't match a registered route:
// src/main/bx/Lambda.bx
class {
function run( event, context, response ){
response.statusCode = 200
response.headers = { "Content-Type": "application/json" }
response.body = jsonSerialize({
message: "Hello from BoxLang!",
input: event
})
}
// Alternative method (call with x-bx-function header)
function anotherLambda( event, context, response ){
return "Hola!!"
}
}Application.bx)Place Application.bx next to Lambda.bx at the project root for lifecycle hooks. It fires for every request, whether it's served by Lambda.bx or by a routed handler under handlers/, and is never itself a URI-routing target.
class {
this.name = "MyFunction"
boolean function onApplicationStart(){
application.db = createObject("component","DataService").init()
return true
}
boolean function onRequestStart( required string targetPage ){
return true
}
}run() and every request lifecycle hook (onRequestStart, onRequestEnd, onError, onAbort) receive the same response struct as their last argument. The value a handler returns is stored in response.body before onRequestEnd runs, so a hook can wrap or replace it:
class {
function onRequestEnd( target, event, context, response ){
response.body = { ok: true, data: response.body }
}
function onError( exception, eventName, event, context, response ){
response.body = { ok: false, error: exception.message }
// response.statusCode = 404 // override the default 500
}
}Hook arguments: onRequestStart( target, event, context, response ), onRequestEnd( target, event, context, response ), onError( exception, eventName, event, context, response ), onAbort( target, event, context, response ). onApplicationStart and the session hooks are fired by BoxLang itself and receive no request data.
Rules to remember:
onRequestEnd runs before onError. On a failure onRequestEnd wrapsthe empty body first, then onError overwrites it, so onError has the last word.
500 unless onError setsresponse.statusCode. It used to be 200.
Application.bx defines onError, the error counts as handled whateverthe hook returns. To fail the invocation, rethrow from onError or do not define it.
response argument keep working.handlers/Every request goes to Lambda.bx by default. To route requests to a different class based on the URL path, add it under src/main/bx/handlers/ instead of the project root:
// src/main/bx/handlers/Products.bx
class {
function run( event, context, response ){
response.statusCode = 200
response.body = {
"error": false,
"data": [ "Product A", "Product B" ]
}
}
}| Incoming URI | Handler File |
|---|---|
/products | handlers/Products.bx |
/api/test | handlers/api/Test.bx (nested) |
/user-profiles | handlers/UserProfiles.bx (hyphens map to PascalCase) |
/ or anything unmatched | Lambda.bx (the default handler) |
Folders can be nested and use any case you like - only the leaf .bx filename needs to be PascalCase. Only files under `handlers/` are ever routable. Application.bx, Lambda.bx, and anything else at the project root can never be reached this way, no matter what path or x-bx-function header a client sends.
manifest.json: the routing table./gradlew generateManifest scans handlers/ and writes src/main/bx/manifest.json - the build-time routing table the runtime reads once at cold start. It's wired via dependsOn into test, runFunction, and buildLambdaZip, so it can never silently drift. It's gitignored - never hand-edited or committed.
{
"manifestVersion": 1,
"defaultHandler": { "file": "Lambda.bx", "method": "run" },
"handlers": {
"products": { "file": "handlers/Products.bx" },
"api/test": { "file": "handlers/api/Test.bx" }
},
"reserved": ["Application.bx", "Lambda.bx"]
}The runtime enforces reserved and defaultHandler, not just documents them: a manifest can never route to a reserved file, and defaultHandler.file/ method is honored as the fallback handler when present.
If manifest.json is missing or invalid, the runtime falls back to scanning handlers/ directly, and if that directory doesn't exist either, to scanning the function root for backward compatibility with pre-handlers/ deployments - gated behind BOXLANG_ENABLE_ROOT_SCAN (default true; set to false to restrict that last-resort scenario to the default handler only).
Use the x-bx-function header to call a specific method on the resolved handler:
curl http://localhost:9099/products # run()
curl -H "x-bx-function: createOrder" -X POST http://localhost:9099/products \
-H "Content-Type: application/json" \
-d '{"productId": 1, "qty": 2}'Only a public/remote method you declared is reachable this way.
| Variable | Description |
|---|---|
BOXLANG_GCP_ROOT | Root directory for .bx files. Default: /workspace |
BOXLANG_GCP_CLASS | Override the default handler path |
BOXLANG_GCP_DEBUGMODE | Enable verbose logging and disable class caching |
BOXLANG_GCP_CONFIG | Path to a custom boxlang.json configuration |
BOXLANG_ENABLE_ROOT_SCAN | Allow the legacy root-directory routing fallback (see URI Routing above). Default: true. Shared across every BoxLang serverless runtime (AWS/GCP/Azure). |
K_SERVICE / K_REVISION / GOOGLE_CLOUD_PROJECT | Set automatically by GCF |
./gradlew runFunction # start local server (port 9099)
./gradlew runFunction -PtestPort=8080 # custom port
./gradlew runFunction -PdebugMode=true # disable class cache for hot reloadcurl http://localhost:9099/
curl -X POST http://localhost:9099/products -H "Content-Type: application/json" -d '{"qty":2}'
curl -H "x-bx-function: anotherLambda" http://localhost:9099/| Mode | Behavior |
|---|---|
| Cold start | Runtime initializes; first request is slower |
| Warm invocation | Compiled handler class reused - fast |
| Debug mode ON | Class cache disabled; edits picked up immediately |
| Debug mode OFF (production) | Maximum performance via class caching |
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
./gradlew clean shadowJar buildLambdaZip
gcloud functions deploy YOUR_FUNCTION_NAME \
--gen2 \
--runtime=java21 \
--region=us-central1 \
--entry-point=ortus.boxlang.runtime.gcp.FunctionRunner \
--trigger-http \
--allow-unauthenticated \
--source=build/distributions/boxlang-google-function-project-1.0.0.zipIf you changed version in gradle.properties, update the ZIP filename in --source accordingly.
Handler patterns are intentionally identical across all three BoxLang serverless runtimes - the same handlers/ convention, manifest.json schema, run(event, context, response) contract, and x-bx-function header dispatch. .bx code moves between providers unmodified.
| Feature | GCF | AWS Lambda | Azure Functions |
|---|---|---|---|
| Runner class | FunctionRunner | LambdaRunner | AzureFunctionRunner |
| Handler method | run() | run() | run() |
| Method routing header | x-bx-function | x-bx-function | x-bx-function |
| Debug mode env var | BOXLANG_GCP_DEBUGMODE | BOXLANG_LAMBDA_DEBUGMODE | BOXLANG_AZURE_DEBUGMODE |
| Root-scan opt-out | BOXLANG_ENABLE_ROOT_SCAN | BOXLANG_ENABLE_ROOT_SCAN | BOXLANG_ENABLE_ROOT_SCAN |
BOXLANG_GCP_DEBUGMODE=false in productiononError (set response.statusCode when 500 is not right); remember onError always counts as handledhandlers/, manifest.json regenerated via generateManifest (wired into buildLambdaZip)BOXLANG_ENABLE_ROOT_SCAN=false once you've fully migrated to handlers/./gradlew clean test./gradlew shadowJar buildLambdaZip~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.