Security

Adama approaches security differently than most systems. Rather than bolting security onto an API layer and hoping nothing slips through, Adama embeds privacy and access control directly into the language. The compiler enforces security policies before your code runs. I think this is one of the most important design decisions in the whole system -- and the one I'm most proud of.

This chapter covers authentication mechanisms, privacy enforcement, policy best practices, and secure configuration patterns.

Security Architecture

Adama's security model operates at multiple layers:

Layer Mechanism Enforcement
Document creation Static policies Compile time
Connection @connected handler Runtime
Record visibility require policies Runtime
Field visibility Privacy modifiers Compile time
Message handling Channel guards Runtime
External access Web handlers Runtime

Each layer provides defense in depth. An attacker would have to bypass multiple controls to access unauthorized data. That's the point.

Authentication Mechanisms

Authentication in Adama establishes who is connecting to a document. The system supports multiple patterns.

The Principal Type

Every connected user is represented by a principal -- a typed identity that cannot be forged:

private principal document_owner;

@connected {
  // @who is the connecting principal
  return @who == document_owner;
}

Principals come from the authentication layer. The Adama runtime validates principals before they reach your code, so by the time you see @who, you know it's legit.

Anonymous Access

Anonymous users connect with a special anonymous principal:

@connected {
  if (@who.isAnonymous()) {
    // Allow read-only anonymous access
    return true;
  }
  // Require authentication for full access
  return @who != @no_one;
}

Anonymous access is useful for public-facing features -- read-only dashboards, game spectating, that sort of thing.

Adama Developer Authentication

Users authenticated through the Adama platform have verified identities:

@connected {
  if (@who.isAdamaDeveloper()) {
    // Trusted platform user
    return true;
  }
  return false;
}

Custom Authentication

For integrating with external identity providers, use authorization channels:

message AuthRequest {
  string email;
}

@authorization (AuthRequest req) {
  // Look up user and validate credentials
  return {
    agent: req.email,
    hash: "password_hash_here"
  };
}

The authorization handler receives a message with credentials and returns a response containing an agent identifier and a password hash for validation.

Compile-Time Privacy Enforcement

This is where things get interesting. Adama's most powerful security feature is compile-time privacy checking. The compiler prevents you from accidentally exposing private data. Not at runtime. Not in testing. At compile time.

Privacy Modifiers

Every field must have a privacy modifier:

private principal owner;

policy can_see {
  return @who == owner;
}

private int internal_state;              // Never exposed
public int shared_state;                  // Visible to all viewers
viewer_is<owner> int personal_data;       // Visible only to owner
use_policy<can_see> int controlled_data;  // Visibility determined by policy

Compile-Time Guarantees

The compiler enforces privacy rules:

private int secret = 42;

// ERROR: Cannot assign private value to public field
public int exposed = secret;  // Compiler rejects this

// OK: Formula can compute from private data
public formula safe = secret > 0;  // Boolean result doesn't leak value

This means privacy bugs are caught during development, not discovered in production when someone reports that they can see data they shouldn't. I cannot overstate how much better this is than the traditional approach.

Data Flow Analysis

The compiler tracks data flow to prevent leaks:

message UpdateMsg {
  string newDisplay;
}

private string password;
public string display;

channel updateDisplay(UpdateMsg msg) {
  // ERROR: Cannot assign private data to public field
  display = password;  // Compiler rejects this

  // OK: Assign message data
  display = msg.newDisplay;
}

Policy Best Practices

Policies control access to documents, records, and fields. Well-designed policies are the foundation of a secure application.

Document-Level Policies

Control who can create and access documents:

@static {
  // Who can explicitly create documents
  create {
    // Require authenticated user
    return @who != @no_one;
  }

  // Who can create documents by connecting to non-existent key
  invent {
    // Only allow invention for specific patterns
    return @context.key.startsWith("public-");
  }
}

Connection Policies

The @connected handler is your first line of defense:

private principal owner;

record MemberRecord {
  public principal who;
}

table<MemberRecord> members;

@connected {
  // Owner always allowed
  if (@who == owner) {
    return true;
  }

  // Members allowed
  if ((iterate members where who == @who).size() > 0) {
    return true;
  }

  // Everyone else rejected
  return false;
}

Returning false from @connected prevents the user from seeing any document data. Full stop.

Record-Level Policies

Use require to hide entire records:

record PrivateNote {
  private principal author;
  public string content;

  policy is_author {
    return @who == author;
  }

  require is_author;  // Record invisible to non-authors
}

table<PrivateNote> notes;

Users can't see records they aren't authorized to view -- not even their existence. The record simply doesn't appear in their view of the data.

Field-Level Policies

Apply policies to specific fields:

record AdminRecord {
  public principal who;
}

table<AdminRecord> admins;

record UserProfile {
  public string name;
  use_policy<is_self> string email;
  use_policy<is_admin> string internal_notes;

  private principal user_id;

  policy is_self {
    return @who == user_id;
  }

  policy is_admin {
    return (iterate admins where who == @who).size() > 0;
  }
}

Policy Composition

Combine policies for complex rules:

record BannedUser {
  public principal who;
}

table<BannedUser> banned_users;
public int permission_level;

use_policy<is_authenticated, is_not_banned, has_permission> int sensitive;

policy is_authenticated {
  return @who != @no_one;
}

policy is_not_banned {
  return (iterate banned_users where who == @who).size() == 0;
}

policy has_permission {
  return permission_level >= 3;
}

The field is visible only if ALL policies return true. Chain as many as you need.

Channel Security

Channels are how clients send messages to documents. Secure them carefully -- they're your input surface.

Channel Guards

Use guards to restrict who can send to a channel:

message AdminRequest {
  string action;
}

record AdminRecord {
  public principal who;
}

table<AdminRecord> admins;

channel adminAction(AdminRequest req) {
  if ((iterate admins where who == @who).size() == 0) {
    return;  // Silently ignore non-admin requests
  }
  // Process admin action
}

Message Validation

Validate message content before processing. Trust nothing from the client.

message Transfer {
  int amount;
  principal recipient;
}

record UserRecord {
  public principal who;
}

table<UserRecord> users;
int MAX_TRANSFER = 10000;

procedure processTransfer(principal from, principal to, int amount) {
  // transfer logic
}

channel transfer(Transfer msg) {
  // Validate amount
  if (msg.amount <= 0 || msg.amount > MAX_TRANSFER) {
    return;  // Invalid amount
  }

  // Validate recipient exists
  if ((iterate users where who == msg.recipient).size() == 0) {
    return;  // Unknown recipient
  }

  // Safe to process
  processTransfer(@who, msg.recipient, msg.amount);
}

Rate Limiting

Protect against abuse by tracking message frequency:

message ActionMsg {
  string action;
}

record RateLimit {
  private principal user;
  private datetime last_action;
  private int action_count;
}

table<RateLimit> rate_limits;

channel sensitiveAction(ActionMsg msg) {
  // Find or create rate limit record
  list<RateLimit> limit = iterate rate_limits where user == @who;

  if (limit.size() > 0) {
    if (limit[0] as record) {
      timespan elapsed = record.last_action.between(Time.datetime());
      double elapsedSec = elapsed.seconds();

      if (elapsedSec < 60.0 && record.action_count >= 10) {
        return;  // Rate limited
      }
    }

    if (limit[0] as record) {
      timespan elapsed = record.last_action.between(Time.datetime());
      if (elapsed.seconds() >= 60.0) {
        limit.action_count = 1;
      } else {
        limit.action_count = record.action_count + 1;
      }
    }
    limit.last_action = Time.datetime();
  } else {
    rate_limits <- { user: @who, last_action: Time.datetime(), action_count: 1 };
  }

  // Process action
}

Web Endpoint Security

HTTP endpoints need explicit security configuration.

Authentication in Web Handlers

Web handlers receive authentication context:

record ProfileRecord {
  public principal owner;
  public string name;
}

table<ProfileRecord> profiles;

@web get /api/profile {
  if (@who == @no_one) {
    return {
      json: { error: "Authentication required" },
      status: 401
    };
  }

  if ((iterate profiles where owner == @who)[0] as found) {
    return {
      json: { name: found.name },
      status: 200
    };
  }
  return {
    json: { error: "Not found" },
    status: 404
  };
}

Input Validation

Always validate request input:

message UpdateSettings {
  string theme;
}

record Settings {
  public string theme;
}

Settings settings;

@web put /api/settings (UpdateSettings body) {
  // Validate required fields
  if (body.theme == "") {
    return { json: { error: "Theme required" }, status: 400 };
  }

  // Validate allowed values
  if (body.theme != "light" && body.theme != "dark") {
    return { json: { error: "Invalid theme" }, status: 400 };
  }

  // Safe to apply
  settings.theme = body.theme;
  return { json: { success: true }, status: 200 };
}

CORS Configuration

Control cross-origin access:

@web options /api {
  return {cors: true};
}

Secure Configuration

Protect your deployment configuration.

Secrets Management

Never hardcode secrets in Adama code. I know this seems obvious, but I'm saying it anyway:

// WRONG - Secret in code
private string api_key = "sk_live_abc123";

// RIGHT - Reference configured service
service payment {
  class = "http";
  // Credentials configured externally
}

Configure secrets through the service layer or environment:

java -jar adama.jar service config set --space myapp --name payment --key apikey --value $API_KEY

Configuration Security

Protect configuration files:

# Restrict configuration file permissions
chmod 600 ~/.adama
chmod 600 config/production.json

# Use environment variables for sensitive values
export ADAMA_CONFIG=/secure/path/config.json

Network Security

Secure network configuration:

  1. Use TLS: Always terminate TLS at the load balancer or reverse proxy
  2. Restrict ports: Only expose necessary ports
  3. Firewall rules: Limit source IPs where possible
  4. Internal networks: Keep Adama on private networks when possible
# Example nginx TLS termination
server {
    listen 443 ssl;
    ssl_certificate /etc/ssl/certs/myapp.crt;
    ssl_certificate_key /etc/ssl/private/myapp.key;

    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Common Security Patterns

Owner-Based Access

Documents owned by a specific user:

private principal owner;

@construct {
  owner = @who;
}

@connected {
  return @who == owner;
}

Role-Based Access

Multiple roles with different permissions:

enum Role { Admin, Editor, Viewer }

record Member {
  private principal user;
  private Role role;
}

table<Member> members;

policy is_admin {
  return (iterate members where user == @who && role == Role::Admin).size() > 0;
}

policy is_editor {
  return (iterate members where user == @who && (role == Role::Admin || role == Role::Editor)).size() > 0;
}

policy is_viewer {
  return (iterate members where user == @who).size() > 0;
}

Shared with Specific Users

Documents shared with an explicit list:

private principal owner;

record SharedUser {
  public principal who;
}

table<SharedUser> shared_with;

message ShareMsg {
  principal user;
}

@connected {
  if (@who == owner) return true;
  return (iterate shared_with where who == @who).size() > 0;
}

channel share(ShareMsg msg) {
  if (@who != owner) return;  // Only owner can share
  shared_with <- { who: msg.user };
}

Time-Based Access

Access that expires:

private datetime access_expires;

@connected {
  if (Time.datetime() > access_expires) {
    return false;  // Access expired
  }
  return true;
}

Audit Logging

Track security-relevant events:

message ActionMsg {
  string action;
}

record AuditLog {
  private datetime timestamp;
  private principal actor;
  private string action;
  private string details;
}

table<AuditLog> audit_log;

procedure logAction(principal who, string action, string details) {
  audit_log <- {
    timestamp: Time.datetime(),
    actor: who,
    action: action,
    details: details
  };
}

channel sensitiveAction(ActionMsg msg) {
  logAction(@who, "sensitive_action", "Performed by " + @who.agent());
  // ... perform action
}

Security Checklist

Before deploying to production:

Code Review

  • [ ] All fields have appropriate privacy modifiers
  • [ ] @connected handler restricts access appropriately
  • [ ] Static create/invent policies are configured
  • [ ] Channels validate input before processing
  • [ ] No secrets hardcoded in Adama code

Configuration

  • [ ] TLS enabled in production
  • [ ] Configuration files have restricted permissions
  • [ ] Secrets stored securely, not in version control
  • [ ] Unnecessary ports blocked by firewall

Testing

  • [ ] Privacy policies tested with multiple users
  • [ ] Unauthorized access attempts fail appropriately
  • [ ] Edge cases in policies verified

Monitoring

  • [ ] Audit logging enabled for sensitive actions
  • [ ] Alerting configured for authentication failures
  • [ ] Rate limiting configured for sensitive operations

At the end of the day, Adama's approach means security isn't an afterthought -- it's a property of your application that the compiler helps you maintain. The compiler catches accidental data exposure. The privacy system enforces who sees what. Combined with careful configuration and monitoring, this gets you to a strong security posture with less effort than the traditional "bolt it on and pray" approach.

Previous Monitoring
Next Patterns