Version: 1.0 Last Updated: 2026-01-13
- Overview
- Threat Model
- Security Architecture
- Input Validation
- Code Generation Security
- Dependency Security
- Development Security
- Reporting Security Issues
TypedLua is a compiler that transforms typed Lua source code into executable Lua. As a development tool, security considerations focus on preventing vulnerabilities in generated code, protecting developer environments, and maintaining secure development practices.
- Defense in Depth - Multiple layers of protection
- Least Privilege - Minimal permissions required
- Secure by Default - Safe defaults in configuration
- Fail Securely - Errors don't leak sensitive information
- Auditability - Security-relevant events are logged
TypedLua security considerations include:
-
Malicious Input Files
- Crafted source files designed to exploit parser/compiler
- Files with malicious content in comments or strings
- Extremely large or deeply nested structures (DoS)
-
Configuration Injection
- Malicious
tlconfig.yamlfiles - Command-line argument injection
- Environment variable manipulation
- Malicious
-
Code Injection via Generated Code
- Template literals with unsanitized content
- String interpolation vulnerabilities
- Eval-like constructs in generated Lua
-
Information Disclosure
- Leaking file paths in error messages
- Exposing sensitive data in diagnostics
- Source maps revealing proprietary code
-
Supply Chain
- Compromised dependencies
- Malicious LSP extensions
- Tampered build artifacts
The following are explicitly not security boundaries:
-
Trusted Code Execution - TypedLua compiles code the user has written or explicitly chosen to compile. We assume the source files are trusted.
-
Sandbox Escape - TypedLua does not run untrusted code. Generated Lua is executed in the user's chosen environment (which may or may not be sandboxed).
-
Lua Runtime Security - Securing the Lua runtime environment is the responsibility of the deployment platform, not TypedLua.
TypedLua operates as a non-sandboxed compiler that:
- Reads files from disk (within project directory)
- Writes compiled output to disk
- Does NOT execute arbitrary code during compilation
- Does NOT make network requests (except for LSP over local socket)
No Dynamic Code Execution:
The compiler never uses eval, exec, or similar constructs. All code generation is template-based and statically analyzable.
┌─────────────────────────────────────────────┐
│ User's Operating System │
│ (Filesystem, Network, Process Management) │
└──────────────────┬──────────────────────────┘
│
┌─────────▼──────────┐
│ TypedLua CLI │
│ (User privileges) │
└─────────┬──────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌─────────┐
│ Lexer │ │ Parser │ │ Type │
│ │ │ │ │ Checker │
└────────┘ └──────────┘ └─────────┘
│
▼
┌──────────┐
│ CodeGen │
└──────────┘
│
▼
┌──────────┐
│ Output │
│ (Lua) │
└──────────┘
Key Points:
- Runs with user privileges (not elevated)
- File access limited by OS permissions
- No special capabilities required
- Does not spawn subprocesses (except for LSP)
All file paths are validated before access:
fn validate_file_path(path: &Path) -> Result<(), SecurityError> {
// 1. Reject absolute paths outside project root
let canonical = path.canonicalize()?;
if !canonical.starts_with(&project_root) {
return Err(SecurityError::PathTraversal);
}
// 2. Reject symlinks outside project
if canonical.is_symlink() {
let target = fs::read_link(&canonical)?;
if !target.starts_with(&project_root) {
return Err(SecurityError::SymlinkEscape);
}
}
// 3. Check file extension
if !allowed_extensions.contains(&canonical.extension()) {
return Err(SecurityError::InvalidExtension);
}
Ok(())
}Protections:
- ✅ Path traversal prevention (
../../etc/passwd) - ✅ Symlink escape detection
- ✅ File extension validation
- ✅ Project root boundary enforcement
tlconfig.yaml is validated on load:
fn load_config(path: &Path) -> Result<CompilerConfig, ConfigError> {
// 1. Size limit (prevent DoS)
let metadata = fs::metadata(path)?;
if metadata.len() > MAX_CONFIG_SIZE {
return Err(ConfigError::FileTooLarge);
}
// 2. Parse YAML safely (no arbitrary code execution)
let content = fs::read_to_string(path)?;
let config: CompilerConfig = serde_yaml::from_str(&content)?;
// 3. Validate values
validate_config_values(&config)?;
Ok(config)
}
fn validate_config_values(config: &CompilerConfig) -> Result<(), ConfigError> {
// Check include/exclude patterns for path traversal
for pattern in &config.include {
if pattern.contains("..") {
return Err(ConfigError::InvalidPattern);
}
}
// Validate output directory is within project
if let Some(out_dir) = &config.compiler_options.out_dir {
validate_output_directory(out_dir)?;
}
Ok(())
}Protections:
- ✅ File size limits (prevents DoS)
- ✅ No arbitrary code execution in YAML
- ✅ Path traversal in patterns rejected
- ✅ Output directory validation
Limits on source file complexity:
const MAX_FILE_SIZE: usize = 5_000_000; // 5 MB
const MAX_NESTING_DEPTH: usize = 128; // Prevent stack overflow
const MAX_IDENTIFIER_LENGTH: usize = 512;
const MAX_STRING_LITERAL_LENGTH: usize = 1_000_000;Protections:
- ✅ File size limits (DoS prevention)
- ✅ Nesting depth limits (stack overflow prevention)
- ✅ Identifier length limits (buffer overflow prevention)
- ✅ String literal length limits (memory exhaustion prevention)
TypedLua generates Lua code using safe templating with no string interpolation of user input:
// SAFE: User input is not interpolated into code
fn generate_function_call(&mut self, callee: &str, args: &[Arg]) {
self.emit_identifier(callee); // Validated identifier
self.emit("(");
for (i, arg) in args.iter().enumerate() {
if i > 0 {
self.emit(", ");
}
self.generate_expression(arg); // Recursive generation
}
self.emit(")");
}
// UNSAFE (NOT USED):
// self.emit(format!("{}({})", callee, args)); // NEVER DO THISTemplate literals are escaped:
fn generate_template_literal(&mut self, parts: &[TemplatePart]) {
for part in parts {
match part {
TemplatePart::String(s) => {
// Escape special characters
let escaped = escape_lua_string(s);
self.emit(&escaped);
}
TemplatePart::Expression(expr) => {
self.emit("tostring(");
self.generate_expression(expr);
self.emit(")");
}
}
}
}
fn escape_lua_string(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\0', "\\0")
}Source maps do not expose sensitive information:
- ✅ Only relative paths (never absolute)
- ✅ No comments from source
- ✅ No type annotations
- ✅ Optional generation (can be disabled)
fn generate_source_map(&self, output: &str) -> SourceMap {
SourceMap {
version: 3,
file: self.output_filename.clone(),
sources: vec![self.relative_source_path()], // Relative only
names: vec![],
mappings: self.encode_mappings(),
source_content: None, // Don't embed source
}
}All dependencies are vetted:
- Minimal Dependencies - Only essential crates
- Trusted Sources - Only from crates.io
- Version Pinning - Lock file committed
- Regular Audits -
cargo auditin CI/CD
Current Dependencies:
| Crate | Purpose | Audit Status |
|---|---|---|
thiserror |
Error handling | ✅ Trusted |
anyhow |
Error propagation | ✅ Trusted |
serde |
Serialization | ✅ Trusted |
serde_yaml |
Config parsing | ✅ Trusted |
clap |
CLI parsing | ✅ Trusted |
bumpalo |
Arena allocation | ✅ Trusted |
lsp-server |
LSP protocol | ✅ Trusted |
CI/CD pipeline includes:
# .github/workflows/security.yml
- name: Audit dependencies
run: cargo audit
- name: Check for outdated dependencies
run: cargo outdated --exit-code 1
- name: Verify reproducible builds
run: cargo build --release --lockedProcess for handling dependency vulnerabilities:
- CI automatically runs
cargo auditon every push - Dependabot creates PRs for security updates
- Security updates are prioritized and merged ASAP
- Release notes mention fixed vulnerabilities
Pre-commit hook enforces security practices:
# .git/hooks/pre-commit
# 1. Prevent committing secrets
if grep -rE '(API_KEY|SECRET|PASSWORD|TOKEN)=[A-Za-z0-9+/]{20,}' .; then
echo "ERROR: Potential secret detected"
exit 1
fi
# 2. Prevent debug macros
if grep -r 'dbg!' --include='*.rs' .; then
echo "ERROR: dbg!() found"
exit 1
fi
# 3. Format and lint
cargo fmt --check
cargo clippy -- -D warningsConfiguration: .git/hooks/pre-commit-config.json
{
"custom_checks": [
{
"name": "no-dbg-macro",
"command": "grep -q 'dbg!' ",
"fail_on_match": true,
"error_message": "Found dbg!() macro - remove before committing",
"file_patterns": ["*.rs"]
}
],
"file_size_limits": {
"enabled": true,
"max_file_size_kb": 5000
}
}The following files are gitignored:
# Environment files
.env
.env.local
*.pem
*.key
credentials.json
# IDE files (may contain local configs)
.vscode/
.idea/
# Build artifacts
target/Guidelines:
- ❌ Never commit API keys or passwords
- ❌ Never commit private keys or certificates
- ❌ Never commit credentials.json or .env files
- ✅ Use environment variables for secrets
- ✅ Document required env vars in README
All code changes must:
- ✅ Pass automated security checks
- ✅ Be reviewed by at least one maintainer
- ✅ Include tests for security-relevant changes
- ✅ Update docs if security posture changes
- ✅ Validate all external input (files, CLI args, env vars)
- ✅ Use Result<T, E> for error handling (never panic)
- ✅ Limit recursion depth (prevent stack overflow)
- ✅ Set size limits on data structures (prevent DoS)
- ✅ Escape output when generating code
- ✅ Use cargo audit regularly
- ✅ Keep dependencies up to date
- ✅ Review diffs carefully before committing
- ❌ Trust user input without validation
- ❌ Use unsafe Rust without justification
- ❌ Interpolate user input into generated code
- ❌ Log sensitive information
- ❌ Commit secrets or credentials
- ❌ Ignore compiler warnings
- ❌ Use deprecated or vulnerable dependencies
Issue: Malicious input can cause excessive compilation time.
Example:
type T1 = [string, string]
type T2 = [T1, T1]
type T3 = [T2, T2]
-- ... exponential growth
type T20 = [T19, T19] -- 2^20 = 1M elementsMitigation:
- Type expansion depth limit: 128
- Type complexity limit: 10,000 nodes
- Compilation timeout: 60 seconds
Issue: Large files or deeply nested structures can exhaust memory.
Mitigation:
- File size limit: 5 MB
- Nesting depth limit: 128
- Arena size monitoring
- Graceful error on OOM
Issue: Malicious config could access files outside project.
Mitigation:
- All paths validated before access
- Symlinks resolved and checked
- Output directory must be within project root
Issue: Error messages might reveal sensitive paths.
Mitigation:
- Use relative paths in diagnostics
- Sanitize error messages before display
- Option to redact file paths (
--no-file-paths)
#[test]
fn test_path_traversal_prevention() {
let config = CompilerConfig::default();
let result = compile_file(&config, "../../etc/passwd");
assert!(result.is_err());
}
#[test]
fn test_nesting_depth_limit() {
let source = generate_deeply_nested_expression(200);
let result = compile_source(&source);
assert!(matches!(result.unwrap_err(), Error::NestingTooDeep));
}
#[test]
fn test_no_code_injection() {
let source = r#"
const evil = "'; os.execute('rm -rf /'); --"
print(evil)
"#;
let lua_code = compile_source(source).unwrap();
assert!(!lua_code.contains("os.execute"));
}Fuzzing with cargo-fuzz:
// fuzz/fuzz_targets/lexer.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
use typedlua_core::Lexer;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = Lexer::new(s, diagnostics.clone()).tokenize();
}
});Run fuzzing:
cargo fuzz run lexer -- -max_len=10000 -timeout=5If you discover a security vulnerability in TypedLua:
-
DO NOT open a public GitHub issue
-
DO email security@typedlua.dev with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact assessment
- Suggested fix (if known)
-
Allow 90 days for response and patching before public disclosure
- Acknowledgment - Within 48 hours
- Assessment - Severity rating within 7 days
- Fix Development - Patch created ASAP
- Coordinated Disclosure - Public announcement with patch
- Credit - Reporter credited in release notes (if desired)
Critical vulnerabilities:
- Patch released immediately
- Security advisory published
- All users notified via GitHub and mailing list
Non-critical vulnerabilities:
- Included in next regular release
- Mentioned in changelog
Before each release:
- Run
cargo auditand address all issues - Run
cargo outdatedand update dependencies - Review all changed files for secrets
- Test with fuzzer for 1 hour minimum
- Verify pre-commit hooks are enforced
- Update SECURITY.md if security posture changed
- Sign release artifacts
- Generate SHA256 checksums for binaries
- OWASP Secure Coding Practices
- Rust Security Best Practices
- Supply Chain Security
- Vulnerability Disclosure Policy
Security Email: security@typedlua.dev PGP Key: Available at https://typedlua.dev/security.asc Security Policy: https://github.com/forge18/typed-lua/security/policy
Version: 1.0 Contributors: TypedLua Security Team License: MIT