Back to flin
flin

30 TODOs, 5 Production Panics, 0 Security Issues

The audit results: 30 TODO markers, 5 production panic calls, and zero security vulnerabilities.

Thales & Claude | March 25, 2026 9 min flin
flinaudittodospanicssecurityresults

Every programmer writes TODOs. They are promises to your future self -- markers left in the heat of implementation, when the feature at hand matters more than the edge case you just noticed. In a codebase built across 301 sessions, those promises accumulate. The question is never whether TODOs exist. The question is whether they represent minor polish or ticking bombs.

FLIN's audit catalogued every open item in 186,252 lines of Rust. Thirty TODOs, ranging from a missing storage backend to an outdated compiler warning message. Five production panic calls that could theoretically crash the runtime. And, in the most welcome finding of the entire audit, zero security vulnerabilities -- no SQL injection vectors, no XSS in template rendering, no path traversal in file operations, no hardcoded secrets.

Here is the full inventory.

The TODO Taxonomy

Not all TODOs are equal. The audit classified each one by severity based on two criteria: how likely is it that a user will hit this code path, and what happens when they do?

Severity Distribution:
  CRITICAL  --  2  (blocks core functionality)
  HIGH      --  4  (degrades important features)
  MEDIUM    -- 11  (affects secondary features)
  LOW       -- 13  (cosmetic or rare edge cases)

The two critical TODOs were both in the core execution path. TODO-001 was the unimplemented storage.destroy_entity() method in the database module -- meaning that destroy commands (hard deletes) appeared to work but never actually removed data from disk. TODO-003 was the missing CloseUpvalue emission in the codegen module -- meaning that closures capturing mutable variables could exhibit incorrect behavior when the enclosing scope exited.

rust// TODO-001: database/zerocore.rs line 4488
// The destroy command executed in memory but never persisted
fn destroy_entity(&mut self, entity_type: &str, id: u64) {
    // In-memory removal worked fine
    self.collections.get_mut(entity_type)
        .map(|c| c.remove(&id));

    // But disk persistence was missing:
    // TODO: Implement storage.destroy_entity() to remove from disk
    // Result: destroyed entities would reappear after server restart
}
rust// TODO-003: codegen/emitter.rs line 325
// Closures did not close over upvalues on scope exit
fn end_scope(&mut self) {
    // Variables go out of scope...
    // TODO: Emit CloseUpvalue when full closure support is implemented
    // Result: mutable captures could read stale values
}

These were not theoretical risks. Any FLIN application using destroy would experience data resurrection on restart. Any FLIN application using closures with mutable captures could see stale state. Both bugs were subtle enough that they might not be noticed for weeks -- and debugging them without the audit trail would have been enormously time-consuming.

The High-Priority Items

The four high-priority TODOs affected features that worked partially but had correctness gaps:

TODO-002: Transaction WAL entries. Transactions executed correctly in memory but did not write grouped entries to the Write-Ahead Log. This meant that a crash during a transaction commit could leave the database in a partially committed state -- exactly the scenario transactions are designed to prevent.

TODO-004 and TODO-012: Destructuring codegen. Two related TODOs marking the same gap -- destructuring patterns in let statements (let [a, b, c] = list) did not generate bytecode. The parser accepted the syntax, the typechecker validated it, but the codegen silently produced no output.

TODO-020: Component click handler serialization. When FlinUI components like <Button> received click handlers with arguments, the renderer serialized them incorrectly, producing onClick={fn(); _flinUpdate()} instead of the proper _flinAction('fn', [args]) format. This caused component buttons to fail inside loops where the argument values were loop-scoped.

rust// TODO-020: renderer.rs line 3788
// Component click handlers lost their arguments in loops
//
// What the renderer generated:
//   onClick={deleteTodo(); _flinUpdate()}
//
// What it should have generated:
//   _flinAction('deleteTodo', [{"id":3,"title":"Buy milk","done":false}])
//
// The fix would require evaluating arguments at prop extraction time
// while the loop scope was still active

The Medium-Priority Inventory

The eleven medium-priority TODOs fell into three clusters:

Typechecker gaps (4 items). TODOs 005, 007, 008, and 009 were all in the typechecker's check_pattern() function. Destructuring patterns used FlinType::Unknown for all bindings instead of extracting proper types from the source value. This did not cause runtime errors -- the VM is dynamically typed at the value level -- but it meant that the typechecker could not catch type mismatches in destructured bindings.

WebSocket features (4 items). TODOs 021-024 marked missing WebSocket capabilities: no direct send to specific connections, no binary message support, no connection close capability, and no fragmented message handling per RFC 6455. FLIN's WebSocket worked for basic broadcast scenarios but lacked the features needed for production real-time applications.

Database and storage (3 items). TODO-013 for the TryUnwrap opcode (the ? operator for error propagation), TODO-017 for entity predicate filtering (.where() returned all entities), and TODO-028 for the S3 storage backend (returned an error when users tried to configure cloud storage).

rust// TODO-017: vm.rs line 5084
// Entity.where() accepted a predicate but ignored it
OpCode::QueryWhere => {
    let predicate = self.pop()?;  // Popped and discarded!
    let entity_type = self.pop()?;
    // Returns ALL entities instead of filtered results
    let all = self.database.get_all(&entity_type)?;
    self.push(Value::List(all))?;
}

The Low-Priority Remainder

Thirteen low-priority TODOs, each representing a minor polish item or a rare edge case:

  • Console entity registry wiring (3 TODOs for admin dashboard stats)
  • Format destructuring display (already working via the Display trait)
  • Outdated compiler warning message
  • Value::List support in persistence (intentional design -- lists serialize to JSON)
  • Range opcode TODO (already implemented, the comment was stale)
  • list.where() with lambda predicates
  • Union type computation for try/catch blocks
  • Middleware next() call verification

None of these would cause data loss or incorrect behavior. They were the kind of rough edges that make a difference between a beta and a polished release, but not between a working system and a broken one.

The Five Production Panics

Rust's panic! macro is an intentional crash -- the program terminates with a stack trace. In test code, panics are expected (every assert! is a potential panic). In production code, they are almost always bugs.

The audit identified 5 production panic calls that could be reached through normal user operations:

PANIC-001  codegen/bytecode.rs:1711  Constant pool overflow (max u16)
PANIC-002  codegen/bytecode.rs:2709  Expected Float type assertion
PANIC-003+ vm/vm.rs (various)        Type assertions in native functions
PANIC-004+ vm/renderer.rs (various)  Parsing assertions in template engine

PANIC-001 was a legitimate fatal error -- if a FLIN program defines more than 65,535 constants, the bytecode format cannot represent the constant pool index. This is a hard limit of the bytecode format and panicking is the correct response (though a friendly error message would be better than a raw panic).

PANIC-002 was an internal consistency check. If the compiler generates a Float constant but the value stored is not actually a float, something has gone deeply wrong in the compilation pipeline. Again, panicking is arguably correct here, though converting to a Result would allow for cleaner error reporting.

The remaining panics in vm.rs and renderer.rs were type assertions on values received from FLIN programs. These could be triggered by sufficiently creative (or buggy) FLIN code and should be converted to proper error handling:

rust// Example of a production panic that should be a Result
// vm.rs -- inside a native function handler
fn builtin_string_split(&mut self) -> Result<Value, VmError> {
    let separator = self.pop()?;
    let string = self.pop()?;

    // PANIC: if string is not a string type
    let s = self.extract_string(&string)
        .expect("split() requires a string argument");  // <-- should be ?

    // Better:
    let s = self.extract_string(&string)
        .ok_or(VmError::TypeError {
            expected: "text",
            got: string.type_name(),
            context: "split() first argument",
        })?;

    // ... rest of implementation
}

The Security Clean Bill

The most significant finding of the entire audit was the absence of security vulnerabilities. Across 186,252 lines of code implementing a web server, a database, a template engine, file operations, and OAuth integrations, the audit found:

  • No SQL injection vectors (FLIN uses its own query engine, not SQL)
  • No XSS in template rendering (output escaping is applied by default)
  • No path traversal in file operations (paths are validated against allowed directories)
  • No CSRF vulnerabilities (guard system enforces token checking)
  • No hardcoded secrets in code
  • httpOnly, secure, sameSite attributes on all session cookies
  • Proper password hashing in authentication flows

This is partly a benefit of Rust (no buffer overflows, no use-after-free) and partly a result of FLIN's architecture. Because FLIN does not use SQL -- it has its own embedded database with a purpose-built query language -- the entire class of SQL injection vulnerabilities simply does not exist. Because FLIN's template engine escapes output by default and only allows raw HTML through an explicit @html directive, XSS requires a deliberate developer choice rather than an accidental omission.

flin// FLIN's template engine escapes by default
<p>{user.name}</p>              // Auto-escaped: safe from XSS
<p>{@html user.bio}</p>         // Explicit opt-in: developer takes responsibility

// Entity queries use a type-safe API, not string concatenation
users = User.where(name == input)  // No injection possible

The Audit's Verdict

Thirty TODOs in 186,252 lines is a density of one TODO per 6,208 lines -- well below the industry average for a codebase of this age and velocity. Five production panics is five too many for a language runtime, but all were in code paths that required specific, unusual inputs to trigger. Zero security issues is the result that mattered most for a language that generates web-facing applications.

The audit did not just count defects. It mapped them. It classified them. And it produced a fix plan that would organize the resolution of every finding into phases, from critical items that blocked the beta launch to low-priority polish items that could be addressed at leisure.

The next article covers that fix plan -- how we took the audit's inventory and turned it into a systematic elimination of every open item in the FLIN codebase.


This is Part 148 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: - [147] The Duplicate Opcode That Almost Broke Everything - [148] 30 TODOs, 5 Production Panics, 0 Security Issues (you are here) - [149] The Audit Fix Plan

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles