Files
shopdb-flask/tests/test_plugins/test_endpoints_agentupload.py
benjamin.baker d3f69bbb71 Trust the binary PE version over the typed agent version
Uploading an agent build whose product version carried semver build metadata
(1.5.0+abcdef) was rejected, because the typed version string was validated as
a dotted-numeric value before the file was ever read. That string is only a
hint from the operator, and the build metadata it carries is not a legal
directory name for the version store.

The authority is the dotted-numeric FILE version stamped in the binary PE
resource, so validation now happens after the upload is parsed: when the typed
value is missing or not dotted-numeric, fall back to the detected version, and
reject only when neither yields a usable one.
2026-09-04 16:19:28 -04:00

66 lines
2.6 KiB
Python

"""Agent binary upload version gating: PE-fallback and precedence.
The upload route no longer rejects the version at raw input. A typed value is
only a hint; the dotted-numeric FILE version in the binary's PE resource is the
authority. The route falls back to it when the typed value is missing or not
dotted-numeric, and only refuses when neither yields a usable version.
Building a real PE with a RT_VERSION resource inline is impractical (see the
agentstore unit tests), so these API tests monkeypatch read_pe_file_version to
stand in for the parser. PE-magic bytes keep looks_like_pe happy so the request
reaches the version gate.
"""
import io
from plugins.endpoints.api import agentstore
UPLOAD_URL = '/api/endpoints/siteagent/binaries/{}'
_PE_BYTES = b'MZ' + b'\x00' * 128
def _upload(client, headers, monkeypatch, detected, typed=None):
monkeypatch.setattr(agentstore, 'read_pe_file_version',
lambda data: detected)
form = {'file': (io.BytesIO(_PE_BYTES), 'shopdbagent.exe')}
if typed is not None:
form['version'] = typed
return client.post(
UPLOAD_URL.format('agent'),
data=form,
content_type='multipart/form-data',
headers=headers)
def test_upload_falls_back_to_pe_version_when_typed_is_missing(
client, db, app, auth_headers, monkeypatch):
response = _upload(client, auth_headers, monkeypatch, '1.5.0.0')
assert response.status_code == 200, response.get_json()
payload = response.get_json()['data']
assert payload['current'] == '1.5.0.0'
assert any(v['version'] == '1.5.0.0' for v in payload['versions'])
def test_upload_falls_back_when_typed_is_not_dotted_numeric(
client, db, app, auth_headers, monkeypatch):
response = _upload(client, auth_headers, monkeypatch, '1.5.0.0',
typed='1.5.0+1a1bf0640f956634dada1b34840')
assert response.status_code == 200, response.get_json()
assert response.get_json()['data']['current'] == '1.5.0.0'
def test_upload_prefers_valid_typed_version_over_pe(
client, db, app, auth_headers, monkeypatch):
response = _upload(client, auth_headers, monkeypatch, '9.9.9.9',
typed='1.6.3')
assert response.status_code == 200, response.get_json()
assert response.get_json()['data']['current'] == '1.6.3'
def test_upload_rejects_when_neither_typed_nor_pe_is_usable(
client, db, app, auth_headers, monkeypatch):
response = _upload(client, auth_headers, monkeypatch, None)
assert response.status_code == 400
assert 'dotted-numeric' in response.get_json()['data']['error']['message']