Skip to main content

Three Stages

Stage 1: Static Analysis

Regex pattern scanning for 25+ vulnerability categories:
  • SQL injection (string concatenation, format strings)
  • Command injection (eval, exec, subprocess)
  • Hardcoded secrets (API keys, passwords, private keys, connection strings)
  • XSS (innerHTML, dangerouslySetInnerHTML)
  • Path traversal (../ in file operations)
  • Insecure deserialization (pickle, yaml.load)
  • Weak cryptography (MD5, SHA-1)
  • Resource abuse (infinite loops)
  • Supply chain attacks (pipe-to-shell, insecure package indices)

Stage 2: LLM Adversarial Review

A local Ollama model with an attacker-mindset prompt reviews the output. Catches logic flaws and attack vectors that regex can’t find.

Stage 3: Sandbox Execution

Runs code blocks in ephemeral Docker/Podman containers with strict security constraints:
ConstraintValue
--network=noneNo network access
--memory=128mMemory limit
--cpus=0.5CPU limit
--read-onlyRead-only root filesystem
--cap-drop=ALLDrop all Linux capabilities
--security-opt=no-new-privileges:trueNo privilege escalation
TTL timeoutForce-kills after configured seconds
Analyzes output for network access attempts, filesystem violations, privilege escalation, and crash signals.

Usage

use laminae::shadow::{ShadowEngine, ShadowEvent, create_report_store};

let store = create_report_store();
let engine = ShadowEngine::new(store.clone());

let mut rx = engine.analyze_async(
    "session-1".into(),
    "Here's some code:\n```python\neval(user_input)\n```".into(),
);

while let Some(event) = rx.recv().await {
    match event {
        ShadowEvent::Finding { finding, .. } => {
            eprintln!("[{}] {}: {}",
                finding.severity, finding.category, finding.title);
        }
        ShadowEvent::Done { report, .. } => {
            println!("Clean: {} | Issues: {}",
                report.clean, report.findings.len());
        }
        _ => {}
    }
}

Configuration

Shadow is configured via ShadowConfig (JSON file at ~/.config/laminae/shadow.json):
FieldDefaultDescription
enabledtrueMaster enable/disable
aggressiveness21=static, 2=static+LLM, 3=all stages
llm_review_enabledtrueEnable LLM adversarial reviewer
sandbox_enabledfalseEnable container sandbox (requires Docker/Podman)
shadow_modelqwen2.5:14bOllama model for LLM review
sandbox_imagepython:3.12-slimDocker image for sandbox
sandbox_ttl_secs30Max execution time per block

Custom Analyzers

Implement the Analyzer trait to add custom analysis stages:
use laminae::shadow::analyzer::{Analyzer, AnalyzerError};

struct MyAnalyzer;

impl Analyzer for MyAnalyzer {
    fn name(&self) -> &'static str { "my-analyzer" }
    async fn is_available(&self) -> bool { true }
    async fn analyze(&self, output: &str, blocks: &[ExtractedBlock])
        -> Result<Vec<VulnFinding>, AnalyzerError>
    {
        // Your analysis logic
        Ok(vec![])
    }
}