Module Development - Best Practices
Error Handling
Always call self.err(msg) and return immediately - never continue after an error.
result = self.exec("cat /etc/shadow")
if not result.success:
self.err(f"Could not read /etc/shadow (rc={result.returncode})")
return
Wrap network calls in try/except and surface the exception:
with self.spinner("Fetching release info..."):
try:
data = urllib.request.urlopen(url, timeout=15).read()
except Exception as exc:
self.err(f"Download failed: {exc}")
return
Post-upload Verification
AV/EDR may silently delete a binary after it lands on disk. Always verify.
# Linux
result = self.exec(f"test -s {dest} && echo OK || echo MISS")
if "OK" not in result.stdout:
self.err("File missing after upload.")
return
self.exec(f"chmod +x {dest}")
# Windows
time.sleep(1.0) # give AV time to act
check = self._win_query(f"(Test-Path '{dest}').ToString()")
if check.strip().lower() != "true":
self.err("File not present after upload, likely removed by AV.")
return
Remote Workspace Cleanup
If your module creates a temporary directory on the target, implement _cleanup() and call it on both success and early exit.
def run(self) -> None:
work = f".\\sh_{uuid.uuid4().hex[:8]}"
self._win_query(f"New-Item -ItemType Directory -Path '{work}' -Force | Out-Null")
if not self._upload_bytes(raw, f"{work}\\tool.exe"):
self.err("Upload failed.")
self._cleanup(work)
return
# ... rest of the logic ...
self._cleanup(work)
self.success("Done.")
def _cleanup(self, work_dir: str) -> None:
try:
self._win_query(
f"Remove-Item -Recurse -Force '{work_dir}' -ErrorAction SilentlyContinue"
)
except Exception:
pass
Windows Sessions
- Always use
_win_queryfor commands that return output. Never useexecon Windows. - Always use
_dispatch_psto send fire-and-forget PS commands. Never callsession.conn.sendall()directly. - Always use
_upload_bytesfor uploads. Never reimplement the TCP transfer. _win_queryexpressions must be single expressions with no top-level;. Use pipes instead of variable assignments at the outer level.- Upgraded (ConPtyShell) sessions return larger, more reliable results. Warn users if accuracy matters and the session is not upgraded.
Checklist
- [ ] Class inherits from
KoiModule, file is insrc/koi/modules/ - [ ]
name,description, andplatformare set correctly - [ ]
run()is implemented, no logic in__init__ - [ ]
exec()only called on Linux;_win_query()for Windows - [ ] File uploads use
self._upload_bytes()- not a manual reimplementation - [ ] Arbitrary PS commands use
self._dispatch_ps() - [ ] Every error path calls
self.err()andreturn - [ ] Remote temp workspaces are cleaned up on both success and failure
- [ ] Post-upload file existence is verified before continuing