Skip to content

USL Security Features

Version: 1.0.0
Last Updated: January 15, 2026

Introduction

This document provides detailed technical documentation of USL's built-in security features. USL is designed with security-by-default principles, providing compile-time guarantees for policy enforcement, secret management, and secure code generation.


1. Secret Types

1.1 Overview

USL provides first-class support for sensitive data through the Secret<T> type system. Secrets are tracked through the compiler's type system and enforced at both compile-time and runtime.

1.2 Secret Type Variants

entity User {
  email: String                      // Plain text
  password: Secret<Hashed>           // Bcrypt/Argon2 hash
  ssn: Secret<Encrypted>             // AES-256-GCM encryption
  credit_card: Secret<Tokenized>     // Token replacement
}
Variant Use Case Storage Retrievability
Secret<Hashed> Passwords, PINs One-way hash Not retrievable
Secret<Encrypted> PII, SSNs Encrypted Decryptable with key
Secret<Tokenized> Credit cards External token Via token service

1.3 Secret Flow Analysis

The compiler tracks secret values through all code paths:

E401: Secret Assignment

Error: Cannot assign secret to non-secret variable

entity User {
  password: Secret<Hashed>
}

behavior GetPassword(user: User) -> String {  // ❌ E401 Error
  return user.password  // Cannot expose secret
}

Fix: Return secret type or use approved operations

behavior VerifyPassword(user: User, input: String) -> Bool {
  return verify(input, user.password)  // ✅ Comparison OK
}

E402: Explicit Secret Conversion

Error: Cannot explicitly convert secret to plain type

behavior LeakSecret(user: User) {
  let plain: String = user.password as String  // ❌ E402 Error
}

No workaround: This is intentionally forbidden.

E403: Secret Comparison

Error: Cannot compare secrets with non-secrets

behavior CheckPassword(user: User, input: String) -> Bool {
  return user.password == input  // ❌ E403 Error
}

Fix: Use verification functions

escape VerifyPassword(plain: String, hash: Secret<Hashed>) -> Bool 
  adapter "bcrypt_verify"

behavior CheckPassword(user: User, input: String) -> Bool {
  return VerifyPassword(input, user.password)  // ✅ OK
}

E404: Secret in Error Message

Error: Secret value appears in error message

The compiler detects when secrets might leak through error handling:

behavior UpdatePassword(user: User, password: Secret<Hashed>) {
  if (password.length < 8) {
    throw "Password too short: {password}"  // ❌ E404 Error
  }
}

Fix: Never include secret values in messages

behavior UpdatePassword(user: User, password: Secret<Hashed>) {
  if (password.length < 8) {
    throw "Password too short"  // ✅ OK
  }
}

1.4 Generated Code Security

Hashed Secrets (Passwords)

Generated TypeScript:

import bcrypt from 'bcrypt';

async function hashPassword(plain: string): Promise<string> {
  const saltRounds = 12;  // Configurable, min 10
  return await bcrypt.hash(plain, saltRounds);
}

async function verifyPassword(plain: string, hash: string): Promise<boolean> {
  return await bcrypt.compare(plain, hash);
}

Database Schema (PostgreSQL):

CREATE TABLE users (
  id UUID PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  password_hash VARCHAR(255) NOT NULL  -- Never called "password"
);

Encrypted Secrets (PII)

Generated TypeScript:

import crypto from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'base64');

function encrypt(plaintext: string): string {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);

  let encrypted = cipher.update(plaintext, 'utf8', 'base64');
  encrypted += cipher.final('base64');

  const authTag = cipher.getAuthTag();

  // Format: iv:authTag:ciphertext
  return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`;
}

function decrypt(ciphertext: string): string {
  const [ivB64, authTagB64, encrypted] = ciphertext.split(':');
  const iv = Buffer.from(ivB64, 'base64');
  const authTag = Buffer.from(authTagB64, 'base64');

  const decipher = crypto.createDecipheriv(ALGORITHM, KEY, iv);
  decipher.setAuthTag(authTag);

  let plaintext = decipher.update(encrypted, 'base64', 'utf8');
  plaintext += decipher.final('utf8');

  return plaintext;
}

Tokenized Secrets (Credit Cards)

Generated TypeScript:

import { PaymentTokenService } from './services/payment';

async function tokenizeCard(cardNumber: string): Promise<string> {
  // Integrates with Stripe, Braintree, or custom tokenization service
  const token = await PaymentTokenService.tokenize(cardNumber);
  return token;  // Returns token, never stores card number
}

async function chargeCard(token: string, amount: number): Promise<void> {
  await PaymentTokenService.charge(token, amount);
}

1.5 Redaction in Logs and Errors

Generated Logging:

// Before redaction
logger.info('User created', { 
  id: user.id, 
  email: user.email, 
  password: user.password  // Secret field
});

// After redaction (automatic)
logger.info('User created', { 
  id: user.id, 
  email: user.email, 
  password: '[REDACTED]'  // Automatic
});

Error Messages:

try {
  await createUser(data);
} catch (error) {
  // Automatic secret scrubbing
  logger.error('Failed to create user', redactSecrets(error));
}


2. Policy Enforcement

2.1 Overview

USL enforces a policy-first architecture: no behavior can execute without explicit authorization. Policies are checked at compile-time (policy totality proof) and enforced at runtime (generated middleware).

2.2 Policy Definition

policy CanReadUser(actor: User, target: User) {
  actor.id == target.id ||       // Users can read their own data
  actor.role == "admin" ||       // Admins can read any user
  actor.friends.contains(target) // Friends can read each other
}

Policy Components: - Name: CanReadUser - Parameters: actor (who is acting), target (resource) - Expression: Boolean expression defining authorization

2.3 Behavior Authorization

behavior GetUser(actor: User, id: String) -> User {
  authorize CanReadUser(actor, User.findById(id))
  expose Read[User]
}

Compiler Checks: 1. E301: Behavior must have authorize statement 2. E304: Policy must cover all code paths (totality) 3. Type checker validates policy parameters

2.4 Policy Totality Proof

The compiler proves that all execution paths are authorized:

behavior UpdateUser(actor: User, id: String, data: UserData) {
  let user = User.findById(id)
  authorize CanUpdateUser(actor, user)

  if (data.email.isDefined) {
    user.email = data.email.get  // ✅ Authorized
  }

  if (data.password.isDefined) {
    user.password = data.password.get  // ✅ Authorized
  }

  user.save()  // ✅ All paths authorized
}

Proof Algorithm: 1. Build control-flow graph (CFG) 2. Identify all execution paths 3. Verify each path has authorization 4. Fail compilation if any path is unauthorized

2.5 Generated Runtime Enforcement

Generated Express Middleware:

// Middleware for GetUser endpoint
router.get('/users/:id', authenticate, async (req, res) => {
  const actor = req.user;  // From JWT
  const targetId = req.params.id;

  // Load target user
  const target = await User.findById(targetId);
  if (!target) {
    return res.status(404).json({ error: 'User not found' });
  }

  // Check policy
  if (!CanReadUser(actor, target)) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Execute behavior
  const result = await GetUser(actor, targetId);
  res.json(result);
});

// Policy implementation
function CanReadUser(actor: User, target: User): boolean {
  return (
    actor.id === target.id ||
    actor.role === 'admin' ||
    actor.friends.some(f => f.id === target.id)
  );
}

Defense in Depth: Both compile-time and runtime checks ensure security.

2.6 Complex Policy Examples

Time-Based Policies

policy CanEditPost(user: User, post: Post) {
  user.id == post.authorId &&
  post.createdAt.plusHours(24) > now()  // Can edit within 24 hours
}

Hierarchical Permissions

policy CanAccessResource(user: User, resource: Resource) {
  user.role == "superadmin" ||
  (user.role == "admin" && resource.org == user.org) ||
  (user.role == "member" && resource.owner == user.id)
}

Multi-Factor Policies

policy CanTransferMoney(user: User, amount: Money) {
  user.verified == true &&
  user.mfaEnabled == true &&
  user.recentMfaCheck.plusMinutes(5) > now() &&
  amount < user.dailyLimit
}

3. Escape Validation

3.1 Overview

Escape hatches allow integration with external code, but require explicit adapter registration to establish trust boundaries.

3.2 Escape Declaration

escape ValidateEmail(email: String) -> Bool 
  adapter "email_validator"

escape SendNotification(user: User, message: String) -> Void
  adapter "notification_service"

Compiler Requirement: E902 error if adapter is not registered

3.3 Adapter Registration

File: adapters.yaml

adapters:
  email_validator:
    type: npm
    package: "validator"
    function: "isEmail"

  notification_service:
    type: custom
    path: "./src/adapters/notification.ts"
    function: "sendNotification"

3.4 Generated Code

TypeScript Integration:

import validator from 'validator';
import { sendNotification as customSendNotification } from './adapters/notification';

// Escape function
function ValidateEmail(email: string): boolean {
  return validator.isEmail(email);
}

function SendNotification(user: User, message: string): void {
  customSendNotification(user, message);
}

3.5 Security Considerations

What Escapes Can Do: - ✅ Call external libraries - ✅ Perform I/O operations - ✅ Integrate with third-party services

What Escapes Cannot Do: - ❌ Access secret types (compile-time prevention) - ❌ Bypass policy enforcement - ❌ Violate layer boundaries

User Responsibility: - Audit adapter code for vulnerabilities - Ensure adapters handle errors properly - Keep adapter dependencies updated - Test adapters independently


4. Layer Boundaries

4.1 Overview

USL enforces architectural layers through compile-time checks, ensuring clear separation of concerns.

4.2 Layer Hierarchy

┌─────────────┐
│  API Layer  │  (HTTP, GraphQL, gRPC)
└──────┬──────┘
┌──────▼──────┐
│Service Layer│  (Orchestration, composition)
└──────┬──────┘
┌──────▼──────┐
│Domain Layer │  (Business logic, pure functions)
└──────┬──────┘
┌──────▼──────┐
│Query Layer  │  (Database reads, caching)
└─────────────┘

4.3 Layer Rules

Layer Can Call Cannot Call I/O Allowed
API Service, Query Domain Yes
Service Domain, Query - Limited
Domain - Service, Query, API No
Query - Service, Domain, API Read-only

4.4 Compiler Enforcement

E601: Domain Layer I/O

domain behavior CalculateTotal(items: List<Item>) -> Money {
  let result = Database.query("SELECT SUM(price) FROM items")  // ❌ E601
  return result
}

Fix: Move database access to query layer

query GetItemsTotal() -> Money {
  return Database.query("SELECT SUM(price) FROM items")  // ✅ OK
}

domain behavior CalculateTotal(items: List<Item>) -> Money {
  return items.sum(item => item.price)  // ✅ Pure function
}

E602: Service Direct DB Access

service behavior CreateOrder(user: User, items: List<Item>) {
  Database.insert("INSERT INTO orders ...")  // ❌ E602
}

Fix: Use query layer

query CreateOrderRecord(order: Order) -> Order {
  return Database.insert(order)  // ✅ OK
}

service behavior CreateOrder(user: User, items: List<Item>) {
  let order = Order.new(user, items)
  CreateOrderRecord(order)  // ✅ OK
}

E651: Query Layer Writes

query UpdateUserEmail(userId: String, email: String) {
  Database.update("UPDATE users SET email = $1 WHERE id = $2", [email, userId])  // ❌ E651
}

Fix: Query layer is read-only; use service layer for writes

service behavior UpdateUserEmail(userId: String, email: String) {
  authorize CanUpdateUser(currentUser(), User.findById(userId))
  Database.update("UPDATE users SET email = $1 WHERE id = $2", [email, userId])  // ✅ OK
}

4.5 Benefits

  • Testability: Domain layer is pure (easy to test)
  • Maintainability: Clear separation of concerns
  • Security: Enforced read-only query layer
  • Performance: Caching at query layer

5. Generated Code Security

5.1 SQL Injection Prevention

All queries are parameterized:

query FindUserByEmail(email: String) -> User? {
  return Database.query("SELECT * FROM users WHERE email = $1", [email])
}

Generated SQL (PostgreSQL):

async function FindUserByEmail(email: string): Promise<User | null> {
  const result = await db.query(
    'SELECT * FROM users WHERE email = $1',
    [email]  // ✅ Parameterized
  );
  return result.rows[0] || null;
}

Never Generated:

// ❌ NEVER: String concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`;

5.2 XSS Prevention

Content-Type Headers:

app.use((req, res, next) => {
  res.setHeader('Content-Type', 'application/json');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  next();
});

CSP Headers:

app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', "default-src 'self'");
  next();
});

5.3 Authentication

JWT Validation:

import jwt from 'jsonwebtoken';

function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!);
    req.user = payload;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

5.4 CSRF Protection

Token Validation:

import csurf from 'csurf';

const csrfProtection = csurf({ cookie: true });

app.use(csrfProtection);

app.post('/api/users', (req, res) => {
  // CSRF token validated automatically
  // ...
});

5.5 Rate Limiting

Configurable Rate Limits:

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100  // Max 100 requests per window
});

app.use('/api/', limiter);


6. Effect System

6.1 Overview

USL tracks side effects through the type system, making I/O operations explicit.

6.2 Effect Types

behavior PureCalculation(x: Int) -> Int {
  return x * 2  // No effects
}

behavior ReadDatabase(id: String) -> User effect Read {
  return Database.query(...)  // Read effect
}

behavior WriteDatabase(user: User) effect Write {
  Database.insert(user)  // Write effect
}

behavior SendEmail(user: User) effect IO {
  EmailService.send(...)  // I/O effect
}

6.3 Effect Composition

behavior CreateAndNotifyUser(data: UserData) effect Write, IO {
  let user = CreateUser(data)      // Write effect
  SendWelcomeEmail(user)           // IO effect
  return user
}

Compiler Check: Effect signature must include all transitive effects


7. Proof Engines

7.1 Policy Totality

Proves: All code paths are authorized

Algorithm: Symbolic execution + SMT solving

Example:

behavior UpdateUser(actor: User, target: User, data: UserData) {
  authorize CanUpdateUser(actor, target)

  if (data.email.isDefined) {
    target.email = data.email.get
  }

  if (data.password.isDefined) {
    target.password = data.password.get
  }
}

// Proof: All paths (4 total) have authorization
// Path 1: email=true, password=true → authorized
// Path 2: email=true, password=false → authorized
// Path 3: email=false, password=true → authorized
// Path 4: email=false, password=false → authorized

7.2 Invariants

Proves: Data constraints always hold

entity BankAccount {
  balance: Money

  invariant BalanceNonNegative {
    balance >= 0
  }
}

behavior Withdraw(account: BankAccount, amount: Money) {
  if (account.balance >= amount) {
    account.balance = account.balance - amount
  } else {
    throw "Insufficient funds"
  }
}

// Proof: Invariant holds after withdraw
// If (balance >= amount): new_balance = balance - amount >= 0 ✓
// Else: throws error (balance unchanged) ✓

7.3 Capability Safety

Proves: Resources accessed with proper capabilities

capability FileRead(path: String)
capability FileWrite(path: String)

behavior ReadFile(path: String) -> String 
  requires FileRead(path) {
  return FileSystem.read(path)
}

8. Trust Boundaries

8.1 Import Trust Policies

import AuthModule from "github.com/acme/auth" 
  trust "verified"

import BetaFeature from "./experimental/feature"
  trust "internal"

Trust Levels: - verified: Audited third-party code - internal: Team-developed code - external: Unaudited external code (warnings)

8.2 Export Restrictions

export entity User {
  expose [id, email]  // Only these fields exported
  hide [password, ssn]  // Never exported
}

Summary

USL's security features provide defense-in-depth:

  1. Secret Types: Compile-time + runtime protection
  2. Policy Enforcement: Totality proofs + runtime checks
  3. Escape Validation: Explicit trust boundaries
  4. Layer Boundaries: Architectural security
  5. Generated Code: Secure by default (parameterized queries, etc.)
  6. Effect System: Explicit side effects
  7. Proof Engines: Formal verification
  8. Trust Boundaries: Import/export control

Result: Security vulnerabilities caught at compile-time, minimizing runtime risk.


For questions, contact: security@usl-lang.org