Back to flin
flin

The Security Sprint: 18 Sessions

18 sessions in 2 days to build a complete security system from scratch.

Thales & Claude | March 25, 2026 10 min flin
flinsecuritysprintauthenticationauthorization

Security is not a feature you add later. It is a foundation you build early or spend the rest of the project regretting. On January 15, 2026, FLIN's security sprint began with Session 183 and ended with Session 200. Eighteen sessions. Two days. A complete security stack: AES-256-GCM encryption, Argon2 password hashing, JWT authentication, CSRF protection, security headers, rate limiting, guards, middleware, request validation, file upload security, WebSocket authentication, two-factor authentication, OAuth2 with social providers, and 75 dedicated security tests.

This is the story of how a programming language went from zero security primitives to a production-ready security framework in a single weekend.

The Security Landscape: Session 183

Session 183 established the foundation with 18 security functions across four categories: cryptography, JWT authentication, secrets management, and password utilities.

The cryptography functions used industry-standard algorithms with no room for compromise:

// AES-256-GCM encryption -- the gold standard
key = secure_random(32)
encrypted = encrypt("sensitive data", key)
decrypted = decrypt(encrypted, key)

// Argon2id password hashing -- winner of the Password Hashing Competition hash = argon2_hash("user_password") valid = argon2_verify("user_password", hash)

// CSRF protection with constant-time comparison csrf = csrf_token() if csrf_verify(form_token, csrf) { // Process form safely } ```

The design principle was explicit: FLIN developers should not need to know the difference between AES-128 and AES-256, or between bcrypt and Argon2. The language provides one function for each operation, and that function uses the best available algorithm. encrypt() always uses AES-256-GCM. argon2_hash() always uses Argon2id. There is no configuration because there is no reason to use a weaker option.

The JWT implementation was equally opinionated:

// JWT creation with automatic claims (Session 183)
// Always includes iat (issued at) and exp (expiration)
// Default expiration: 1 hour
// Algorithm: HS256

fn jwt_create(payload: &Map, secret: &str, expires_in: Option) -> String { let expiration = expires_in.unwrap_or(3600); // 1 hour default let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() as i64;

let mut claims = payload.clone(); claims.insert("iat".to_string(), Value::Int(now)); claims.insert("exp".to_string(), Value::Int(now + expiration));

encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes())) .unwrap_or_default() } ```

By the end of Session 183, FLIN had 18 security functions, and the test count stood at 2,538. The security category had jumped from 20/160 to 38/160 (23.75%).

Sessions 184-186: Headers, Rate Limiting, and Guards

Session 184 implemented security headers -- the HTTP response headers that protect against common web attacks:

// Security headers applied automatically to every response
//
// Content-Security-Policy: default-src 'self'
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// X-XSS-Protection: 1; mode=block
// Strict-Transport-Security: max-age=31536000; includeSubDomains
// Referrer-Policy: strict-origin-when-cross-origin

FLIN applies these headers by default. Developers can customize them, but the defaults follow OWASP recommendations. A new FLIN application is more secure out of the box than most hand-configured Express or Django applications.

Session 184 also implemented rate limiting -- a critical defense against brute-force attacks, credential stuffing, and API abuse:

pub struct RateBucket {
    count: u64,
    window_start: u64,
    window_ms: u64,
    limit: u64,
}

impl RateBucket { pub fn increment(&mut self) -> bool { let now = current_timestamp_ms(); if now - self.window_start >= self.window_ms { // New window self.count = 1; self.window_start = now; true } else if self.count < self.limit { self.count += 1; true } else { false // Rate limited } } } ```

Sessions 185-186 built the guards system -- FLIN's declarative approach to access control:

// Guards: declarative security at the route level

route GET "/admin/dashboard" { guard auth // Must be authenticated guard role("admin") // Must have admin role guard rate_limit(100, "1m") // 100 requests per minute

// Route handler -- only reached if all guards pass users = User.all { users: users } } ```

Guards are composable and ordered. Each guard either passes (allowing the request to continue) or fails (returning an appropriate HTTP error). The guard auth directive checks for a valid JWT or session token. The guard role("admin") directive checks the authenticated user's role. The guard rate_limit(100, "1m") directive enforces request throttling.

Sessions 187-193: Middleware, Validation, and Response Helpers

Sessions 187-188 implemented FLIN's middleware system. Unlike guards (which either pass or fail), middleware can modify requests and responses:

// Middleware: transform requests and responses

middleware log_request { print("${request.method} ${request.path}") next() // Continue to next middleware or handler }

middleware cors { response.header("Access-Control-Allow-Origin", "*") if request.method == "OPTIONS" { return { status: 204 } } next() }

route GET "/api/data" { use log_request use cors

{ data: "response" } } ```

Sessions 189-192 built the request validation system. FLIN validates incoming request bodies against declared schemas, rejecting malformed input before it reaches the handler:

route POST "/api/users" {
    validate {
        name: text @required @min_length(2) @max_length(100)
        email: text @required @email
        age: int @min(13) @max(150)
        password: text @required @min_length(8)
    }

// body.name, body.email, body.age, body.password // are all validated and type-safe at this point

hash = argon2_hash(body.password) save User { name: body.name, email: body.email, password_hash: hash } { status: "created" } } ```

The validation system uses annotations (@required, @email, @min_length, @max_length, @min, @max) that are checked at runtime. Invalid requests receive a 400 response with detailed error messages describing which fields failed and why.

Session 193 added response helpers -- utility functions for common HTTP response patterns:

// Response helpers
json({ data: users })           // 200 with JSON body
created({ id: user.id })        // 201 Created
no_content()                    // 204 No Content
bad_request("Invalid input")    // 400 Bad Request
unauthorized("Token expired")   // 401 Unauthorized
forbidden("Admin only")         // 403 Forbidden
not_found("User not found")     // 404 Not Found

Sessions 194-196: File Upload and WebSocket Security

Session 194 secured file uploads with comprehensive validation:

route POST "/upload" {
    validate {
        file: file @required @max_size("5MB") @extension(".jpg", ".png", ".pdf")
    }

path = save_file(body.file, "uploads/") { path: path } } ```

The save_file() function includes security features built during the file upload session: path traversal protection (blocking .. in filenames), UUID-based filename generation (preventing filename collisions and information leakage), automatic directory creation, and SHA-256 hash computation for integrity verification.

Sessions 195-196 extended security to WebSocket connections and configuration guards.

Sessions 197-198: Two-Factor Authentication and OAuth2

Session 197 implemented TOTP-based two-factor authentication:

// Generate 2FA secret for a user
secret = totp_secret()
uri = totp_uri(secret, "[email protected]", "MyApp")
qr = totp_qr(uri)  // Data URL for QR code display

// Verify a 2FA code valid = totp_verify(secret, user_code)

// Backup codes for recovery codes = backup_codes(8) // Generate 8 backup codes ```

Session 198 was one of the most ambitious of the sprint: OAuth2 with social authentication providers. In a single session, FLIN gained support for Google, GitHub, and WhatsApp authentication:

// OAuth2 configuration
route GET "/auth/google" {
    redirect(oauth2_url("google", {
        client_id: env("GOOGLE_CLIENT_ID"),
        redirect_uri: "https://myapp.com/auth/google/callback",
        scope: "openid email profile"
    }))
}

route GET "/auth/google/callback" { token = oauth2_exchange("google", query.code, { client_id: env("GOOGLE_CLIENT_ID"), client_secret: env("GOOGLE_CLIENT_SECRET"), redirect_uri: "https://myapp.com/auth/google/callback" })

user_info = oauth2_userinfo("google", token.access_token) // Create or update user from OAuth profile } ```

Sessions 199-200: Testing Everything

Session 199 wrote integration tests for the complete security stack. Session 200 was dedicated entirely to unit tests -- 75 new tests covering every security function:

Security Unit Tests (Session 200):

Rate Limiting: 8 tests Guards: 34 tests Password (Argon2): 7 tests JWT Tokens: 6 tests Secrets: 3 tests Crypto (AES/UUID): 5 tests (including roundtrip) CSRF: 5 tests 2FA/TOTP: 7 tests

Total New: 75 tests Total Project: 2,926 tests (0 failures) ```

The test coverage was comprehensive. Password hashing tests verified that Argon2 produced valid PHC-format strings and that different passwords produced different hashes. JWT tests verified creation, verification, decoding, expiration detection, and signature validation. Crypto tests verified encrypt/decrypt roundtrips and the uniqueness of random values. CSRF tests verified constant-time comparison to prevent timing attacks.

// Example: Testing constant-time CSRF comparison (Session 200)
#[test]
fn test_csrf_verify_matching_tokens() {
    let token = "abc123def456";
    assert!(csrf_verify(token, token));
}

#[test] fn test_csrf_verify_different_tokens() { assert!(!csrf_verify("token_a", "token_b")); }

#[test] fn test_csrf_verify_empty_tokens() { // Empty tokens should not match assert!(!csrf_verify("", "non_empty")); assert!(!csrf_verify("non_empty", "")); } ```

The Complete Security Stack

By Session 200, FLIN's security stack comprised:

CategoryFunctions/FeaturesSessions
CryptographyAES-256-GCM, secure random, UUID183
Password HashingArgon2id, password strength183
JWT AuthenticationCreate, verify, decode, expiry183
Secrets Managementenv_or, env_exists, secret (path-safe)183
CSRF ProtectionToken generation, constant-time verify183
Security HeadersCSP, HSTS, X-Frame, X-Content-Type184
Rate LimitingPer-route, per-IP, sliding window184
Guards Systemauth, role, rate_limit, custom guards185-186
MiddlewareRequest/response transformation187-188
Request ValidationType-safe body validation with annotations189-192
Response HelpersStandard HTTP response utilities193
File Upload SecurityPath traversal, size limits, type checking194
WebSocket AuthToken-based WS authentication195-196
2FA (TOTP)Secret generation, QR codes, backup codes197
OAuth2Google, GitHub, WhatsApp providers198
Security Tests75 dedicated unit tests199-200

All of this is built into the FLIN runtime. No npm install passport. No pip install django-oauth-toolkit. No configuration files. A FLIN developer writes guard auth and authentication is enforced. They write validate { email: text @email } and input is validated. The security stack is part of the language.

Why Two Days Was Enough

Eighteen sessions in two days for a complete security framework sounds impossible by traditional development standards. The explanation is straightforward: security primitives are well-specified.

AES-256-GCM is not a design problem. It is an implementation problem with a clear specification (NIST SP 800-38D). JWT is defined by RFC 7519. TOTP is defined by RFC 6238. OAuth2 is defined by RFC 6749. Argon2 is the winner of the Password Hashing Competition with a published reference implementation.

The AI CTO excels at implementing well-specified standards. Given an RFC number and a target language (Rust), it can produce a correct implementation quickly. The human's role is choosing which standards to implement and how to expose them in FLIN's syntax. Juste decided that FLIN should use Argon2 rather than bcrypt. He decided that security headers should be applied by default. He decided that guards should be declarative. These are product decisions. The implementations are engineering.

What would take a traditional team weeks -- not because the code is hard, but because of code review cycles, testing coordination, documentation, and integration -- takes two days in the CEO + AI CTO model because the feedback loop is minutes, not days.

---

This is Part 200 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a programming language from scratch.

Series Navigation: - [199] The Temporal Debugging Marathon - [200] The Security Sprint: 18 Sessions (you are here) - [201] The File Storage Marathon: 30 Sessions

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles