Skip to content

fix: use Cmd2ArgumentParser so the shell works on cmd2 >=4.0 (#124) - #129

Open
chryzsh wants to merge 1 commit into
garrettfoster13:mainfrom
chryzsh:fix/cmd2-argument-parser-compat
Open

chryzsh wants to merge 1 commit into
garrettfoster13:mainfrom
chryzsh:fix/cmd2-argument-parser-compat

Conversation

@chryzsh

@chryzsh chryzsh commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

AI-assisted contribution — please read before reviewing

This patch was developed with AI assistance (Claude/Anthropic), directed and tested by me. I'm submitting it in good faith that it's correct, not as an expert assertion that it is. Please treat it with whatever scrutiny you'd want for an AI-assisted contribution.

What this fixes

Closes #124. cmd2 ≥4.0 requires every parser factory used with @cmd2.with_argparser to return a Cmd2ArgumentParser (or subclass) — a plain argparse.ArgumentParser now raises a TypeError from cmd2's internal _build_parser. All 17 parser factories in lib/parsers/parsers.py returned a plain argparse.ArgumentParser, so on cmd2 ≥4.0 every single command in the interactive shell is broken, not just get_device — get_user, get_collection, cd, interact, sessionhunter, application, the script commands, the admin commands, all of it.

The fix is: switch all 17 factories to build a cmd2.Cmd2ArgumentParser instead. Also fixed do_decrypt_parsers(), which was missing its return parser entirely — currently dead code (not wired to any command), but a real bug sitting in the same file I was already touching.

Also relaxed the requirements.txt pin. It already had cmd2<4.0.0 (the workaround mentioned in the issue), while pyproject.toml had no upper bound (cmd2>=2.7.0) — so which cmd2 version you got, and whether you hit this bug at all, depended on which of the two files your install path used. Now that the actual incompatibility is fixed, I aligned both to cmd2>=2.7.0 with no upper bound, rather than leaving a workaround pin in place indefinitely for a bug that's now fixed. Happy to leave the pin in place instead if you'd rather be conservative here — this part is more of a judgment call than the rest of the fix.

Reproduction: unpatched main, real cmd2 4.2.2, against a real lab

The dev environment here has cmd2 2.7.0 installed (which doesn't hit the bug), so I built a throwaway venv with real cmd2 4.2.2 to actually reproduce and verify this, rather than just trusting the traceback in the issue.

$ printf 'interact SMS00001\nget_device ps1-pss\nexit\n' | python sccmhunter.py admin -u domainjoin -p '<redacted>' -ip ps1-pss.mayyhem.com -d mayyhem.com -dc dc.mayyhem.com
SCCMHunter v2.0.0 by @unsigned_sh0rt
[!] Enter help for extra shell commands
TypeError: 'interact_parser' must return a 'Cmd2ArgumentParser' (or subclass).
Received: 'ArgumentParser'.

TypeError: 'get_device_parser' must return a 'Cmd2ArgumentParser' (or subclass).
Received: 'ArgumentParser'.

I also checked this wasn't limited to get_device/interact by instantiating the real SHELL class from lib/attacks/admin directly (no network) and running a representative set of commands under cmd2 4.2.2 — every single one failed with the same TypeError:

get_device mc          -> TypeError: 'get_device_parser' must return a 'Cmd2ArgumentParser' ...
get_user someuser       -> TypeError: 'get_user_parser' must return a 'Cmd2ArgumentParser' ...
get_collection SMS00001 -> TypeError: 'get_collection_parser' must return a 'Cmd2ArgumentParser' ...
cd C:\                  -> TypeError: 'cd_parser' must return a 'Cmd2ArgumentParser' ...
interact SMS00001       -> TypeError: 'interact_parser' must return a 'Cmd2ArgumentParser' ...
sessionhunter someuser  -> TypeError: 'do_sessionhunter_parser' must return a 'Cmd2ArgumentParser' ...
cat somefile.ps1        -> TypeError: 'cat_parser' must return a 'Cmd2ArgumentParser' ...

After the fix: same lab, same cmd2 4.2.2 venv

$ printf 'interact SMS00001\nget_device ps1-pss\nexit\n' | python sccmhunter.py admin -u domainjoin -p '<redacted>' -ip ps1-pss.mayyhem.com -d mayyhem.com -dc dc.mayyhem.com
SCCMHunter v2.0.0 by @unsigned_sh0rt
[!] Enter help for extra shell commands
[*] Collecting device...
'value'
[-] Could not find device: ps1-pss

No TypeError — the command reaches the actual AdminService query and fails only on the lookup itself (ps1-pss isn't an exact match in this environment's SMS_R_System, unrelated to this fix). Same result for the representative command set above run against the real SHELL class: all 7 correctly proceed past parsing into their actual request logic (verified they reach real HTTP calls, either succeeding or failing on network/lookup grounds — never on the parser-type check).

Regression / no-breakage checks

  • python -m unittest tests/test_parsers.py -v — new test asserting every *_parser/*_parsers factory in PARSERS returns a Cmd2ArgumentParser. Confirmed it fails against the pre-fix code (AssertionError: ... got ArgumentParser) and passes after — this doesn't need cmd2 ≥4.0 installed to catch a future regression.
  • python -m py_compile lib/parsers/parsers.py — clean.
  • git diff --check — clean.
  • Live-tested against a real lab on both cmd2 2.7.0 (this repo's normal venv, still works) and cmd2 4.2.2 (throwaway venv, now works) — no regression on the older version this project currently targets.

…foster13#124)

cmd2 >=4.0 requires every parser factory used with @cmd2.with_argparser to
return a Cmd2ArgumentParser (or subclass) -- plain argparse.ArgumentParser
now raises a TypeError from cmd2's internal _build_parser. All 17 parser
factories in lib/parsers/parsers.py returned plain argparse.ArgumentParser,
so every single command in the interactive shell was broken under cmd2
>=4.0, not just get_device as the issue title suggests.

Verified with a throwaway venv running real cmd2 4.2.2: every one of a
representative sample of commands (get_device, get_user, get_collection,
cd, interact, sessionhunter, cat) raised the exact TypeError from the
issue on unpatched main, and none do after this fix -- confirmed both in
isolation and by importing the real SHELL class from lib/attacks/admin,
and live against a real lab (reproduced the crash on main, confirmed a
clean run on this branch).

Also:
- Fixed do_decrypt_parsers(), which was missing its `return parser`
  entirely (currently dead code -- not wired to any command -- but a
  real bug in the same file).
- Relaxed requirements.txt's `cmd2<4.0.0` pin (added as a workaround,
  per the issue) now that the actual incompatibility is fixed, and
  aligned it with pyproject.toml's unbounded `cmd2>=2.7.0` so both
  dependency declarations agree.

Added tests/test_parsers.py asserting every *_parser factory returns a
Cmd2ArgumentParser -- this doesn't require cmd2 >=4.0 to be installed
to catch a regression, since it checks the same thing cmd2's runtime
check enforces. Confirmed it fails against the pre-fix code and passes
after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_device broken with the new cmd2 package release

1 participant