Skip to main content
This document describes the technical architecture and development practices for Osmedeus. It’s intended for developers who want to understand, modify, or extend the codebase.

Table of Contents

Project Structure

Architecture Overview

Osmedeus follows a layered architecture:

Core Components

Workflow Types

Module: Single execution unit with sequential steps Flow: Orchestrates multiple modules with dependency management

Step Types

remote-bash Step Type

The remote-bash step type allows per-step Docker or SSH execution, independent of the module-level runner:

Decision Routing (Conditional Branching)

Steps can include decision routing to jump to different steps based on switch/case matching:
The _end special value terminates workflow execution from the current step.

Execution Context

The context is passed through the execution pipeline and accumulates state:
  • Variables are set by the executor (built-in variables)
  • Params are user-provided
  • Exports are step outputs that propagate to subsequent steps

Workflow Engine

Parser

The parser (internal/parser/parser.go) handles YAML parsing:

Loader

The loader (internal/parser/loader.go) provides caching and lookup:
Lookup order:
  1. Check cache
  2. Try workflows/<name>.yaml
  3. Try workflows/<name>-flow.yaml
  4. Try workflows/modules/<name>.yaml
  5. Try workflows/modules/<name>-module.yaml

Execution Pipeline

Flow

Executor

Key responsibilities:
  1. Initialize execution context with built-in variables
  2. Create and setup the appropriate runner
  3. Iterate through steps, dispatching to appropriate handler
  4. Handle pre-conditions, exports, and decision routing
  5. Process on_success/on_error actions

Step Dispatcher

The dispatcher uses a plugin registry pattern for extensible step type handling:
Built-in executors registered at startup:
  • BashExecutor - handles bash steps
  • FunctionExecutor - handles function steps
  • ForeachExecutor - handles foreach steps
  • ParallelExecutor - handles parallel-steps steps
  • RemoteBashExecutor - handles remote-bash steps
  • HTTPExecutor - handles http steps
  • LLMExecutor - handles llm steps

Runner System

Interface

Host Runner

Simple local execution using os/exec:

Docker Runner

Supports both ephemeral (docker run --rm) and persistent (docker exec) modes:

SSH Runner

Uses golang.org/x/crypto/ssh for remote execution:

Authentication Middleware

Auth Types

The server supports two authentication methods:
MethodHeaderDescription
API Keyx-osm-api-keySimple token-based auth
JWTAuthorization: Bearer <token>Token from /osm/api/login

Priority Logic

Priority order:
  1. API Key Auth - If EnabledAuthAPI is true
  2. JWT Auth - If API key auth disabled and NoAuth is false
  3. No Auth - If NoAuth option is true

APIKeyAuth Implementation

Security features:
  • Case-sensitive exact matching
  • Rejects empty/whitespace-only keys
  • Rejects placeholder values (“null”, “undefined”, “nil”)

Template Engine

Variable Resolution

The template engine (internal/template/engine.go) handles {{variable}} interpolation:
Resolution order:
  1. Check context variables
  2. Check environment variables (optional)
  3. Return empty string if not found

Built-in Variable Injection

Foreach Variable Syntax

Foreach uses [[variable]] syntax (double brackets) to avoid conflicts with template variables:

Function Registry

Otto JavaScript Runtime

Functions are implemented in Go and exposed to an Otto JavaScript VM:

Adding New Functions

  1. Add the Go implementation in the appropriate file:
  1. Register in registerFunctions():

Output and Control Functions

These functions provide output and execution control within workflows:
Usage in workflows:

Event Functions

These functions enable event-driven workflows by generating and emitting events:
Usage in workflows:
Event delivery uses a fallback chain:
  1. Server API - POST to /osm/api/events/emit if server configured
  2. Redis Pub/Sub - Publish to osm:events:{topic} in distributed mode
  3. Database Queue - Store in event_logs table with processed=false
  4. Webhooks - Send to configured webhook endpoints

Function Execution

Scheduler System

Trigger Types

Scheduler

The scheduler manages workflow triggers using gocron for cron jobs and fsnotify for file watching:
File watching uses fsnotify for instant inotify-based notifications (sub-millisecond latency) instead of polling.

Event Filtering

Events are matched using JavaScript expressions:

Workflow Linter

The workflow linter (internal/linter/) provides static analysis of workflow YAML files to catch common issues before execution.

Usage

Severity Levels

SeverityDescriptionExit Code
infoBest practice suggestions (e.g., unused exports)0
warningPotential issues that may cause problems0
errorCritical issues that will likely cause failures1 (with —check)

Built-in Rules

RuleSeverityDescription
missing-required-fieldwarningDetects missing required fields (name, kind, type)
duplicate-step-namewarningDetects multiple steps with the same name
empty-stepwarningDetects steps with no executable content
unused-variableinfoDetects exports that are never referenced
invalid-gotowarningDetects decision goto references to non-existent steps
invalid-depends-onwarningDetects depends_on references to non-existent steps
circular-dependencywarningDetects circular references in step dependencies
Note: The undefined-variable rule is available but not enabled by default as it can produce false positives for dynamically-injected variables.

Built-in Variables

The linter recognizes all runtime-injected variables to avoid false positives. These include: Path Variables: BaseFolder, Binaries, Data, ExternalData, ExternalConfigs, Workflows, Workspaces, etc. Target Variables: Target, target, TargetFile, TargetSpace Output Variables: Output, output, Workspace, workspace Metadata Variables: Version, RunUUID, TaskDate, TimeStamp, Today, RandomString Heuristic Variables: TargetType, TargetRootDomain, TargetTLD, Org, TargetHost, TargetPort, etc. Chunk Variables: ChunkIndex, ChunkSize, TotalChunks, ChunkStart, ChunkEnd

Linter Architecture

Adding a New Lint Rule

  1. Create the rule in internal/linter/rules.go:
  1. Register in GetDefaultRules():

Database Layer

Multi-Engine Support

Models

Repository Pattern

Schedule Operations

JSONL Import

Testing

Test Structure

Running Tests

Writing Tests

Use testify for assertions:
For integration tests, use build tags:

Adding New Features

Adding a New Step Type

  1. Define the type in internal/core/types.go:
  1. Create executor in internal/executor/mynew_executor.go:
  1. Register in dispatcher (internal/executor/dispatcher.go):

Adding a New Runner

  1. Create runner in internal/runner/myrunner.go:
  1. Add type in internal/core/types.go:
  1. Register in factory (internal/runner/runner.go):

Adding a New Installer Mode

  1. Create installer in internal/installer/mymode.go:
  1. Add flag in pkg/cli/install.go:
  1. Register in runInstallBinary() switch statement.
See internal/installer/nix.go for a complete example.

Adding a New API Endpoint

  1. Add handler in pkg/server/handlers/handlers.go:
  1. Register route in pkg/server/server.go:

Adding a New CLI Command

  1. Create command file in pkg/cli/mycommand.go:
  1. Register in pkg/cli/root.go:

CLI Shortcuts and Tips

Command Aliases

  • osmedeus func - alias for osmedeus function
  • osmedeus func e - alias for osmedeus function eval
  • osmedeus db ls - alias for osmedeus db list

Database CLI Commands

Query and manage database tables directly from the CLI:
Default columns per table:
  • runs: run_uuid, workflow_name, target, workspace, status, completed_steps, total_steps, started_at
  • event_logs: topic, source, source_type, processed, data_type, workspace, data
  • artifacts: artifact_path, artifact_type, content_type, workspace, run_id
  • assets: asset_value, host_ip, title, status_code, last_seen_at, technologies
  • schedules: name, workflow_name, trigger_type, schedule, is_enabled, run_count
  • workspaces: name, local_path, total_assets, total_vulns, risk_score, last_run

Function Evaluation CLI

Evaluate utility functions from the command line with bulk processing support:

New Scan Flags

  • -c, --concurrency - Number of targets to scan concurrently
  • --timeout - Scan timeout (e.g., 2h, 3h, 1d)
  • --repeat - Repeat scan after completion
  • --repeat-wait-time - Wait time between repeats (e.g., 30m, 1h, 1d)
  • -m can be specified multiple times to run modules in sequence

Debugging Tips

  • Use osmedeus --usage-example to see comprehensive examples for all commands
  • Use --verbose or --debug for detailed logging
  • Use --dry-run to preview scan execution without running commands
  • Use --log-file-tmp to create timestamped log files for debugging

Code Style

  • Use go fmt and golangci-lint
  • Follow Go naming conventions
  • Use structured logging with zap
  • Return errors, don’t panic
  • Use context for cancellation
  • Write tests for new features

Useful Commands