diff --git a/app/contacts/contact_http.py b/app/contacts/contact_http.py index 9650a9544..0532b3821 100644 --- a/app/contacts/contact_http.py +++ b/app/contacts/contact_http.py @@ -19,7 +19,14 @@ async def start(self): async def _beacon(self, request): try: - profile = json.loads(self.contact_svc.decode_bytes(await request.read())) + config_val = self.get_config('contact_http_max_body_size_kb') + max_kb = int(config_val) if config_val is not None else 512 + max_body_size = max_kb * 1024 + body = await request.read() + if len(body) > max_body_size: + self.log.warning('Beacon body exceeds size limit: %d > %d bytes', len(body), max_body_size) + return web.Response(status=413, text='Request body too large (limit: %d KB)' % max_kb) + profile = json.loads(self.contact_svc.decode_bytes(body)) profile['paw'] = profile.get('paw') profile['contact'] = profile.get('contact', self.name) agent, instructions = await self.contact_svc.handle_heartbeat(**profile) diff --git a/conf/default.yml b/conf/default.yml index ba0653c94..de9d6bc5e 100644 --- a/conf/default.yml +++ b/conf/default.yml @@ -44,6 +44,7 @@ plugins: port: 8888 reports_dir: /tmp auth.login.handler.module: default +contact_http_max_body_size_kb: 512 requirements: go: command: go version diff --git a/tests/security/test_contact_http_body_limit.py b/tests/security/test_contact_http_body_limit.py new file mode 100644 index 000000000..90273dc88 --- /dev/null +++ b/tests/security/test_contact_http_body_limit.py @@ -0,0 +1,32 @@ +import pytest + + +class TestContactHttpBodyLimit: + def test_body_size_default_computation(self): + """Default 512 KB limit computes correctly.""" + config_val = None + max_kb = int(config_val) if config_val is not None else 512 + max_bytes = max_kb * 1024 + assert max_bytes == 524288 + + def test_zero_config_respected(self): + """A config value of 0 should not fall back to the default.""" + config_val = 0 + max_kb = int(config_val) if config_val is not None else 512 + assert max_kb == 0 + + def test_string_config_coerced(self): + """String config values from YAML are coerced to int.""" + config_val = '256' + max_kb = int(config_val) if config_val is not None else 512 + assert max_kb == 256 + + def test_oversized_body_detected(self): + max_bytes = 512 * 1024 + body = b'x' * (max_bytes + 1) + assert len(body) > max_bytes + + def test_normal_body_accepted(self): + max_bytes = 512 * 1024 + body = b'x' * 1000 + assert not (len(body) > max_bytes)