FLIN's nine built-in guards cover the most common access control patterns: authentication, roles, rate limiting, CSRF, API keys, IP whitelists, time restrictions, resource ownership, and method filtering. But real applications have domain-specific security requirements that no framework can anticipate in advance.
A SaaS application needs to check subscription tiers. An educational platform needs to verify enrollment status. A financial application needs audit logging on every mutation. A multi-tenant system needs to ensure users only access their own tenant's data.
FLIN's guard_definition syntax and middleware system let developers create these custom security layers with the same declarative API as built-in guards.
Defining Custom Guards
The guard_definition keyword creates a new guard type that can be used anywhere the built-in guards are used:
flin// Define a custom guard
guard_definition verified_email {
user = request.user
if user == none || !user.emailVerified {
return response {
status: 403
body: { error: "Email verification required" }
}
}
}The guard definition contains the check logic. If the function returns a response, the guard fails and that response is sent to the client. If the function returns nothing (falls through), the guard passes.
Use the custom guard exactly like a built-in guard:
flinguard auth
guard verified_email
route POST {
// Only authenticated users with verified email reach here
}Parameterized Custom Guards
Custom guards can accept parameters for configurable behavior:
flinguard_definition subscription(required_plan) {
user = request.user
if user == none {
return response {
status: 401
body: { error: "Authentication required" }
}
}
plan_levels = { "free": 0, "starter": 1, "pro": 2, "enterprise": 3 }
user_level = plan_levels[user.plan] || 0
required_level = plan_levels[required_plan] || 0
if user_level < required_level {
return response {
status: 403
body: {
error: "This feature requires the " + required_plan + " plan",
current_plan: user.plan,
required_plan: required_plan,
upgrade_url: "/pricing"
}
}
}
}
// Usage
guard auth
guard subscription("pro")
route POST {
// Only pro and enterprise users
}The error response includes context that the frontend can use to show an upgrade prompt.
Real-World Custom Guard Patterns
Enrollment Check for Education Platform
flinguard_definition enrolled(course_id_param) {
user = request.user
if user == none {
return response { status: 401, body: { error: "Authentication required" } }
}
course_id = to_int(params[course_id_param])
enrollment = Enrollment.where(
user_id == user.id && course_id == course_id
).first
if enrollment == none {
return response {
status: 403
body: {
error: "You are not enrolled in this course",
course_id: course_id,
enroll_url: "/courses/" + to_text(course_id) + "/enroll"
}
}
}
// Attach enrollment to request for downstream use
request.enrollment = enrollment
}
// app/api/courses/[course_id]/lessons/[id].flin
guard auth
guard enrolled("course_id")
route GET {
// request.enrollment is available
lesson = Lesson.find(params.id)
lesson
}Multi-Tenant Isolation
flinguard_definition tenant_member {
user = request.user
if user == none {
return response { status: 401, body: { error: "Authentication required" } }
}
tenant_id = to_int(params.tenant_id || headers["X-Tenant-ID"])
membership = TenantMember.where(
user_id == user.id && tenant_id == tenant_id
).first
if membership == none {
return response {
status: 403
body: { error: "You do not have access to this tenant" }
}
}
request.tenant_id = tenant_id
request.tenant_role = membership.role
}
guard_definition tenant_admin {
if request.tenant_role != "admin" && request.tenant_role != "owner" {
return response {
status: 403
body: { error: "Admin access required for this tenant" }
}
}
}
// app/api/tenants/[tenant_id]/settings.flin
guard auth
guard tenant_member
guard tenant_admin
route PUT {
// Only tenant admins and owners
}Feature Flags
flinguard_definition feature(flag_name) {
flag = FeatureFlag.where(name == flag_name).first
if flag == none || !flag.enabled {
return response {
status: 404
body: { error: "This feature is not available" }
}
}
// Check user-specific rollout
if flag.rollout_percent < 100 {
user = request.user
if user != none {
user_hash = hash_string(to_text(user.id) + flag_name) % 100
if user_hash >= flag.rollout_percent {
return response {
status: 404
body: { error: "This feature is not available" }
}
}
}
}
}
// app/api/beta/ai-search.flin
guard auth
guard feature("ai_search_beta")
route POST {
// Only available when the ai_search_beta flag is enabled
// and the user is in the rollout percentage
}Security Middleware Patterns
While guards handle per-route access control, middleware handles cross-cutting security concerns. Custom middleware is defined in _middleware.flin files.
Audit Logging Middleware
flin// app/api/_middleware.flin
middleware {
start = now()
next()
// Log after handler completes
if request.method != "GET" && request.method != "OPTIONS" {
save AuditLog {
user_id: request.user_id || 0,
method: request.method,
path: request.path,
ip: request.ip,
status: response.status,
duration_ms: (now() - start),
user_agent: request.user_agent,
body_summary: truncate(to_text(body), 500)
}
}
}This middleware logs every mutating request (POST, PUT, DELETE, PATCH) with the user, the endpoint, the response status, and a summary of the request body. The audit trail is automatic for every API endpoint.
Request Signing Middleware
For APIs that require request signing (webhook receivers, partner APIs):
flin// app/api/webhooks/_middleware.flin
middleware {
signature = headers["X-Signature-256"]
if signature == "" {
return response { status: 401, body: { error: "Missing signature" } }
}
expected = hmac_sha256(request.raw_body, env("WEBHOOK_SECRET"))
if !constant_time_eq(signature, "sha256=" + expected) {
return response { status: 401, body: { error: "Invalid signature" } }
}
next()
}Geoblocking Middleware
flin// app/api/restricted/_middleware.flin
middleware {
country = geoip_country(request.ip)
blocked = ["XX", "YY"] // Sanctioned countries
if blocked.contains(country) {
return response {
status: 403
body: { error: "Service not available in your region" }
}
}
request.country = country
next()
}Guard Composition Patterns
Custom guards compose naturally with built-in guards using AND logic:
flin// Maximum security for financial operations
guard auth
guard role("accountant", "admin")
guard verified_email
guard subscription("enterprise")
guard tenant_member
guard rate_limit(10, 60)
guard time("08:00", "18:00")
route POST {
// This endpoint requires:
// 1. Authentication
// 2. Accountant or admin role
// 3. Verified email address
// 4. Enterprise subscription
// 5. Tenant membership
// 6. Under rate limit
// 7. During business hours
}Seven guards, seven lines. Each is evaluated in order. If any fails, the request is rejected with the appropriate status code and error message.
Testing Custom Guards
Custom guards should be tested with the same rigor as built-in guards:
flin// test/guards/subscription_guard_test.flin
test "subscription guard rejects free user for pro feature" {
ctx = mock_request_context({ user: { plan: "free" } })
result = guard_subscription(ctx, ["pro"])
assert result.status == 403
}
test "subscription guard accepts pro user for pro feature" {
ctx = mock_request_context({ user: { plan: "pro" } })
result = guard_subscription(ctx, ["pro"])
assert result == pass
}
test "subscription guard accepts enterprise user for pro feature" {
ctx = mock_request_context({ user: { plan: "enterprise" } })
result = guard_subscription(ctx, ["pro"])
assert result == pass
}The Architecture of Extensible Security
FLIN's security architecture follows a principle: defaults are non-negotiable, extensions are unlimited.
The nine built-in guards and the automatic security headers cannot be removed or weakened. They provide a baseline that every FLIN application inherits. But above that baseline, developers can add any number of custom guards, custom middleware, and custom validation rules to implement their specific security requirements.
This architecture means: - A new FLIN developer gets enterprise-grade security out of the box. - An experienced developer can extend the security model without fighting the framework. - Security features are visible, declarative, and testable. - The attack surface is known and bounded.
This concludes Arc 10 -- FLIN's security features. Ten articles covering OWASP Top 10 coverage, Argon2 password hashing, JWT authentication, rate limiting, security headers, 2FA, OAuth2, WhatsApp OTP, input validation, security testing, and custom guards. In Arc 11, we enter the most innovative part of FLIN: the AI and Intent Engine, where natural language meets database queries.
This is Part 115 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO designed and built a programming language from scratch.
Series Navigation: - [114] 75 Security Tests: How We Verified Everything - [115] Custom Guards and Security Middleware (you are here) - [116] The Intent Engine: Natural Language Database Queries - [117] Semantic Search and Vector Storage