The repo had no SECURITY.md, so GitHub had no "report a vulnerability" path and nothing stated which code execution is intentional. Reviewing #311 showed the cost: the design line between "the workflow engine ran the command in the workflow" and a real bug was only in people's heads. Expands on docs.osmedeus.org/others/security-warning with the parts that need to live next to the code: - a trust table for each input class, and the test for whether something is a bug: did the operator ask for this code to run? - what is intentional (workflows, func eval, install commands including a caller-supplied registry_url, worker dispatch, --no-auth, webhook triggers) - what is a vulnerability (execution from a read-only surface, execution from target data, auth bypass, token leakage to the wrong host, traversal, SSRF the operator did not ask for) - known limitations: the session cookie is SameSite=Lax with reflect-all CORS and is not HTTPOnly, so it is not CSRF-hardened - invariants for contributors, including the two that #311 and its follow-ups turned into code: GET never executes, and only a trusted registry may be Also links it from the README documentation table and disclaimer.
9.5 KiB
Security Policy
Osmedeus is a workflow engine that runs commands on your machine. That is what it is for. This document draws the line between the code execution that is intentional and the code execution that is a vulnerability, so operators know what they are running and reporters know what is worth reporting.
The narrative version of the operator guidance lives at docs.osmedeus.org/others/security-warning. This file is the canonical security model for the repository.
Supported versions
Security fixes land on main and ship in the next release. Only the latest release is supported —
if you are running an older tag, upgrade with osmedeus update before reporting.
Reporting a vulnerability
Report through GitHub Security Advisories.
Please do not open a public issue, and please do not disclose publicly until a fix is available. Include the endpoint or workflow involved, a reproduction, and what an attacker gains. A working proof of concept gets a fix out much faster than a description of one.
Before reporting, check the Intentional by design list below. Reports that amount to "the workflow engine ran the command in the workflow" are closed as working-as-intended.
The trust model
Osmedeus executes code because the operator asked it to. The operator is the person who runs the binary, writes the workflows, and holds the API credentials. Everything the operator authors is trusted; everything that arrives from outside is not.
| Input | Trust | Rule |
|---|---|---|
| Workflow YAML the operator wrote or installed | Trusted | May execute anything |
The embedded binary registry (public/presets/) |
Trusted | Its valide-command may be executed |
| CLI flags and local config | Trusted | Operator is at the keyboard |
A registry supplied through the API (registry_url) |
Untrusted | Never execute its entries |
| Scan targets, and anything a tool discovers about them | Untrusted | Never reaches a shell as code |
| Tool output parsed back in (SARIF, nmap XML, httpx JSON) | Untrusted | Data only |
| Unauthenticated HTTP requests | Untrusted | Must not cause execution |
The test for whether something is a bug: did the operator ask for this code to run? Osmedeus running a command from a workflow the operator installed is the product. The same command running because someone else triggered it, because a scan target was named a certain way, or because an endpoint documented as read-only executed something — that is a vulnerability.
Intentional by design
These are not vulnerabilities. They are the tool working.
Workflows execute arbitrary code. bash, remote-bash, function, agent, and agent-acp
steps run commands, scripts, and LLM-driven tool calls with the privileges of the Osmedeus process.
Never run a workflow you have not read. This is the same posture as Airflow, Argo, GitHub Actions,
and Jenkins.
Utility functions execute code. osmedeus func e '<expr>' and the functions API evaluate
expressions through a JavaScript runtime that includes exec_python(), exec_ts(), tmux_run(),
and ssh_exec(). Anyone who can call these can run commands. That is the feature.
Installing binaries executes commands. osmedeus install and
POST /osm/api/registry-install download binaries and run their install commands — including from a
registry you point at with registry_url. Installing is executing; a caller who can reach the
install endpoint and supply a registry can run commands by design. The CLI prints a security warning
before it does this. Treat install access as equivalent to shell access, and see the read-only
carve-out in the next section.
Distributed workers execute dispatched work. A worker that joins a master runs what the master
sends. ssh_exec() and the rsync helpers reach configured hosts. Only join masters you control.
--no-auth disables authentication. It exists for isolated development. Using it on a reachable
interface is an operator error, not a bug.
Webhook triggers are unauthenticated. When server.enable_trigger_via_webhook is on,
/osm/api/webhook-runs/{uuid}/trigger starts a run for anyone holding the UUID (plus the optional
auth key). The CLI warns about this when it prints a webhook URL. The UUID is the credential.
Scans look like attacks. Osmedeus sends traffic that IDS/WAF products will flag. Get authorization for every target before you scan it.
What is a vulnerability
Report these:
- Execution from a read-only surface. Any
GET, or any endpoint documented as returning information, that causes a command to run. Read-only means read-only. - Execution from untrusted data. A scan target, a hostname a tool discovered, a filename in an archive, or a field in parsed tool output reaching a shell as code.
- Execution from an untrusted registry. Entries loaded from a caller-supplied
registry_urlbeing executed anywhere other than an explicit install request. - Authentication bypass. Reaching an authenticated endpoint without credentials, forging a JWT, or defeating the API key check.
- Credential leakage.
GITHUB_API_KEY, cloud provider keys, or LLM API keys being sent to a host other than the intended one — including via URL parsing tricks, redirects, or log output. - Path traversal. API parameters reading or writing outside the workspace, or archive extraction escaping its destination directory.
- SSRF where the operator supplied no URL. The operator pointing Osmedeus at an internal host is the feature; a request to an internal host they did not name is not.
- Privilege escalation between workspaces, users, or workers.
Hardening a deployment
Fresh installs generate random values for auth_api_key (32 chars), jwt.secret_signing_key
(64 chars), and the default user password (12 chars), so there are no shipped default credentials.
To rotate them:
osmedeus config set server.password "$(openssl rand -hex 12)"
osmedeus config set server.jwt.secret_signing_key "$(openssl rand -hex 32)"
osmedeus config set server.auth_api_key "$(openssl rand -hex 24)"
| Area | Do this |
|---|---|
| Exposure | Never put the API on the public internet. Bind to localhost or a private interface; reach it over a VPN or SSH tunnel |
| Transport | Terminate TLS at a reverse proxy in front of the server |
| Auth | Keep server.enabled_auth_api on and use the x-osm-api-key header for automation |
| Privileges | Run as a dedicated non-root user with the minimum filesystem access it needs |
| Workflows | Review before running; keep them in version control; osmedeus workflow validate <name> |
| Binaries | Install from the embedded registry or a registry you host; prefer Nix builds for reproducibility |
| Database | PostgreSQL with TLS in production; encrypt backups |
| Monitoring | Enable logging and audit access to the API |
Known limitations
Be aware of these when deciding how to expose the server:
- The browser session is not CSRF-hardened. The
osmedeus_sessioncookie isSameSite=Laxand CORS reflects any origin with credentials allowed. A top-level navigation from another site will carry the cookie. Prefer API-key auth for anything scripted, and do not leave a dashboard session open in a browser you also use for general browsing while the server is reachable. - The session cookie is readable by JavaScript (
HTTPOnly=false, so the UI can read login state) and is not markedSecure— another reason to terminate TLS at a proxy and keep the server off shared networks.
For contributors
Invariants to preserve when touching these areas. Breaking one is a vulnerability, not a style issue:
GEThandlers never execute. If a handler can reachexec.Command,sh -c, or an installer command path, it must be aPOSTthat says what it does.- Only a trusted registry may be executed. Use
installer.IsBinaryInstalledfor the embedded registry andinstaller.IsBinaryInstalledNoExecwhen the source came from a caller. If you add a code path that runs anything out of aBinaryEntry, gate it on the registry's provenance. - Match hosts, never substrings, before attaching a token. Use
installer.IsGitHubURL, which compares the parsed hostname.strings.Contains(url, "github.com")sends your token toevil.tld/?x=github.com. - Target data is data. Interpolating
{{Target}}or a discovered hostname into a shell command is the highest-risk pattern in the codebase. Quote it, or pass it through a file. - Bound reads from the network. Wrap response bodies in
io.LimitReader; a caller-supplied URL should not be able to exhaust memory. - New endpoints go in
docs/api/with their auth requirements and any execution side effects stated explicitly.
Disclaimer
Osmedeus is for authorized security testing only. Unauthorized use may violate the law where you live. By using it you accept that:
- You need authorization. Explicit permission before scanning any target, every time.
- You are responsible. For legal compliance and for every consequence of running this tool.
- There is no warranty. Provided "AS IS". The authors are not liable for damages, claims, or legal trouble arising from its use.
- It executes code by design. Review workflows before you run them.
- Third-party tools have their own terms. Comply with the licenses of everything Osmedeus integrates.