Merge the wilm work, act on the five-agent review, verify the launch-path policy on Windows

This commit is contained in:
2026-09-02 16:15:53 -04:00
parent 2144305433
commit bb733f1332
84 changed files with 7107 additions and 186 deletions

View File

@@ -3,7 +3,7 @@
import logging
import re
from flask import Blueprint, request, Response
from flask import Blueprint, request, Response, current_app
from flask_jwt_extended import jwt_required
from shopdb.api import db, cache, Setting, Asset, AssetType, Vendor, Model, Communication, CommunicationType, AssetRelationship, RelationshipType, success_response, error_response, paginated_response, ErrorCodes, get_pagination_params, paginate_query
@@ -380,6 +380,24 @@ def printer_install_list():
UNIVERSAL_INSTALL_VENDORS = frozenset({'HP', 'Xerox'})
# A hostname and nothing else: letters, digits, dot, hyphen, optional :port.
# Anything outside this cannot reach the generated script, so the quote that
# would end the PowerShell literal never gets the chance.
_HOST_RE = re.compile(r'^[A-Za-z0-9.-]+(?::[0-9]{1,5})?$')
def _safe_host(value):
"""A request-supplied host, or None when it is not one.
The value reaches a single-quoted PowerShell -Uri literal in the generated
.bat, so it is validated rather than escaped: an allowlist of what a host
may contain is a smaller thing to get right than enumerating what a shell
does with the rest.
"""
host = (value or '').strip()
return host if host and _HOST_RE.match(host) else None
def _batch_base_url():
"""Base URL the generated .bat downloads installers from. Prefer the
configured site_base_url (it already includes scheme + the /shopdb mount).
@@ -387,12 +405,34 @@ def _batch_base_url():
Fallback matters: behind IIS the app sees http on a loopback port and its
url_root drops the mount, so a naive request.url_root yields a broken
http://127.0.0.1/installers/... URL. Rebuild from the forwarded Host + the
mount (script_root) and force https instead."""
mount (script_root) and force https instead.
THE FALLBACK IS THE DEFAULT PATH, not an edge case: site_base_url ships
blank. So the host it uses is caller-supplied on every fresh install, and
it lands unescaped inside a single-quoted PowerShell literal. X-Forwarded-Host
is only believed when a proxy is actually declared (TRUSTED_PROXY_COUNT) -
the same rule shopdb/utils/clientip.py applies to X-Forwarded-For, which was
centralised for this exact reason and did not cover the Host variant. Either
way the value is validated as a hostname before it is interpolated, and a
request that supplies neither a usable forwarded host nor a usable Host gets
a relative base rather than a poisoned absolute one.
"""
base = (Setting.get('site_base_url') or '').strip().rstrip('/')
if base:
return base
host = request.headers.get('X-Forwarded-Host') or request.host
host = None
if current_app.config.get('TRUSTED_PROXY_COUNT'):
host = _safe_host(request.headers.get('X-Forwarded-Host'))
if not host:
host = _safe_host(request.host)
root = (request.script_root or '').rstrip('/')
if not host:
# Same-origin relative URLs still download correctly from the browser
# that fetched the script. Better than emitting a host we cannot vouch
# for into a file that runs commands.
return root
return 'https://%s%s' % (host, root)