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
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:
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:
- Secret Types: Compile-time + runtime protection
- Policy Enforcement: Totality proofs + runtime checks
- Escape Validation: Explicit trust boundaries
- Layer Boundaries: Architectural security
- Generated Code: Secure by default (parameterized queries, etc.)
- Effect System: Explicit side effects
- Proof Engines: Formal verification
- Trust Boundaries: Import/export control
Result: Security vulnerabilities caught at compile-time, minimizing runtime risk.
For questions, contact: security@usl-lang.org