Chapter 21: Governance and LLM Code Quality
May 19, 2026 · View on GitHub
NAAb includes a built-in governance engine that enforces project-level policies on polyglot code blocks. By placing a govern.json file in your project directory, you can control which languages are allowed, what APIs can be called, enforce code quality standards, and — uniquely — detect common LLM code generation failures like oversimplified stubs, incomplete error handling, and hallucinated APIs.
21.1 What is Governance?
Governance is policy-as-code for your polyglot blocks. Every polyglot block (<<python ... >>, <<javascript ... >>, etc.) is checked against your governance rules before execution. If a rule is violated, execution is blocked or a warning is emitted, depending on the enforcement level.
21.1.1 Three Enforcement Levels
NAAb uses a three-tier enforcement model inspired by HashiCorp Sentinel:
| Level | Behavior | Override |
|---|---|---|
| HARD | Block execution. Cannot be overridden. | None |
| SOFT | Block execution. Can be overridden with --governance-override. | --governance-override |
| ADVISORY | Warn only. Execution continues. | N/A (always continues) |
21.1.2 Three Governance Modes
| Mode | Behavior |
|---|---|
| enforce | Normal enforcement (default). Rules are checked and enforced. |
| audit | Dry-run. All rules are checked but nothing is blocked. Useful for testing new rules. |
| off | Governance disabled entirely. |
21.1.3 Zero Overhead
When no govern.json file exists, the governance engine is completely inactive. There is no performance impact on programs that don't use governance.
21.2 Quick Start
Step 1: Create govern.json
Place a govern.json file in the same directory as your .naab file (or any parent directory):
{
"version": "5.0",
"mode": "enforce",
"languages": {
"allowed": ["python", "javascript"]
},
"code_quality": {
"no_secrets": { "level": "hard" },
"no_oversimplification": { "level": "soft" },
"no_incomplete_logic": { "level": "soft" },
"no_hallucinated_apis": { "level": "advisory" }
}
}
Step 2: Run Your Program
naab-lang my_program.naab
If governance rules are loaded, you'll see:
[governance] Loaded: /path/to/govern.json (mode: enforce)
Step 3: Read the Summary
After execution, a summary is printed showing which checks passed, which were warned, and which were blocked:
=== Governance Summary ===
PASS code_quality.no_secrets [HARD]
PASS code_quality.no_oversimplification [SOFT]
WARN code_quality.no_hallucinated_apis [ADVISORY]
".push() is JavaScript — in Python, use .append()"
21.3 The govern.json Reference
The governance configuration file has 13 sections. All sections are optional — only configure what you need.
21.3.1 Top-Level Fields
{
"version": "5.0",
"mode": "enforce",
"description": "My project governance rules"
}
| Field | Type | Description |
|---|---|---|
version | string | Config version. Use "3.0". |
mode | string | "enforce", "audit", or "off". |
description | string | Human-readable description. |
21.3.2 Languages
Control which languages can be used and set per-language rules:
{
"languages": {
"allowed": ["python", "javascript", "rust"],
"blocked": ["shell"],
"per_language": {
"python": {
"timeout": 15,
"max_lines": 100,
"banned_functions": ["eval(", "exec(", "compile("],
"imports": {
"mode": "blocklist",
"blocked": ["subprocess", "ctypes", "os.system"]
}
},
"javascript": {
"timeout": 10,
"banned_functions": ["eval(", "Function("],
"imports": {
"mode": "blocklist",
"blocked": ["child_process", "fs"]
}
}
}
}
}
21.3.3 Capabilities
Control what polyglot blocks can access:
{
"capabilities": {
"network": { "enabled": false },
"filesystem": { "mode": "read" },
"shell": { "enabled": false }
}
}
Capabilities also support the legacy flat format: "network": false.
21.3.4 Limits
Set resource limits for execution:
{
"limits": {
"timeout": {
"global": 30,
"per_block": 15
},
"execution": {
"call_depth": 50,
"polyglot_blocks": 20
},
"data": {
"array_size": 10000,
"string_length": 100000
},
"code": {
"max_lines_per_block": 200,
"max_nesting_depth": 8
}
}
}
21.3.5 Requirements
Enforce structural requirements:
{
"requirements": {
"main_block": {
"level": "soft",
"message": "Programs should have a main block"
}
}
}
21.3.6 Restrictions
Set security restrictions for dangerous operations:
{
"restrictions": {
"dangerous_calls": { "level": "hard" },
"shell_injection": { "level": "hard" },
"privilege_escalation": { "level": "hard" },
"imports": {
"level": "soft",
"mode": "blocklist",
"blocked": {
"python": ["subprocess", "ctypes", "pickle"],
"javascript": ["child_process", "fs", "net"]
}
}
}
}
21.3.7 Code Quality
The largest section. Each check can be set as a simple string ("hard", "soft", "advisory") or as an object with detailed configuration:
{
"code_quality": {
"no_secrets": { "level": "hard" },
"no_placeholders": "soft",
"no_simulation_markers": { "level": "hard" },
"no_oversimplification": { "level": "soft" },
"no_incomplete_logic": { "level": "soft" },
"no_hallucinated_apis": { "level": "advisory" }
}
}
See sections 21.4 and 21.5 for the full list of code quality checks.
21.3.8 Custom Rules
Define your own regex-based governance rules:
{
"custom_rules": [
{
"id": "CUSTOM-001",
"name": "no_eval_python",
"description": "Prevent eval() in Python",
"pattern": "\\beval\\s*\\(",
"languages": ["python"],
"level": "hard",
"message": "eval() is forbidden — use ast.literal_eval() or json.loads()",
"enabled": true
}
]
}
See section 21.8 for the full custom rules reference.
21.3.9 Output
Control how governance results are displayed:
{
"output": {
"summary": {
"enabled": true,
"format": "detailed",
"show_passing": true
},
"errors": {
"verbose": true,
"show_help": true,
"max_errors_per_rule": 3
}
}
}
21.3.10 Audit
Configure audit logging:
{
"audit": {
"level": "basic",
"log_events": {
"checks_passed": true,
"checks_failed": true
}
}
}
21.3.11 Meta
Schema validation settings:
{
"meta": {
"schema_validation": {
"warn_unknown_keys": true,
"suggest_corrections": true
}
}
}
21.4 Code Quality Checks
NAAb provides 20 built-in code quality checks. Each can be enabled individually and set to any enforcement level.
Standard Checks
| Check | Default Level | Description |
|---|---|---|
no_secrets | hard | Detects API keys, passwords, tokens (entropy-based + pattern matching) |
no_placeholders | soft | Detects TODO, FIXME, STUB, PLACEHOLDER markers |
no_hardcoded_results | advisory | Detects fabricated return values like return {"status": "ok"} |
no_pii | advisory | Detects SSNs, credit card numbers, email addresses |
no_temporary_code | soft | Detects temporary/throwaway code patterns |
no_simulation_markers | hard | Detects "Simulated", "Mock", "Fake" markers in code |
no_mock_data | advisory | Detects hardcoded mock/test data |
no_apologetic_language | advisory | Detects "sorry", "unfortunately" in code comments |
no_dead_code | advisory | Detects commented-out code blocks |
no_debug_artifacts | soft | Detects console.log, print statements used for debugging |
no_unsafe_deserialization | hard | Detects pickle.loads, yaml.load without SafeLoader |
no_sql_injection | hard | Detects string concatenation in SQL queries |
no_path_traversal | hard | Detects ../../ path traversal patterns |
no_hardcoded_urls | advisory | Detects hardcoded URLs (configurable allowlist) |
no_hardcoded_ips | advisory | Detects hardcoded IP addresses |
max_complexity | advisory | Enforces max lines, nesting depth, cyclomatic complexity |
encoding | advisory | Blocks null bytes, Unicode BiDi attacks, homoglyphs |
Example: Configuring no_secrets
"no_secrets": {
"level": "hard",
"entropy_check": {
"enabled": true,
"threshold": 4.5,
"min_length": 20
}
}
This enables Shannon entropy checking — strings with high randomness and sufficient length are flagged as potential secrets, even without matching known API key patterns.
21.5 LLM Anti-Drift Checks
These three checks specifically target common failures in LLM-generated code. They are designed to catch the patterns that make AI-generated code "look right" but behave incorrectly.
21.5.1 no_oversimplification
Purpose: Detect stub, trivial, and placeholder implementations where an LLM produced the minimal possible code instead of real logic.
What it catches:
- Empty function bodies (
def process(data): pass) - Trivial returns (
def validate(x): return True) - Identity/passthrough functions (
def transform(x): return x) - Not-implemented markers (
raise NotImplementedError) - Comment-only bodies (
# implementation here) - Fabricated results (
return {"status": "ok"})
Example violation:
main {
let result = <<python
def validate_user(user):
return True // <- Governance blocks this
def process_data(data):
pass // <- Governance blocks this
>>
}
Error output:
Error: Governance error: Oversimplified code: "def process_data(data):
pass" [HARD-MANDATORY]
Rule (govern.json): code_quality.no_oversimplification
Help:
- This looks like a stub or trivial implementation.
- LLMs often produce minimal code that passes syntax checks but lacks real logic.
Example:
X Blocked: def validate(data): return True
V Allowed: def validate(data):
if not isinstance(data, dict): raise TypeError(...)
Configuration:
"no_oversimplification": {
"level": "soft",
"check_empty_bodies": true,
"check_trivial_returns": true,
"check_identity_functions": true,
"check_not_implemented": true,
"check_comment_only_bodies": true,
"check_fabricated_results": true,
"case_sensitive": false,
"min_function_lines": 2,
"custom_patterns": []
}
Each sub-check can be toggled independently. For example, if you want to allow NotImplementedError in development but block empty bodies:
"no_oversimplification": {
"level": "hard",
"check_not_implemented": false,
"check_empty_bodies": true
}
21.5.2 no_incomplete_logic
Purpose: Detect logic gaps, missing error handling, degenerate control flow, and shortcuts that indicate the LLM took a shortcut instead of implementing complete logic.
What it catches:
- Empty/swallowed error handling (
except: pass, empty catch blocks) - Generic/vague error messages (
raise Exception("error")) - Bare raises (
raisewithout exception type) - Degenerate loops (loops that return/break on first iteration)
- Always-true/false conditions (
if True:,if False:) - Placeholder error messages (
"Something went wrong")
Example violation:
main {
let result = <<python
try:
data = open("file.txt").read()
except:
pass // <- Governance blocks this: swallowed exception
>>
}
Error output:
Error: Governance error: Incomplete logic: "except:
pass" [HARD-MANDATORY]
Rule (govern.json): code_quality.no_incomplete_logic
Help:
- This code has logic gaps that indicate shortcuts or lazy implementation.
- Common issues: empty catch blocks, generic error messages, degenerate loops.
Example:
X Blocked: except Exception: pass # swallows all errors
V Allowed: except ValueError as e:
logger.error(f"Validation failed: {e}")
raise
Configuration:
"no_incomplete_logic": {
"level": "soft",
"check_empty_catch": true,
"check_swallowed_exceptions": true,
"check_generic_errors": true,
"check_vague_error_messages": true,
"check_single_iteration_loops": true,
"check_bare_raise": true,
"check_always_true_false": true,
"check_missing_validation": true,
"case_sensitive": false,
"custom_patterns": []
}
21.5.3 no_hallucinated_apis
Purpose: Detect calls to non-existent APIs, wrong method names, and cross-language confusion. LLMs frequently mix up APIs between Python and JavaScript — this check catches those mistakes and suggests the correct alternative.
What it catches:
Python blocks using JavaScript APIs:
.push()instead of.append().lengthinstead oflen()console.log()instead ofprint()JSON.stringify()instead ofjson.dumps()Math.floor()instead ofmath.floor()===instead of==nullinstead ofNone
JavaScript blocks using Python APIs:
print()instead ofconsole.log()len()instead of.length.append()instead of.push().strip()instead of.trim()True/False/Noneinstead oftrue/false/nulland/or/notinstead of&&/||/!
Cross-language syntax confusion:
//comments in Python (should be#)#comments in JavaScript (should be//)
Example violation:
main {
let result = <<python
data = [1, 2, 3]
data.push(4) // <- ".push() is JavaScript — in Python, use .append()"
length = data.length // <- ".length is JavaScript — in Python, use len()"
>>
}
Error output:
Error: Governance error: Hallucinated API in python block: ".length" [SOFT-MANDATORY]
Rule (govern.json): code_quality.no_hallucinated_apis
Help:
- .length is JavaScript — in Python, use len()
Configuration:
"no_hallucinated_apis": {
"level": "advisory",
"check_cross_language": true,
"check_made_up_functions": true,
"check_wrong_syntax": true,
"case_sensitive": true,
"custom_patterns": []
}
The check is language-aware: it automatically selects the appropriate pattern set based on the polyglot block's language header (<<python, <<javascript, etc.).
21.6 Security Checks
Beyond code quality, governance includes dedicated security checks:
| Check | Default Level | Description |
|---|---|---|
shell_injection | hard | Detects shell injection patterns (backticks, $(...), os.system) |
code_injection | hard | Detects dynamic code execution (eval, exec, Function()) |
privilege_escalation | hard | Detects sudo, chmod 777, os.setuid |
data_exfiltration | hard | Detects base64 encoding + network send patterns |
resource_abuse | soft | Detects fork bombs, crypto mining patterns |
info_disclosure | advisory | Detects information leakage patterns |
crypto_weakness | advisory | Detects weak crypto (MD5, SHA1, DES, ECB mode) |
These are configured in the restrictions section of govern.json.
21.7 Per-Language Rules
Each language can have its own set of rules for fine-grained control:
{
"languages": {
"per_language": {
"python": {
"timeout": 15,
"max_lines": 100,
"banned_functions": ["eval(", "exec("],
"imports": {
"mode": "blocklist",
"blocked": ["subprocess", "ctypes"]
},
"style": {
"no_star_imports": { "level": "advisory" }
}
},
"javascript": {
"timeout": 10,
"max_lines": 80,
"banned_functions": ["eval(", "Function("],
"style": {
"strict_mode": { "level": "soft" },
"no_var": { "level": "advisory" },
"no_console_log": { "level": "advisory" }
}
},
"shell": {
"timeout": 5,
"banned_commands": ["rm -rf", "mkfs"],
"style": {
"require_set_e": { "level": "soft" },
"no_curl_pipe_sh": { "level": "hard" }
}
}
}
}
}
21.8 Custom Rules
For project-specific policies, define custom regex-based rules:
{
"custom_rules": [
{
"id": "PROJ-001",
"name": "no_hardcoded_urls",
"description": "All URLs must come from config",
"pattern": "https?://(?!localhost|127\\.0\\.0\\.1)",
"languages": ["python", "javascript"],
"level": "soft",
"message": "Hardcoded URLs are not allowed. Use config.get_url() instead.",
"help": "Move URLs to your configuration file",
"good_example": "url = config.get_url('api')",
"bad_example": "url = 'https://api.example.com'",
"enabled": true,
"case_sensitive": false
},
{
"id": "PROJ-002",
"name": "no_print_debug",
"description": "No debug print statements",
"pattern": "print\\(.*[Dd]ebug",
"languages": [],
"level": "advisory",
"message": "Debug print statements should be removed",
"enabled": true
}
]
}
Custom Rule Fields
| Field | Required | Description |
|---|---|---|
id | Yes | Unique rule identifier (shown in errors) |
name | Yes | Short name for the rule |
description | No | Human-readable description |
pattern | Yes | Regex pattern to match violations |
languages | No | Languages to apply to (empty = all languages) |
level | No | Enforcement level (default: "hard") |
message | No | Error message shown on violation |
help | No | Help text explaining how to fix |
good_example | No | Example of correct code |
bad_example | No | Example of incorrect code |
enabled | No | Whether rule is active (default: true) |
case_sensitive | No | Case-sensitive matching (default: false) |
21.9 CLI Flags and Reports
Governance CLI Flags
| Flag | Description |
|---|---|
--governance-override | Override SOFT-level governance rules (HARD rules cannot be overridden) |
--governance-report <path> | Write JSON governance report to file |
--governance-sarif <path> | Write SARIF report (for IDE/CI integration) |
--governance-junit <path> | Write JUnit XML report (for CI pipelines) |
Examples
# Run with governance and override soft rules
naab-lang --governance-override my_program.naab
# Generate a JSON report for review
naab-lang --governance-report report.json my_program.naab
# Generate SARIF for GitHub Code Scanning
naab-lang --governance-sarif results.sarif my_program.naab
# Generate JUnit for CI (Jenkins, GitLab CI, etc.)
naab-lang --governance-junit results.xml my_program.naab
SARIF Integration
SARIF (Static Analysis Results Interchange Format) reports can be uploaded to GitHub Code Scanning:
# .github/workflows/governance.yml
- name: Run NAAb Governance
run: naab-lang --governance-sarif results.sarif my_program.naab
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
21.10 Best Practices
For CI/CD Pipelines
Use enforce mode with --governance-sarif or --governance-junit for machine-readable results:
{
"version": "5.0",
"mode": "enforce",
"code_quality": {
"no_secrets": { "level": "hard" },
"no_sql_injection": { "level": "hard" },
"no_path_traversal": { "level": "hard" },
"no_unsafe_deserialization": { "level": "hard" }
}
}
For LLM-Generated Code Review
Enable all three LLM anti-drift checks to catch the most common AI code failures:
{
"version": "5.0",
"mode": "enforce",
"code_quality": {
"no_oversimplification": { "level": "hard" },
"no_incomplete_logic": { "level": "hard" },
"no_hallucinated_apis": { "level": "soft" },
"no_simulation_markers": { "level": "hard" },
"no_placeholders": { "level": "soft" }
}
}
For Team Projects
Start with audit mode to understand what would be flagged, then switch to enforce:
{
"version": "5.0",
"mode": "audit",
"description": "Team governance rules (audit mode for rollout)",
"languages": {
"allowed": ["python", "javascript"]
},
"code_quality": {
"no_secrets": { "level": "hard" },
"no_oversimplification": { "level": "soft" },
"no_incomplete_logic": { "level": "advisory" }
}
}
Gradual Adoption
- Start with
"mode": "audit"to see what gets flagged without blocking anything - Enable security checks first (
no_secrets,no_sql_injection,no_unsafe_deserialization) - Add LLM checks at
"advisory"level, then promote to"soft"or"hard"as confidence grows - Use
custom_rulesfor project-specific patterns - Generate reports with
--governance-sariffor CI integration
21.10 Project Context Awareness
NAAb governance can read your existing project files and use them to supplement govern.json. This turns governance from a static config into an adaptive partner that understands your project holistically.
Key principle: govern.json always wins. Project context supplements, never overrides.
Enabling
{
"project_context": {
"enabled": true
}
}
NAAb scans upward from the script directory for known files and extracts rules from three layers:
| Layer | Files | What Gets Extracted |
|---|---|---|
| LLM files | CLAUDE.md, .cursorrules, copilot-instructions.md, gemini.md | Language preferences, banned functions, style rules |
| Linter configs | .editorconfig, .eslintrc.json, .prettierrc, biome.json | Indent style/size, banned patterns |
| Package manifests | package.json, go.mod, Cargo.toml, pyproject.toml | Language detection (advisory only) |
Each layer is independently toggleable via sources.llm, sources.linters, sources.manifests.
NAAb extracts clear directives from LLM files using strong verbs ("always use", "never use", "prefer X for Y"). Ambiguous text and content inside code blocks is skipped. Extracted language preferences feed into polyglot optimization scoring.
Conflicts are resolved by priority_source (if set), otherwise linter configs win over LLM files, which win over manifests. Every skipped directive is reported with source and reason. Use suppress_rules to surgically suppress specific extractions by ID, or dry_run: true to preview without applying.
See docs/govern-template.json for the full configuration reference.
21.11 Taint Tracking
Taint tracking is NAAb's most powerful security feature, introduced in governance v4.0. It automatically tracks untrusted data from where it enters your program (sources) through all transformations to where it leaves (sinks), blocking unsafe usage unless the data has been sanitized.
21.11.1 How It Works
- Sources mark data as tainted when it enters from untrusted origins
- Propagation carries taint through string concatenation, interpolation, function returns, loop iterators, and container mutations
- Sanitizers clear taint when data passes through validation functions
- Sinks check for tainted data before performing dangerous operations
21.11.2 Configuration
{
"taint_tracking": {
"enabled": true,
"level": "hard",
"sources": ["env.get", "io.read_line", "file.read", "polyglot_output"],
"sinks": ["shell_exec", "python_exec", "go_exec", "javascript_exec",
"nim_exec", "file.write", "file.append", "http.", "env.set_var"],
"sanitizers": ["validate_", "sanitize_", "escape_"],
"propagation": {
"string_concat": true,
"string_interpolation": true,
"function_returns": true,
"for_loop_iterators": true,
"dict_array_access": true
}
}
}
21.11.3 Sources
Sources are functions whose return values are automatically marked as tainted:
| Source | What It Tracks |
|---|---|
env.get | Environment variables (attacker-controlled in CI/CD) |
io.read_line | User input from stdin |
file.read | File contents (may be attacker-supplied) |
polyglot_output | Return values from polyglot blocks |
polyglot_output:python | Only Python block output (per-language) |
Per-language polyglot sources: Use "polyglot_output:python" to taint only Python block output while leaving Go/JS/etc. untainted. This is useful when one language is trusted but another is not.
21.11.4 Sinks
Sinks are operations that must not receive tainted data:
| Sink | What It Protects Against |
|---|---|
shell_exec | Command injection in shell blocks |
python_exec | Code injection in Python blocks |
go_exec, nim_exec, etc. | Code injection in compiled language blocks |
file.write, file.append | Writing untrusted data to files |
http. | SSRF (Server-Side Request Forgery) via tainted URLs |
env.set_var | Environment variable injection |
Prefix matching: "http." matches http.get, http.post, http.put, http.delete — any HTTP method.
21.11.5 Sanitizers
Sanitizer functions clear taint from variables. Matching uses prefix matching:
| Sanitizer Prefix | Matches |
|---|---|
validate_ | validate_input(), validate_url(), etc. |
sanitize_ | sanitize_html(), sanitize_sql(), etc. |
escape_ | escape_shell(), escape_html(), etc. |
int( | int(user_input) — type casting as sanitization |
float( | float(user_input) — type casting as sanitization |
Member expressions work: module.sanitize_foo(x) also clears taint.
Important: Prefix matching means "validate_" matches validate_input but NOT revalidate_input.
21.11.6 Propagation
Taint propagates automatically through these paths:
// String concatenation
let tainted = env.get("USER_INPUT")
let msg = "Hello " + tainted // msg is tainted
// String interpolation
let greeting = "Hello ${tainted}" // greeting is tainted
// Function returns
fn get_data() {
let x = env.get("DATA")
return x // return value carries taint
}
let data = get_data() // data is tainted
// For-loop iterators
for item in env.get("PATH") { // item is tainted
print(item) // safe (print is not a sink)
}
// Container mutations
let arr = [1, 2, 3]
arr.push(tainted) // arr is now tainted
let dict = {"key": "clean"}
dict["key"] = tainted // dict is now tainted
21.11.7 Examples
Example 1: Command injection prevention
use env
fn process_user_input() {
let user_file = env.get("USER_FILE") // tainted
// BLOCKED: tainted data in shell block
// let result = <<shell[user_file]
// cat ${user_file}
// >>
// SAFE: sanitize first
let safe_file = validate_filename(user_file) // taint cleared
let result = <<shell[safe_file]
cat ${safe_file}
>>
return result
}
fn validate_filename(name) {
// Real validation logic here
if string.contains(name, "..") {
return "invalid"
}
return name
}
main {
process_user_input()
}
Example 2: SSRF prevention
use env
use http
main {
let url = env.get("WEBHOOK_URL") // tainted
// BLOCKED: tainted URL passed to http.get
// let response = http.get(url)
// SAFE: validate the URL first
let safe_url = validate_url(url)
let response = http.get(safe_url)
}
fn validate_url(url) {
if !string.starts_with(url, "https://") {
return "https://default.example.com"
}
return url
}
Example 3: Async taint propagation
use env
async fn fetch_data() {
let key = env.get("API_KEY") // tainted
return key
}
main {
let data = await fetch_data() // data is tainted (async propagation)
// Taint flows through await expressions
}
21.11.8 Enforcement Levels
| Level | Behavior |
|---|---|
hard | Taint violation throws an error. Execution stops. Cannot be overridden. |
soft | Taint violation throws an error. Can be overridden with --governance-override. |
advisory | Taint violation emits a warning. Execution continues. |
21.11.9 Limitations
- Async taint is snapshot-based: Each async function gets a snapshot of the parent's taint state at creation time. Multi-level async chains (grandchild functions) may not see taint added by intermediate functions.
- Module imports skip body scan: Taint is enforced at runtime, not during static import analysis. This is a performance optimization.
- Name-based tracking: Taint is tracked by variable name, not by value identity. Reassigning a variable with clean data clears its taint.
21.12 Cross-Module Contracts
Governance v4 adds parameter type validation at function call sites:
{
"contracts": {
"validate_inputs": true,
"functions": {
"process_data": {
"params": ["data:dict", "count:int"],
"return_type": "dict",
"return_keys": ["status", "result"]
},
"calculate_score": {
"params": ["values:array"],
"return_type": "number",
"return_min": 0,
"return_max": 100
}
}
}
}
Contracts are checked on both callFunction() and direct call dispatch paths, with violations logged to the audit trail.
21.13 Enhanced Audit Trail
Governance v4 provides JSONL audit logging with tamper-evident hash chains:
{
"audit": {
"level": "detailed",
"log_file": "governance_audit.jsonl",
"log_events": {
"polyglot_timing": true,
"taint_decisions": true,
"contract_checks": true,
"checks_passed": true,
"checks_failed": true
}
}
}
Each audit entry includes a SHA-256 hash of the previous entry, creating a tamper-evident chain. Events logged include:
- polyglot_timing: Execution duration per polyglot block
- taint_decisions: Every taint mark/clear/block decision
- contract_checks: Every contract pass/fail with parameter details
21.14 The NAAb Scanner
NAAb includes a built-in static analysis scanner accessible via naab-lang --scan:
# Scan a Python file
naab-lang --scan script.py python
# Auto-detect language
naab-lang --scan src/utils.js auto
# Scan with JSON output
naab-lang --scan script.py python --governance-report report.json
The scanner provides 139 checks across 6 categories:
| Category | Checks | What It Detects |
|---|---|---|
| Redundancy | 30 | Dead variables, unused imports, duplicate expressions |
| Code Quality | 20 | Magic numbers, long functions, deep nesting |
| Complexity | 9 | Cyclomatic complexity, cognitive complexity |
| Style | 12 | Naming conventions, formatting issues |
| Security | 0 | (Covered by governance security checks) |
| Language-specific | 68 | Python (14), JavaScript (12), C++ (12), Go (9), Rust (10), NAAb (11) |
The scanner is configurable via the "scanner" section of govern.json and supports text, JSON, and SARIF output formats.
21.15 Multi-Agent Governance
NAAb governance v4.0 introduces multi-agent role enforcement, allowing govern.json to define per-agent restrictions on languages and file paths. This is designed for scenarios where multiple AI agents or automated tools interact with the same codebase.
21.15.1 Agent Roles Configuration
Add an agent_roles section to your govern.json:
{
"agent_roles": {
"code-bot": {
"allowed_languages": ["python", "javascript"],
"allowed_paths": ["./src", "./lib"],
"blocked_paths": ["./secrets", "./.env", "./credentials"]
},
"test-bot": {
"allowed_languages": ["python", "shell"],
"allowed_paths": ["./tests", "./fixtures"],
"blocked_paths": ["./src"]
},
"deploy-bot": {
"allowed_languages": ["shell"],
"allowed_paths": ["./deploy", "./scripts"],
"blocked_paths": ["./src", "./tests"]
}
}
}
Each agent role defines:
| Field | Type | Description |
|---|---|---|
allowed_languages | array | Languages this agent may use in polyglot blocks |
allowed_paths | array | Filesystem paths this agent may read/write |
blocked_paths | array | Filesystem paths this agent is explicitly denied access to |
21.15.2 CLI Usage
Use the --agent-id flag to identify the executing agent:
# Run as a specific agent
naab-lang --agent-id code-bot my_program.naab
# Combine with governance dashboard for summary output
naab-lang --agent-id code-bot --governance-dashboard my_program.naab
When --agent-id is provided, the governance engine:
- Looks up the agent name in
govern.json→agent_roles - Restricts the agent's polyglot blocks to only
allowed_languages - Restricts file operations to
allowed_pathsand blocksblocked_paths - If the agent is not listed in
agent_roles, default governance rules apply (no agent-specific restrictions)
21.15.3 Governance Dashboard
The --governance-dashboard flag outputs a governance summary to stderr after execution:
naab-lang --agent-id code-bot --governance-dashboard my_program.naab
Output:
=== Governance Dashboard ===
Agent: code-bot
Checks: 12 passed, 0 warned, 0 blocked
Languages used: python (2 blocks), javascript (1 block)
Paths accessed: ./src/utils.naab, ./lib/helpers.naab
Taint: 3 sources, 2 sanitized, 0 violations
21.15.4 Telemetry
Enable JSONL telemetry output to track agent execution over time:
{
"telemetry": {
"enabled": true,
"output_file": "telemetry.jsonl"
}
}
Each execution appends a JSONL record with:
- Agent ID (if provided via
--agent-id) - Timestamp
- Languages used
- Paths accessed
- Governance check results (passed/warned/blocked)
- Execution duration
This data can be used for trend analysis, compliance reporting, and monitoring agent behavior across runs.
21.15.5 Best Practices for Multi-Agent Governance
- Principle of least privilege: Give each agent only the languages and paths it needs
- Block sensitive paths: Always block
./secrets,./.env, and credential directories for all agents - Use telemetry for auditing: Enable JSONL telemetry to maintain a log of agent activities
- Combine with taint tracking: Agent roles + taint tracking provides defense-in-depth — even if an agent accesses allowed paths, tainted data is still blocked at sinks
- Test with
--governance-dashboard: Use the dashboard flag during development to verify agent restrictions are correctly applied
21.16 Behavioral Sequence Detection (BSD)
Beyond static checks, NAAb can detect dangerous multi-step behavior patterns at runtime. BSD uses finite state machine matching over an event ring buffer to catch attack sequences like "read secrets, encode them, then exfiltrate."
21.16.1 Configuration
Patterns are fully user-defined in govern.json — there are no hardcoded rules:
{
"behavioral_sequences": {
"enabled": true,
"window_size": 100,
"patterns": [
{
"name": "data_exfiltration",
"sequence": ["env.get:*KEY*|file.read:*secret*", "encode|base64|compress", "http.post|agent.send"],
"level": "hard",
"max_gap": 10,
"decay_seconds": 300,
"decay_turns": 20,
"rationale": "Reading sensitive data, encoding it, then sending it out matches exfiltration."
}
]
}
}
Each step in the sequence is a pipe-separated list of matchers. The optional colon suffix is a detail glob that matches against function arguments (e.g., env.get:*KEY* matches env.get("API_KEY") but not env.get("HOME")).
21.16.2 Key Features
- Pre-execution blocking — when BSD detects that the next action would complete a dangerous sequence, it blocks that action before it executes
- Decay/expiry — events older than
decay_turnsturns ordecay_secondsseconds no longer count toward sequence matches, preventing false positives from stale context - Detail globs — per-argument filtering with pipe-separated alternatives (
env.get:*SECRET*|*TOKEN*) - Cumulative scoring — BSD matches feed into the cumulative risk score like any advisory check
21.16.3 Enforcement Levels
BSD patterns support the same three enforcement levels as other governance checks:
| Level | Behavior |
|---|---|
hard | Block execution immediately. No override. |
soft | Block execution unless --governance-override is passed. |
advisory | Warn and add to cumulative risk score. |
21.17 Context Drift Detection (CDD)
CDD monitors LLM agent conversations for coherence drift — detecting when an agent's behavior diverges from its declared intent.
21.17.1 Configuration
{
"context_drift": {
"enabled": true,
"level": "advisory",
"coherence_threshold": 0.5,
"max_contradictions": 3,
"check_interval_turns": 5,
"fingerprint_window": 10,
"signals": {
"repeated_failures": true,
"circular_actions": true,
"scope_creep": true
},
"weights": {
"circular": 0.3,
"scope_creep": 0.2,
"contradiction": 0.4,
"repeated_failure": 0.1
}
}
}
21.17.2 Drift Signals
| Signal | What It Detects |
|---|---|
repeated_failures | Agent retrying the same failing action multiple times |
circular_actions | Agent cycling through the same set of actions without progress |
scope_creep | Agent expanding beyond its declared intent |
contradiction | Agent taking actions that contradict previous decisions |
Each signal has a configurable weight. The coherence score starts at 1.0 and decays as drift signals accumulate. When it drops below coherence_threshold, CDD triggers enforcement.
21.17.3 Integration with Agent Module
CDD is automatically checked during agent.send() and agent.batch() calls. Governance notices are surfaced in the return dict via the governance_notices field, allowing the calling code to react to drift warnings.
21.18 Cumulative Risk Scoring
Advisory findings don't block execution, but they accumulate. NAAb's cumulative scoring system tracks the total weighted risk across all advisory checks in a single execution.
21.18.1 How It Works
Each advisory check that fires adds its weight to a cumulative score. The score is compared against configurable thresholds:
| Zone | Default Range | Behavior |
|---|---|---|
| Green | 0 — below yellow threshold | No action |
| Yellow | yellow threshold — below red threshold | Warning emitted |
| Red | At or above red threshold | Execution blocked (quality gate) |
21.18.2 Configuration
Global scoring configuration:
{
"scoring": {
"enabled": true,
"default_weight": 1.0,
"green_threshold": 0,
"yellow_threshold": 20,
"red_threshold": 40,
"threshold_mode": "cumulative"
}
}
Named scorer configurations can override the global defaults for specific contexts (e.g., agent review):
{
"scorers": {
"security": {
"enabled": true,
"default_weight": 1.0,
"rule_weights": {},
"green_threshold": 0,
"yellow_threshold": 20,
"red_threshold": 40,
"threshold_mode": "cumulative"
}
}
}
Named scorers are referenced by agent_review.scorer to use a specific scoring profile during AI-assisted code review.