Add comprehensive documentation and GitHub Copilot configuration: - docs/knowledge/: 17 deep-dive knowledge files covering value semantics, RVM architecture, builtins, FFI boundary, feature composition, error handling migration, policy evaluation security, Rego semantics, interpreter/compiler architecture, Azure Policy/RBAC, engine API, time builtins, language extension guide, tooling architecture, causality/partial eval, Rego compiler, Azure Policy aliases, and telemetry/diagnostics - .github/agents/: 16 role-specific AI agent definitions (red-teamer, semantics-expert, architect, performance-engineer, test-engineer, verification-engineer, security-auditor, reliability-engineer, support-engineer, ci-engineer, refactorer, api-steward, program-manager, demo-engineer, dx-engineer, tech-lead) - .github/skills/: 6 workflow skill definitions (thorough-review, design-alternatives, add-builtin, opa-conformance, security-review, verification) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
7.3 KiB
Knowledge: Policy Evaluation Security
Deep knowledge about security properties, DoS protection, resource limits, and input validation in regorus. Read this before modifying evaluation paths, parsers, or resource management.
Threat Model
Regorus evaluates untrusted policy code against untrusted data. Both may be adversarial. The engine must:
- Always terminate — no infinite loops, no unbounded recursion
- Bound resource usage — memory, CPU time, instruction count
- Return correct results — a wrong result is a security vulnerability
- Never crash — panics in daemon mode crash the service
- Not leak information — error messages must not expose sensitive data
Resource Limit Enforcement
Instruction Budget (RVM)
The primary defense against computation-based DoS:
- Default: 25,000 instructions (
src/rvm/vm/machine.rs) - Enforcement: checked every iteration in the execution loop
- Error:
VmError::InstructionLimitExceeded - Configurable:
set_max_instructions(limit)
Execution Time Limits
Wall-clock enforcement via ExecutionTimer (src/utils/limits/time.rs):
- Cooperative checking — the timer is checked periodically, not preemptively
- Amortized overhead — accumulates work units before reading the clock to avoid syscall overhead
- Suspended time excluded —
resume_from_elapsed()preserves elapsed time across VM suspensions, so only active computation counts - Per-instance override — each VM can set its own timer config
- Error:
VmError::TimeLimitExceeded
Memory Limits
Global memory tracking via src/utils/limits/memory.rs:
- Global atomic limit —
GLOBAL_MEMORY_LIMIT: AtomicU64 - Throttled checking — dual strategy to avoid contention:
- Stride-based: check every 16 iterations
- Delta-based: check when 32 KiB has been allocated since last check
- Per-thread flushing — auto-flush at 1 MiB threshold
- Enforcement points: Value construction, deserialization, parsing
- Error:
VmError::MemoryLimitExceeded
The allocator-memory-limits feature uses mimalloc to enforce at the allocator
level.
Input Validation
Policy Source (src/lexer.rs)
Rego source is validated during lexing with configurable limits:
| Limit | Default | Purpose |
|---|---|---|
max_col |
1,024 chars | Lines exceeding this are likely minified/attack code |
max_file_bytes |
1 MiB | Prevents memory exhaustion from huge files |
max_lines |
20,000 | Prevents excessive parsing time |
Memory limit is also checked after each logical chunk during lexing.
Parser Depth
The parser enforces expression nesting depth:
- Default:
MAX_EXPR_DEPTH = 32(src/parser.rs) - Prevents stack overflow from deeply nested expressions like
(((((...))))) - Returns error, not panic
JSON/YAML Data
Data added via add_data() must be an object (checked by engine.rs).
Value construction during deserialization checks memory limits at each node.
RVM Programs
Compiled programs validated by validate_limits() (src/rvm/program/core.rs):
| Resource | Limit |
|---|---|
| Instructions | 65,535 |
| Literals | 65,535 |
| Rules | 4,000 |
| Entry points | 1,000 |
| Source files | 256 |
| Builtins | 512 |
| Path depth | 32 |
These prevent adversarial serialized programs from consuming excessive resources during deserialization or execution.
Recursion Protection
- Parser:
MAX_EXPR_DEPTH = 32for expression nesting - RVM:
MAX_PATH_DEPTH = 32for rule path depth - Virtual documents:
needs_runtime_recursion_checkflag enables detection whenVirtualDataDocumentLookupinstructions are present - Rule evaluation: processed rules tracked in
self.processedset to prevent re-evaluation cycles
DoS via Regular Expressions
Regorus uses the regex crate which compiles to a DFA — no catastrophic
backtracking. Protection is layered:
- DFA-based regex engine (no exponential blowup)
- Instruction budget limits total work
- Execution time limits bound wall-clock
- LRU cache prevents repeated compilation (256 patterns, hard cap 2^16)
Undefined vs False
This is a security-critical distinction. In policy evaluation:
allow { input.role == "admin" }
If input.role is missing:
input.role == "admin"→Undefined(notfalse)allow→Undefined(rule body didn't succeed)not allow→true(becausenot Undefined = true)
A bug that treats Undefined as false (or vice versa) can change policy
decisions. Every evaluation path must handle the three-valued logic correctly.
See docs/knowledge/value-semantics.md for detailed Undefined propagation rules.
Supply Chain Security
Dependency Auditing
The dependency-audit.yml workflow runs:
- cargo-audit: checks 6 Cargo.lock files (main + 5 bindings) against RustSec advisories
- cargo-deny: checks 9 manifests for CVEs (advisories) and problematic dependencies (bans)
- Schedule: PRs, main pushes, weekly (Mondays 6 AM), manual dispatch
Dependency Management
- Pinned action SHAs: all GitHub Actions references use full commit SHAs, not mutable tags — prevents supply chain attacks via tag mutation
- Locked dependencies:
Cargo.lockcommitted,cargo fetch --locked/--frozenin CI ensures reproducible builds - Dependabot: automated weekly updates for Cargo, GitHub Actions, Maven, NuGet, pip, npm, bundler, Go
- Minimal dependency surface: prefer
core/allocover external crates
Spectre Mitigation
On Windows (MSVC), the optional msvc_spectre_libs dependency links with
Spectre-mitigated CRT and libraries.
Panic Safety
The 80+ deny lints in src/lib.rs exist not just for style — they prevent
panics at compile time:
| Denied | Why |
|---|---|
clippy::unwrap_used |
.unwrap() panics on None/Err |
clippy::expect_used |
.expect() panics on None/Err |
clippy::indexing_slicing |
vec[i] panics on out-of-bounds |
clippy::arithmetic_side_effects |
a + b can overflow and panic |
clippy::panic |
Explicit panic!() |
clippy::unreachable |
Explicit unreachable!() |
clippy::todo |
Explicit todo!() |
In daemon mode, any panic is a service crash. The deny lints are the first
line of defense. The FFI layer's with_unwind_guard() is the second — it
catches panics and poisons the engine (see docs/knowledge/ffi-boundary.md).
But panic containment is a last resort. The goal is zero panics in all code paths, including error paths, resource exhaustion, and adversarial input.
Security Review Checklist
When reviewing code for security:
- Undefined handling — does the code correctly distinguish Undefined from false?
- Resource limits — does new code respect instruction budget, time, memory?
- Input validation — is untrusted input validated before use?
- Panic paths — can any code path panic (overflow, indexing, unwrap)?
- Error messages — do errors avoid leaking policy content or data?
- Recursion — is recursion bounded?
- Allocation — can adversarial input cause unbounded allocation?
- Cache behavior — can cache be poisoned or exhausted?