Org (tenant) layer - New Org model with org_uuid denormalized onto workspaces, assets, vulnerabilities and runs so cross-workspace queries need no join - Automatic attribution via BeforeAppendModel hooks; importers stay org-unaware - Read semantics: empty org means no filter (backward compatible) Write semantics: empty org coerced to the default org - Migration backfills every pre-existing row into the default org - CLI: osmedeus org create/show/assign/use/rename/delete - API: /osm/api/orgs CRUD plus ?org= on assets, vulns, runs and workspaces npm distribution - npm install -g @j3ssie/osmedeus ships the Go binary through npm - One npm name with version-suffixed platform builds pulled in as aliased optionalDependencies, so an install downloads exactly one binary - Binary ships gzipped and is decompressed on first run into a version-scoped cache, so an upgrade can never exec a stale binary - make bump-version is the single source of truth for the version constant Bundled agent skills - public/skills/ embedded in the binary, installed via osmedeus skills install - Filesystem-driven discovery: a new bundle needs no code change - make sync-skills mirrors bundles out to the standalone skills repo Platform sub-projects - Vendor dashboard, registry and workflow under platform/ so they version with the engine they talk to; make sync-platform publishes them out - Rebuild the embedded UI in public/ui/
35 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build and Test Commands
# Build
make build # Build to bin/osmedeus
make build-all # Cross-platform builds (linux, darwin, windows)
# Test
make test-unit # Fast unit tests (no external dependencies)
make test-integration # Integration tests (requires Docker)
make test-e2e # E2E CLI tests (requires binary build)
make test-e2e-ssh # SSH E2E tests (module & step level SSH runner)
make test-e2e-api # API E2E tests (all endpoints with Redis + seeded DB)
make test-e2e-cloud # Cloud E2E tests (cloud CLI commands)
make test-sudo # Sudo-aware E2E tests (requires interactive sudo prompt)
make test-cloud # Cloud integration tests (internal cloud package)
make test-distributed # Distributed run e2e tests (requires Docker for Redis)
make test-docker # Docker runner tests
make test-ssh # SSH runner unit tests (starts test SSH container)
make test-canary-all # Canary tests: real scans in Docker (30-60min)
make test-canary-repo # Canary: SAST scan on juice-shop (~25min)
make test-canary-domain # Canary: domain recon on hackerone.com (~20min)
make test-canary-ip # Canary: CIDR scan on IP list (~25min)
make test-canary-general # Canary: domain-list-recon on hackerone.com subdomains (~40min)
go test -v ./internal/functions/... # Run tests for specific package
go test -v -run TestName ./... # Run single test by name
# Development
make fmt # Format code
make lint # Run golangci-lint
make tidy # go mod tidy
make run # Build and run
# Installation
make install # Install to $GOBIN (or $GOPATH/bin)
make swagger # Generate Swagger documentation
# Docker Toolbox
make docker-toolbox # Build toolbox image (all tools pre-installed)
make docker-toolbox-run # Start toolbox container
make docker-toolbox-shell # Enter toolbox container shell
# Docker Canary (real-world scan testing)
make canary-up # Build & start canary container
make canary-down # Stop & cleanup canary container
# UI
make update-ui # Build platform/osmedeus-dashboard and refresh public/ui/
make update-ui DASHBOARD_SKIP_BUILD=1 # Reuse an existing dashboard build
# Platform sub-projects
make sync-platform # Publish platform/* OUT to standalone repos
make sync-platform PLATFORM=osmedeus-registry # Just one
make sync-platform PLATFORM_COMMIT=1 # Also commit and push
# Agent Skills
make sync-skills # Push public/skills/ out to ../osmedeus-skills
make sync-skills SKILLS_DEST=/path/to/checkout # Push to a checkout elsewhere
# Release
make bump-version # Bump the patch in constants.go (v5.1.0 -> v5.1.1)
make bump-version PART=minor # v5.1.0 -> v5.2.0 (also: major|pre|release)
make bump-version SET=v5.1.0-rc.1 # Set an explicit version
make bump-version DRY_RUN=1 # Preview without writing
make github-release # Build and publish GitHub release (GoReleaser)
make npm-binaries # Cross-compile the 4 npm target platforms
make npm-build # Stage @j3ssie/osmedeus packages into build/dist-npm/
make npm-pack # npm-build + inspectable .tgz tarballs
make npm-publish # Publish to npm (needs NPM_TOKEN); DRY_RUN=1 to preview
Architecture Overview
Osmedeus is a workflow engine for security automation. It executes YAML-defined workflows with support for multiple execution environments.
Layered Architecture
CLI/API (pkg/cli, pkg/server)
↓
Executor (internal/executor) - coordinates workflow execution
↓
StepDispatcher - routes to: BashExecutor, FunctionExecutor, ForeachExecutor, ParallelExecutor, RemoteBashExecutor, HTTPExecutor, LLMExecutor, AgentExecutor, ACPExecutor
↓
Runner (internal/runner) - executes commands via: HostRunner, DockerRunner, SSHRunner
Core Packages
| Package | Purpose |
|---|---|
internal/core |
Type definitions: Workflow, Step, Trigger, RunnerConfig, ExecutionContext |
internal/parser |
YAML parsing, validation, and caching (Loader) |
internal/executor |
Workflow execution engine with step dispatching |
internal/runner |
Execution environments implementing Runner interface |
internal/template |
{{Variable}} interpolation engine |
internal/functions |
Utility functions via Goja JavaScript VM |
internal/scheduler |
Cron, event, and file-watch triggers (fsnotify-based) |
internal/database |
SQLite/PostgreSQL via Bun ORM |
pkg/cli |
Cobra CLI commands |
pkg/server |
Fiber REST API |
internal/snapshot |
Workspace export/import as compressed ZIP archives |
internal/installer |
Binary installation (direct-fetch and Nix modes) |
internal/state |
Run state export for debugging and sharing |
internal/updater |
Self-update functionality via GitHub releases |
internal/cloud |
Cloud infrastructure provisioning (DigitalOcean, AWS, GCP, Linode, Azure) |
public |
Embedded assets: UI, presets, base example, and coding-agent skills |
platform |
Vendored sub-projects: dashboard, registry, workflow (not Go code) |
Key Types
WorkflowKind: "module" | "flow" // module = single unit, flow = orchestrates modules
StepType: "bash" | "function" | "parallel-steps" | "foreach" | "remote-bash" | "http" | "llm" | "agent" | "agent-acp"
RunnerType: "host" | "docker" | "ssh"
TriggerType: "cron" | "event" | "watch" | "manual"
Decision Routing
Steps support conditional branching via decision field with switch/case syntax:
decision:
switch: "{{variable}}"
cases:
"value1": { goto: step-a }
"value2": { goto: step-b }
default: { goto: fallback }
Use goto: _end to terminate workflow.
Workflow Execution Flow
- CLI parses args ▷ loads config from
~/osmedeus-base/osm-settings.yaml - Parser loads YAML workflow, validates, caches in Loader
- Executor initializes context with built-in variables (
{{Target}},{{Output}}, etc.) - StepDispatcher routes each step to appropriate executor
- Runner executes commands, captures output
- Exports propagate to subsequent steps
Template System
{{Variable}}- standard template variables (Target, Output, threads, etc.)[[variable]]- foreach loop variables (to avoid conflicts)- Functions evaluated via Goja JS runtime:
file_exists(),file_length(),trim(),exec_python(),exec_ts(),detect_language(),extract_to(),db_import_sarif(),nmap_to_jsonl(),tmux_run(),ssh_exec(),skip(), etc.
Platform Variables
Built-in variables for environment detection:
{{PlatformOS}}- Operating system (linux, darwin, windows){{PlatformArch}}- CPU architecture (amd64, arm64){{PlatformInDocker}}- "true" if running in Docker container{{PlatformInKubernetes}}- "true" if running in Kubernetes pod{{PlatformCloudProvider}}- Cloud provider (aws, gcp, azure, local)
Agent Step Type
The agent step type provides an agentic LLM execution loop with tool calling, sub-agent orchestration, and memory management.
Key YAML fields:
query/queries- Task prompt (single or multi-goal)agent_tools- List of preset or custom tools available to the agentmax_iterations- Maximum tool-calling loop iterations (required, > 0)system_prompt- System prompt for the agentsub_agents- Inline sub-agents spawnable viaspawn_agenttool callmemory- Sliding window config (max_messages,summarize_on_truncate,persist_path,resume_path)models- Preferred models tried in order before falling back to defaultoutput_schema- JSON schema enforced on final outputplan_prompt- Optional planning stage prompt run before the main loopstop_condition- JS expression evaluated after each iterationon_tool_start/on_tool_end- JS hook expressions for tool call tracingparallel_tool_calls- Enable/disable parallel tool execution (default: true)
Preset tools: bash, read_file, read_lines, file_exists, file_length, append_file, save_content, glob, grep_string, grep_regex, http_get, http_request, jq, exec_python, exec_python_file, exec_ts, exec_ts_file, run_module, run_flow
Available exports: agent_content, agent_history, agent_iterations, agent_total_tokens, agent_prompt_tokens, agent_completion_tokens, agent_tool_results, agent_plan, agent_goal_results
steps:
- name: analyze-target
type: agent
query: "Enumerate subdomains of {{Target}} and summarize findings."
system_prompt: "You are a security reconnaissance agent."
max_iterations: 10
agent_tools:
- preset: bash
- preset: read_file
- preset: save_content
memory:
max_messages: 30
persist_path: "{{Output}}/agent/conversation.json"
exports:
findings: "{{agent_content}}"
Agent-ACP Step Type
The agent-acp step type spawns an external AI coding agent as a subprocess and communicates via the Agent Communication Protocol (ACP). Unlike the agent step type (which uses the internal LLM loop), agent-acp delegates to real agent binaries.
Built-in agents (defined in internal/executor/acp_executor.go):
claude-code—npx -y @zed-industries/claude-code-acp@latestcodex—npx -y @zed-industries/codex-acpopencode—opencode acpgemini—gemini --experimental-acp
Key YAML fields:
agent- Built-in agent name (required unlessacp_config.commandis set)messages- Conversation messages (role + content) used as the promptcwd- Working directory for the ACP sessionallowed_paths- Restrict file reads to these directoriesacp_config.command- Custom agent command (overrides built-in registry)acp_config.args- Custom agent command argumentsacp_config.env- Environment variables for the agent processacp_config.write_enabled- Allow file writes (default: false)
Available exports: acp_output, acp_stderr, acp_agent
steps:
- name: acp-agent
type: agent-acp
agent: claude-code
cwd: "{{Output}}"
allowed_paths:
- "{{Output}}"
acp_config:
env:
CUSTOM_VAR: "hello"
write_enabled: true
messages:
- role: system
content: "You are a security analyst."
- role: user
content: "Analyze the scan results in {{Output}} and create a summary."
exports:
analysis: "{{acp_output}}"
Agent CLI Command
Run an ACP agent interactively from the terminal:
osmedeus agent "your message here" # Run with claude-code (default)
osmedeus agent --agent codex "your message" # Use a specific agent
osmedeus agent --list # List available agents
osmedeus agent --cwd /path/to/project "msg" # Set working directory
osmedeus agent --timeout 1h "msg" # Custom timeout (default: 30m)
echo "message" | osmedeus agent --stdin # Read from stdin
Org (Tenant) Layer
Orgs group multiple workspaces so assets, findings and runs can be queried across all of them at once — a company with many root domains gets one org spanning every workspace.
- Model:
Orgininternal/database/models.go(UUID pk, unique name).org_uuidis a denormalized column onworkspaces,assets,vulnerabilitiesandruns(orgScopedTablesininternal/database/database.go), so cross-workspace queries need no join. - Default org:
DefaultOrgUUID=00000000-0000-0000-0000-000000000001, seeded on migrate. Cannot be deleted or renamed. - Two different empty semantics — this is the backward-compatibility contract:
- Read: empty org means no filter, so queries span every org exactly as they did before orgs existed.
- Write: empty org is coerced to the default org, never stored blank.
- Migration:
addOrgUUIDColumnsaddsorg_uuid TEXT NOT NULL DEFAULT '<default>', so every pre-existing row is attributed to the default org automatically.backfillOrgUUIDthen reclaims rows holding an explicit empty string (Bun writes the Go zero value, bypassing the column DEFAULT). - Attribution is automatic:
BeforeAppendModelhooks ininternal/database/org_hooks.goresolve an unsetorg_uuidfrom the row's workspace on insert and update, so importers need no org awareness and a re-scan never evicts a row from its org. Writes that deliberately setorg_uuiduse raw SQL without a model, so the hooks do not fire on them. - Resolution order (
pkg/cli/org_resolve.go):--orgflag →$OSMEDEUS_ORG_UUID→$OSMEDEUS_ORG→{{base_folder}}/.active-org→ unset (no filter).--orgaccepts a name or a UUID. - CLI:
pkg/cli/org.go. API:pkg/server/handlers/orgs.go, plus?org=on the assets, vulnerabilities, runs and workspaces endpoints.
Re-scanning a workspace without --org deliberately leaves its org alone;
only an explicit --org moves it (EnsureWorkspaceRuntimeWithOrg).
Platform Sub-Projects
platform/ holds the non-Go sub-projects that ship alongside the engine, vendored
into this repo so they version together with the code they talk to.
| Directory | Standalone repo | Purpose |
|---|---|---|
platform/osmedeus-dashboard |
osmedeus/osmedeus-dashboard |
Next.js UI; built into public/ui/ and go:embeded |
platform/osmedeus-registry |
osmedeus/osmedeus-registry |
Binary registry metadata and install scripts |
platform/osmedeus-workflow |
osmedeus/osmedeus-workflow |
Public workflow collection |
- This repo is the source of truth. Edit under
platform/, thenmake sync-platformpublishes to the standalone repos. It writes files only — review and commit there yourself unless you passPLATFORM_COMMIT=1. - No nested
.git. The sub-projects are plain directories here; their history lives in the standalone repos. - Build outputs are gitignored (
node_modules/,.next/,build/,out/)..dockerignoreexcludesplatform/wholesale so an installednode_modulesnever enters the Docker build context. .agents/is un-ignored underplatform/(!platform/*/.agents/) because the dashboard tracks its agent skills upstream — ignoring them here would makesync-platform --deletewipe them.- The dashboard is not the embedded UI.
public/ui/is the built output and is what gets embedded;make update-uirebuilds it fromplatform/.
Targets that consume platform/ rather than a sibling checkout:
| Target / file | Reads from |
|---|---|
make update-ui |
platform/osmedeus-dashboard (builds, then copies to public/ui/) |
make snapshot-release |
$(REGISTRY_DIR) = platform/osmedeus-registry for registry-metadata-direct-fetch.json and install.sh |
build/docker/docker-compose.canary.yaml |
platform/osmedeus-workflow mounted as the canary's workflows |
Adding a new consumer? Point it at platform/<name>/, never ../<name>/ — the
sibling checkout may not exist on a fresh clone.
Note make sync-skills is separate and stays that way: skills live in
public/skills/ because they are go:embeded, so they are not a platform/
sub-project. Both sync targets default to publishing into the directory that
holds this repo.
Bundled Agent Skills
Skill bundles that teach an AI coding agent how to write osmedeus workflows and
drive the CLI are embedded in the binary and installed via osmedeus skills install.
Because they ship inside the binary, an installed skill always matches the running version.
- Content:
public/skills/<bundle>/—SKILL.md(YAML frontmatter:name,description) plus optionalreferences/*.md - Embed:
//go:embed all:skillsinpublic/embed.go, read frompublic.EmbedFSunderskills/ - Implementation:
pkg/cli/skills.go—list,get,installsubcommands - Discovery is filesystem-driven: any directory under
public/skills/containing aSKILL.mdis a bundle, so adding one needs no code change - Install destinations:
--agent claude→.claude/skills/,--agent codex|agents→.agents/skills/;--scope project(cwd) orglobal(home) osmedeus install skillsis a thin alias sharingRunSkillsInstall, mirroring theworkflow install/RunInstallWorkflowpattern (a cobra command has one parent)
public/skills/ is authored in-tree — it is the source of truth, so a skill
change ships in the same commit as the code it documents. The standalone repo
https://github.com/osmedeus/osmedeus-skills is a published mirror (it lets agents
install a skill without osmedeus); make sync-skills pushes this directory out to
a local checkout of it, writing bundle directories only — never the destination's
README.md, which is its own public landing page.
Installing into a project's .claude/skills/ also benefits osmedeus agent, which
spawns claude-code/codex via ACP in that directory.
npm Distribution
npm install -g @j3ssie/osmedeus ships the Go binary through npm. Everything
lives under build/npm/ and is driven by the npm-* make targets.
- One npm name, version-suffixed platform builds (codex-style): the launcher
publishes as
@j3ssie/osmedeus@<version>, each platform build as@j3ssie/osmedeus@<version>-<tag>for the four tagslinux-x64,linux-arm64,darwin-x64,darwin-arm64. The launcher pulls its own build in as an aliased optionalDependency ("@j3ssie/osmedeus-linux-x64": "npm:@j3ssie/osmedeus@<version>-linux-x64"), so an install downloads exactly one binary. - The binary ships gzipped (
vendor/<tag>/osmedeus.gz, ~50MB vs ~240MB raw) because the UI, presets and skills are embedded.bin/osmedeus.jsdecompresses it on first run into~/.osmedeus/npm-bin/<version>/<tag>/(override with$OSMEDEUS_NPM_HOME) — version-scoped, so an upgrade can never exec a stale binary — thenspawns it, forwarding args, stdio, signals and the exit status. - Publish order matters: platform packages first, then the launcher, else its
optionalDependencies do not resolve.
npm-publishdoes this, then pins and verifies thelatestdist-tag (retrying through registry cache lag). - Version source is
VERSIONininternal/core/constants.go, minus thev— bump it withmake bump-version(build/scripts/bump-version.sh), which is the only thing that should rewrite that constant. npm versions are immutable, sobuild.mjsrefuses to pack unless the.build-versionstampmake npm-binarieswrites matches the version being published. Never verify a build by grepping the binary for a version string — the embedded docs/presets/UI mention other versions and it false-matches. - Files:
build/npm/build.mjs(staging),build/npm/bin/osmedeus.js(launcher — kept out of thebin/gitignore rule by a!build/npm/bin/negation), output inbuild/dist-npm-bin/(binaries) andbuild/dist-npm/(staged packages), both gitignored.
CLI Commands
osmedeus run -f <flow> -t <target> # Run flow workflow
osmedeus run -m <module> -t <target> # Run module workflow
osmedeus run -m <m1> -m <m2> -t <target> # Run multiple modules in sequence
osmedeus run -m <module> -t <target> --timeout 2h # With timeout
osmedeus run -m <module> -t <target> --repeat # Repeat continuously
osmedeus run -m <module> -T targets.txt -c 5 # Concurrent target scanning
osmedeus run -m <module> -t <target> -P params.yaml # With params file
osmedeus workflow list # List available workflows
osmedeus workflow show <name> # Show workflow details
osmedeus workflow validate <name> # Validate workflow YAML
osmedeus func list # List utility functions
osmedeus func e 'log_info("{{target}}")' # Evaluate function
osmedeus --usage-example # Show all usage examples
osmedeus server # Start REST API (see docs/api/ for endpoints)
osmedeus server --master # Start as distributed master
osmedeus worker join # Join as distributed worker (ID: wosm-<uuid8>)
osmedeus worker join --get-public-ip # Join with public IP detection (alias: wosm-<ip>)
osmedeus install binary --name <name> # Install specific binary
osmedeus install binary --all # Install all binaries
osmedeus install binary --name <name> --check # Check if binary is installed
osmedeus install binary --all --check # Check all binaries status
osmedeus install binary --nix-build-install # Install binaries via Nix
osmedeus install binary --nix-installation # Install Nix package manager
osmedeus install binary --list-registry-nix-build # List Nix binaries
osmedeus install binary --list-registry-direct-fetch # List direct-fetch binaries
osmedeus install base --preset # Install base from preset repository
osmedeus install base --preset --keep-setting # Install base, restore previous osm-settings.yaml
osmedeus install workflow --preset # Install workflows from preset repository
osmedeus install validate --preset # Validate/install ready-to-use base
osmedeus install env # Add binaries to PATH (auto-detects shell)
osmedeus install env --all # Add to all shell configs
osmedeus skills # List bundled coding-agent skills (alias: skill)
osmedeus skills list --json # List skills as JSON
osmedeus skills get <name> # Print a skill's SKILL.md to stdout
osmedeus skills get <name> --full # Include reference files
osmedeus skills get --all # Print every bundled skill
osmedeus skills install # Install default skill into ./.claude/skills/
osmedeus skills install --scope global # Install into ~/.claude/skills/
osmedeus skills install --agent codex # Install into .agents/skills/ instead
osmedeus skills install --all --force # Install every bundle, overwriting
osmedeus skills install --dir <path> # Install to an explicit directory
osmedeus install skills # Alias for 'osmedeus skills install'
osmedeus update # Self-update to latest version
osmedeus update --check # Check for updates without installing
osmedeus snapshot export <workspace> # Export workspace as ZIP
osmedeus snapshot import <source> # Import from file or URL
osmedeus snapshot list # List available snapshots
osmedeus run -m <module> -t <target> -G # Run with progress bar (shorthand)
osmedeus run -f <flow> -t <target> -x <module> # Exclude module(s) from flow
osmedeus run -f <flow> -t <target> -X <substr> # Fuzzy-exclude modules by substring
osmedeus worker status # Show registered workers
osmedeus worker eval -e '<expr>' # Evaluate function with distributed hooks
osmedeus worker set <id> <field> <value> # Update worker metadata
osmedeus worker queue list # List queued tasks
osmedeus worker queue new -f <flow> -t <target> # Queue task for delayed execution
osmedeus worker queue run --concurrency 5 # Process queued tasks
osmedeus assets # List discovered assets
osmedeus assets -w <workspace> # Filter assets by workspace
osmedeus assets --source httpx --type web # Filter by source and type
osmedeus assets --stats # Show asset statistics
osmedeus assets --stats -w <workspace> # Stats filtered by workspace
osmedeus assets --columns url,title,status_code # Custom columns
osmedeus assets --limit 100 --offset 50 # Pagination
osmedeus assets --json # JSON output
osmedeus assets --org acme # Assets across every workspace in an org
osmedeus org # List orgs (alias: orgs, tenant)
osmedeus org create acme -d "ACME Corp" # Create an org
osmedeus org show acme # Org details, counts and workspaces
osmedeus org assign acme -w acme.com -w acme.io # Group existing workspaces into an org
osmedeus org use acme # Set the active org (eval $(...) to export)
osmedeus org use --clear # Clear the active org
osmedeus org rename acme acme-corp # Rename an org
osmedeus org delete acme # Delete org, data moves to default org
osmedeus org delete acme --purge # Delete org and all its data
osmedeus run -m <module> -t <target> --org acme # Attribute a scan to an org
osmedeus agent "your prompt" # Run ACP agent (default: claude-code)
osmedeus agent --agent codex "your prompt" # Use a specific agent
osmedeus agent --list # List available agents
osmedeus agent --cwd /path/to/project "prompt" # Set working directory
osmedeus agent --timeout 1h "prompt" # Custom timeout (default: 30m)
echo "prompt" | osmedeus agent --stdin # Read from stdin
Event Trigger Input Syntax
Event triggers support two syntaxes for extracting variables:
New exports-style syntax (multiple variables):
triggers:
- name: on-new-asset
on: event
event:
topic: assets.new
input:
target: event_data.url
description: trim(event_data.desc)
source: event.source
Legacy syntax (single input):
input:
type: event_data
field: url
name: target
API Documentation
REST API documentation with curl examples is in docs/api/. Key endpoint categories:
- Runs: Create, list, cancel (with PID termination), get steps/artifacts
- Workflows: List, get details, refresh index
- Schedules: Full CRUD + enable/disable/trigger
- Assets/Workspaces: Query discovered data
- Orgs: Group workspaces under a tenant; full CRUD + workspace assignment.
?org=filters assets, vulnerabilities, runs and workspaces - Event Logs: Query execution events
- Functions: Execute utility functions via API
- Snapshots: Export/import workspace archives
- LLM: OpenAI-compatible chat completions and embeddings
- Agent ACP: OpenAI-compatible endpoint that spawns local ACP agent subprocesses (
POST /osm/api/agent/chat/completions) - Install: Binary registry and installation management
Cloud Documentation
Cloud infrastructure enables distributed security scanning across multiple providers (DigitalOcean, AWS, GCP, Linode, Azure):
- Usage Examples:
docs/cloud-usage-examples.md- Comprehensive examples with copy-paste commands for all cloud operations - Quick Reference:
docs/cloud-quick-reference.md- Fast lookup for common commands and configurations - Cheatsheet:
docs/cloud-cheatsheet.md- Single-page printable reference card - Architecture:
docs/cloud-usage-guide.md- Cloud feature architecture and design - Test Documentation:
test/e2e/CLOUD_TESTS_README.md- Cloud test coverage and patterns - Config Template:
public/presets/cloud-settings.example.yaml- Full configuration example
Key cloud commands:
osmedeus cloud config set <key> <value> # Configure cloud provider
osmedeus cloud create --instances N # Provision infrastructure
osmedeus cloud list # List active infrastructure
osmedeus cloud run -f <flow> -t <target> --instances N # Run distributed workflow
osmedeus cloud destroy <id> # Destroy infrastructure
Workflow Hooks
Workflows support pre/post execution hooks via the hooks field:
hooks:
pre_scan_steps:
- name: setup
type: bash
command: echo "Starting scan"
post_scan_steps:
- name: cleanup
type: bash
command: echo "Scan complete"
Hooks are defined using WorkflowHooks in internal/core/workflow.go. Pre-scan steps run before the main steps, post-scan steps run after completion.
Queue System
Delayed task execution via database and Redis queues:
osmedeus worker queue new -f <flow> -t <target>- Queue a task (creates Run withis_queued=true)osmedeus worker queue run --concurrency N- Process queued tasks with configurable parallelism- Dual-source polling: database (every 5s) + Redis BRPOP (optional)
- Deduplication via runUUID tracking
- Implementation in
pkg/cli/worker_queue.go
Nmap Integration
Utility functions for nmap port scanning and result processing:
nmap_to_jsonl(input_path, output_path)- Convert nmap XML/gnmap to JSONL formatrun_nmap(target, flags?, output?)- Execute nmap and auto-convert results to JSONLdb_import_port_assets(workspace, file_path, source?)- Import port scan JSONL into database
Tmux Session Management
Functions for managing long-running background processes:
tmux_run(command, session_name?)- Create detached tmux session (auto-generatesbosm-<random8>name)tmux_capture(session_name)- Capture pane output (pass"all"for all sessions)tmux_send(session_name, command)- Send keystrokes to sessiontmux_kill(session_name)/tmux_list()- Kill session / list all sessions
SSH & Distributed Sync Functions
Functions for remote execution and file synchronization:
ssh_exec(host, command, user?, key_path?, password?, port?)- Execute command via SSH (pooled connections)ssh_rsync(host, src, dest, user?, key_path?, password?, port?)- Copy files via rsync+SSHsync_from_master(src, dest)- Pull files from master node (falls back to local cp)sync_from_worker(identifier, ip, src, dest)/rsync_to_worker(...)- Sync with specific workers
TypeScript Execution
exec_ts(code)- Run inline TypeScript code viabun -eexec_ts_file(path)- Run a TypeScript file viabun run
Skip Module Control
skip(message?)- Abort remaining steps in current module; flow continues to next module- Raises
ErrSkipModule/SkipModuleError(defined ininternal/functions/constants.go)
Module Exclusion
-x, --exclude <module>- Exclude module(s) from flow execution (exact match, repeatable)-X, --fuzzy-exclude <substr>- Exclude modules whose name contains substring (repeatable)
Webhook Triggers
API endpoints for triggering runs via webhooks:
GET /osm/api/webhook-runs- List webhook-enabled runsGET|POST /osm/api/webhook-runs/{uuid}/trigger- Trigger run via webhook UUID (unauthenticated)- Runs store
webhook_uuidand optionalwebhook_auth_keyfor authentication
CDN/WAF Asset Classification
Assets now include CDN/WAF classification fields derived from httpx JSON data:
is_cdn- Asset is behind a CDN (cdn=trueorcdn_namenon-empty in httpx)is_cloud- CDN name matches a cloud provider (AWS, GCP, Azure, etc.)is_waf-cdn_typeequals "waf" in httpx data
Adding New Features
New Step Type: Add constant in core/types.go, create executor implementing StepExecutor interface in internal/executor/, register in PluginRegistry via dispatcher.go
New Runner: Implement Runner interface in internal/runner/, add type constant, register in runner factory
New CLI Command: Create in pkg/cli/, add to rootCmd in init()
New API Endpoint: Add handler in pkg/server/handlers/, register route in server.go, document in docs/api/
New Utility Function: Add Go implementation in internal/functions/, register in goja_runtime.go, add constant in constants.go
New Agent Preset Tool: Add to PresetToolRegistry in internal/core/agent_tool_presets.go, add case in buildPresetCallExpr() in internal/executor/agent_executor.go
New Bundled Skill: Add a directory with a SKILL.md under public/skills/, then run make sync-skills to mirror it out to the skills repo. No Go changes needed — discovery finds any such directory.
Architecture Notes
- Executor: Fresh instances created per target/request - no global singleton
- Step Dispatcher: Uses plugin registry pattern for extensible step type handling
- Scheduler: File watching uses fsnotify for instant inotify-based notifications
- Decision Routing: Uses switch/case syntax for conditional workflow branching
- Run Registry: Tracks active runs with PID management for cancellation support
- Write Coordinator: Batches database writes (step results, progress, artifacts) reducing I/O by ~70%
- Install Base Backup:
InstallBase()automatically backs uposm-settings.yamltobackup-osm-settings.yaml;--keep-settingflag restores the previous settings after installation - Worker Identity: Worker IDs use
wosm-<uuid8>format; default alias iswosm-<public-ip>orwosm-<local-ip>when no--aliasis provided - Execute Hooks: Distributed coordination via
RegisterExecuteHooks()ininternal/functions/execute_hooks.go- avoids circular imports between functions and distributed packages - Queue System: Dual-source polling (DB + Redis) with deduplication and configurable concurrency in
pkg/cli/worker_queue.go - Command Fallback:
internal/executor/cmd_fallback.gohandles timeout prefix stripping and custom binary path prepending - Bundled Skills:
public/skills/is the source of truth, embedded via//go:embed all:skills; edit in place, thenmake sync-skillspushes the bundles out to the standalone osmedeus-skills repo (a published mirror) for review and commit there - npm Distribution: run
make bump-versionbeforemake npm-publish— npm versions are immutable, so a version can only be superseded, never re-published.make npm-binariesrestampsbuild/dist-npm-bin/.build-version; the guard inbuild.mjsfails closed if it does not match - Org Attribution: never set
org_uuidmanually in a new import path — theBeforeAppendModelhooks derive it from the row's workspace. If you add an org-scoped table, add it toorgScopedTablesand give it aBeforeAppendModelhook, or its rows will be invisible to every org query
SARIF Integration
Utility functions for parsing SARIF (Static Analysis Results Interchange Format) output from SAST tools:
db_import_sarif(workspace, file_path)- Import vulnerabilities from SARIF into database (supports Semgrep, Trivy, Kingfisher, Bearer)convert_sarif_to_markdown(input_path, output_path)- Convert SARIF to readable markdown tablesdetect_language(path)- Detect dominant programming language of a source folder (26+ languages)extract_to(source, dest)- Auto-detect archive format (.zip, .tar.gz, .tar.bz2, .tar.xz, .tgz) and extractnmap_to_jsonl(input, output)- Convert nmap XML/gnmap to JSONLdb_import_port_assets(workspace, file_path, source?)- Import nmap JSONL into database as IP assets
Performance Optimizations
- Compiled JS caching: Loop conditions compiled once and cached (60-80% faster)
- Parallel shard rendering: Template rendering uses parallel shards (20-40% faster startup)
- Memory-mapped I/O: Large files (>1MB) use mmap for 40-60% faster line counting
- Efficient output buffering: Runners use optimized buffer combining