From a048d852656f7a525c3212fe23fe02e27602b40c Mon Sep 17 00:00:00 2001 From: Dylan Jew Date: Thu, 23 Jul 2026 12:56:54 -0400 Subject: [PATCH 1/2] Fix UI form parsing that created ghost fuzzer entries The /jobs form submission retrieved 'fuzzers' as a string and blindly called .split(','), meaning an empty field evaluated as [''] instead of []. This string parsed as a valid mapping attempt for a fuzzer named "", leading to the creation of orphaned FuzzerJob entries in Datastore. This updates the form ingestion to correctly coerce missing values to an empty list. A follow up commit will sweep Datastore to remove any existing ghost mappings. TAG=agy CONV=b5fdacea-8cda-4418-8906-dee99c91f929 --- src/appengine/handlers/jobs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/appengine/handlers/jobs.py b/src/appengine/handlers/jobs.py index 0c70ac86c40..cdd957a532f 100644 --- a/src/appengine/handlers/jobs.py +++ b/src/appengine/handlers/jobs.py @@ -147,7 +147,8 @@ def post(self): 'Job name can only contain letters, numbers, dashes and underscores.', 400) - fuzzers = request.form.get('fuzzers', []).split(',') + fuzzers_string = request.form.get('fuzzers') + fuzzers = fuzzers_string.split(',') if fuzzers_string else [] templates = request.form.get('templates', '').splitlines() for template in templates: if not data_types.JobTemplate.query( From ff9cdb01575720ec2b0ed539777ca18d26a535f0 Mon Sep 17 00:00:00 2001 From: Dylan Jew Date: Thu, 23 Jul 2026 18:37:49 -0400 Subject: [PATCH 2/2] Add test --- .../tests/appengine/handlers/jobs_test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/jobs_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/jobs_test.py index 5c4a15a0b5e..402339d5eab 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/jobs_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/jobs_test.py @@ -222,3 +222,21 @@ def test_post(self): self.assertEqual(200, resp.status_int) self.mock.update_mappings_for_job.assert_called_with( mock.ANY, ['test_fuzzer']) + + def test_post_empty_fuzzers(self): + """Test post method with no fuzzers provided.""" + self.mock.has_access.return_value = True + job = self._create_job('test_job', 'PROJECT_NAME = proj\n') + + resp = self.app.post( + '/', { + 'csrf_token': form.generate_csrf_token(), + 'name': job.name, + 'desciption': job.description, + 'platform': job.platform, + 'fuzzers': '' + }, + expect_errors=True) + + self.assertEqual(200, resp.status_int) + self.mock.update_mappings_for_job.assert_called_with(mock.ANY, [])