Policy Reference

Lexega policies control what happens when signals fire. Policies don't define detection logic—that's handled by builtin rules and custom rules. Policies only define enforcement: block, warn, or allow.

Quick Start

Run lexega-sql init to generate a baseline policy:

lexega-sql init

This creates:

  • .lexega/policy.yml — Permissive baseline (warns on critical/high, nothing blocked)
  • .lexega/exceptions.yml — Scaffold for approved overrides
  • .lexega/baseline.sarif — SARIF evidence snapshot

The default policy is permissive so you can see what signals exist before enabling blocking. To tighten, edit severity_actions and change warn to block.

Policy Structure

# .lexega/policy.yml
schema_version: 1
policy_id: my-org-policy
policy_version: "1.0.0"

# Explicit rule-based policies (highest priority)
policies:
  - rule_id: DML-WRITE-UNBOUNDED
    action: allow          # Override: allow this specific rule
    envs: ["dev"]          # Only in dev environment
    
  - rule_id: DIFF-WRITE-WHERE-RMV
    action: block
    description: "Block WHERE clause removals in prod"

# Severity-based fallback (checked after explicit policies)
severity_actions:
  - critical: block
    high: warn
    
# Final fallback for unmatched signals
default_action: allow

Evaluation Order

For each signal, Lexega evaluates in order:

  1. Explicit policies — Match by rule_id
  2. Severity actions — Match by signal severity level
  3. Default action — Catch-all fallback

First match wins. This layering lets you:

  • Set broad severity-based defaults
  • Override specific rules as needed
  • Never enumerate every possible rule

Severity Actions

severity_actions catches signals by severity level without requiring you to know every rule ID:

severity_actions:
  - critical: block    # Block all critical signals
    high: warn         # Warn on high signals
    medium: allow      # Explicitly allow medium
    # low/info: not specified → falls through to default_action

Scoped Severity Actions

You can scope severity actions to specific paths, environments, or other constraints:

severity_actions:
  # Lenient for tests (first match wins)
  - scope:
      path_patterns: ["**/tests/**"]
    critical: warn       # Only warn in tests
    
  # Lenient for dev environment
  - scope:
      envs: ["dev"]
    critical: warn
    high: allow
    
  # Strict default (no scope = global fallback)
  - critical: block
    high: warn

Available scope constraints:

  • envs — Environment names (case-insensitive)
  • path_patterns — Glob patterns for file paths (e.g., **/tests/**)
  • paths — Exact file paths
  • path_prefixes — Path prefixes
  • repos / repo_prefixes — Repository names
  • roles — Snowflake execution roles (detected from USE ROLE in SQL)
  • expires_at — Expiration timestamp (UTC)

Explicit Policies

For fine-grained control over specific rules:

policies:
  - rule_id: DML-WRITE-UNBOUNDED              # Unbounded DELETE
    action: block
    description: "Never allow unbounded deletes"
    
  - rule_id: Q-JOIN-LEFT-FILT                # LEFT JOIN nullable filter
    action: warn
    envs: ["prod", "staging"]    # Only in these environments
    
  - rule_id: C050                # Encryption disabled
    action: block
    requires_exception: true     # Must have exception to proceed

Policy Fields

FieldRequiredDescription
rule_idRule ID to match (e.g., DML-WRITE-UNBOUNDED, DIFF-WRITE-WHERE-RMV)
actionblock, warn, or allow
descriptionHuman-readable explanation
envsEnvironment filter (e.g., ["prod"])
severity_overrideOverride the rule's default severity
message_overrideOverride the rule's message
requires_exceptionIf true and blocking, exception is required

Exceptions

Exceptions grant temporary or scoped overrides to blocking policies:

# .lexega/exceptions.yml
schema_version: 1
exceptions:
  - exception_id: EXC-001
    policy_id: my-org-policy
    rule_id: DML-WRITE-UNBOUNDED
    scope:
      scoped:                                    # Required wrapper for scoped exceptions
        paths: ["migrations/legacy_cleanup.sql"]
        expires_at: "2026-06-01T00:00:00Z"
    reason: "Approved legacy cleanup - JIRA-1234"
    approved_by: "security-team"
    approved_at: "2026-01-15T12:00:00Z"         # Required timestamp
    ticket: "JIRA-1234"                         # Required ticket reference

For blanket exceptions (use with caution):

exceptions:
  - exception_id: EXC-002
    policy_id: my-org-policy
    rule_id: TBL-RAP-ADD
    scope:
      global:                                    # Explicit global scope
        expires_at: "2026-12-31T23:59:59Z"      # Strongly recommended for global
    reason: "Informational signal, not actionable"
    approved_by: "security-team"
    approved_at: "2026-01-15T12:00:00Z"
    ticket: "SEC-5678"

Exceptions are matched against the signal's context (path, env, etc.) and recorded in the decision artifact for audit.

Decision Artifacts

When using --decision-out, Lexega writes a JSON decision record:

lexega-sql analyze file.sql \
  --policy policy.yml \
  --env prod \
  --decision-out decisions/$GITHUB_RUN_ID/

Tip: When writing to a long-lived directory or cloud storage prefix, include a unique run identifier (like $GITHUB_RUN_ID) so new runs don’t overwrite older decision/report artifacts.

The decision record includes:

  • Input hashes (SQL, policy, exceptions)
  • All matched rules and their actions
  • Exceptions used
  • Final outcome (allowed/blocked)
  • Timestamps for audit

Environment Context

Pass environment context via --env:

lexega-sql analyze file.sql --policy policy.yml --env prod

Flags for Scoping

These flags affect policy/exception matching:

FlagDescriptionCan scope by
--envEnvironment name (required with --policy)envs in policies/exceptions
--repoRepository namerepos, repo_prefixes

File paths are automatically captured and matched against paths, path_prefixes, and path_patterns.

Flags for Audit Trail Only

These flags are recorded in decision artifacts but cannot be used for scoping:

FlagDescription
--teamTeam identifier
--change-idCI build/change ID
--commitCommit SHA
--job-typeJob type (ad_hoc, scheduled)

These appear in the decision artifact's env_context for audit purposes.

CI Integration

When a policy blocks execution, the CLI exits with code 2:

lexega-sql analyze file.sql \
  --policy policy.yml \
  --env prod \
  --decision-out decisions/

# Exit codes:
# 0 = allowed
# 1 = error  
# 2 = blocked by policy

In CI environments, set LEXEGA_CI=1 to require policy evaluation (prevents accidentally skipping policy checks):

export LEXEGA_CI=1
# Now --policy is mandatory; without it, the CLI will exit with an error
lexega-sql analyze file.sql --policy policy.yml --env prod

Schema Validation

Add schema references for IDE support:

# yaml-language-server: $schema=https://lexega.com/schemas/v1/policy.schema.json
schema_version: 1
policy_id: my-policy
# ...

Available schemas:

  • policy.schema.json — PolicyBundle
  • exceptions.schema.json — ExceptionBundle
  • decision.schema.json — DecisionRecord
  • custom_rules.schema.json — CustomRuleSet

Need Help?

Can't find what you're looking for? Check out our GitHub or reach out to support.