Skip to content

Security hardening: shell=True, PowerShell -Command interpolation, verify=False, unescaped task XML #199

Description

@HetCreep

Hi - while reading the code I noticed a few hardening opportunities. Grouping them here for discussion before sending any PR, since some touch areas where you may have a reason for the current approach (especially the TLS one). Happy to open focused PRs for whichever you'd accept.

1. subprocess(..., shell=True) - lib/process_manager.py

def run(args: list[str], cwd=None):
    return subprocess.Popen(args, cwd=cwd, shell=True, ...)   # L32
def run_ps(args: str):
    return subprocess.call(text, shell=True)                  # L39

run() already receives a list; on Windows shell=True with a list is ambiguous and unnecessary. All callers in launch.py pass a real executable + args, so shell=False would be more predictable and drop the shell-parsing surface. (Note: test/test_process.py uses ["echo", "test"], where echo is a shell builtin - that test would need a real exe.)

2. PowerShell command injection - run_ps()

args = args.replace('"', '\\"').replace("\n", "").replace("\r", "")
text = f'powershell -Command "{args}"'

The shortcut template is interpolated into -Command with only quote/newline escaping. A safer pattern is to write the script to a temp .ps1 and invoke powershell -ExecutionPolicy Bypass -File <path>, which removes the string-interpolation surface entirely.

3. TLS verification disabled - lib/DGPSessionV2.py, tab/account.py

urllib3.disable_warnings()                       # DGPSessionV2 L19
... post_device_dgp(url, json=json, verify=False)  # L203
... verify=False ...                              # account.py L291/314/362/379

verify=False on the DMM device endpoints disables certificate validation (MITM exposure). Is this required by a specific DMM endpoint's certificate, or is it incidental? If incidental, enabling verification (and dropping disable_warnings()) would close the hole. If a particular host needs it, scoping verify=False to just that request would be safer than the session-wide default.

4. Scheduled-task XML built by string replace - Schtasks.set()

template = template.replace(r"{{ARGUMENTS}}", " ".join(...))
template = template.replace(r"{{WORKING_DIRECTORY}}", os.getcwd())

User-derived values (username via the UID, cwd, args) are interpolated into the task XML unescaped. A path or arg containing &, <, > produces malformed XML. xml.sax.saxutils.escape() on each substituted value fixes it.

5. Minor: dead code

ProcessManager.search_process() has no caller in the tree.


None of these are urgent; #3 and #4 are the most worth a look. Let me know which you'd like as PRs and I'll keep them small and focused.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions