From 7e9e93be65cae4ce28bbf6277dbe7011685b7f89 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 6 May 2026 09:27:51 +0100 Subject: [PATCH 1/3] Find invalid characters after nul bytes in KV store keys --- .../js-compute/fixtures/module-mode/src/kv-store.js | 11 +++++++++++ .../js-compute/fixtures/module-mode/tests.json | 1 + runtime/fastly/builtins/kv-store.cpp | 8 ++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js b/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js index 7f84296c26..c8f3a90c2b 100644 --- a/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js +++ b/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js @@ -504,6 +504,17 @@ const debug = sdkVersion.endsWith('-debug'); } }, ); + routes.set('/kv-store/put/key-parameter-containing-nul-byte', async () => { + await assertRejects( + async () => { + let store = new KVStore(KV_STORE_NAME); + // Key is 'a\x00;b' — the ; is after a null byte and must still be rejected + await store.put('a\x00;b', ''); + }, + TypeError, + `KVStore key can not contain ; character`, + ); + }); routes.set('/kv-store/put/value-parameter-as-undefined', async () => { const store = new KVStore(KV_STORE_NAME); let result = store.put('undefined', undefined); diff --git a/integration-tests/js-compute/fixtures/module-mode/tests.json b/integration-tests/js-compute/fixtures/module-mode/tests.json index d916c9c7a3..555ee1f853 100644 --- a/integration-tests/js-compute/fixtures/module-mode/tests.json +++ b/integration-tests/js-compute/fixtures/module-mode/tests.json @@ -325,6 +325,7 @@ "GET /kv-store/put/key-parameter-containing-special-characters": { "flake": true }, + "GET /kv-store/put/key-parameter-containing-nul-byte": { "flake": true }, "GET /kv-store/put/value-parameter-as-undefined": { "flake": true }, "GET /kv-store/put/value-parameter-not-supplied": { "flake": true }, "GET /kv-store/put/value-parameter-readablestream-empty": { "flake": true }, diff --git a/runtime/fastly/builtins/kv-store.cpp b/runtime/fastly/builtins/kv-store.cpp index aae08f479b..3f709a8522 100644 --- a/runtime/fastly/builtins/kv-store.cpp +++ b/runtime/fastly/builtins/kv-store.cpp @@ -41,10 +41,10 @@ api::Engine *ENGINE; std::string_view bad_chars{"#;?^|\n\r"}; -std::optional find_invalid_character_for_kv_store_key(const char *str) { +std::optional find_invalid_character_for_kv_store_key(const char *str, size_t len) { std::optional res; - std::string_view view{str, strlen(str)}; + std::string_view view{str, len}; auto it = std::find_if(view.begin(), view.end(), [](auto c) { return bad_chars.find(c) != std::string_view::npos; }); @@ -195,7 +195,7 @@ bool parse_and_validate_key(JSContext *cx, const char *key, size_t len) { } auto key_chars = key; - auto res = find_invalid_character_for_kv_store_key(key_chars); + auto res = find_invalid_character_for_kv_store_key(key_chars, len); if (res.has_value()) { std::string character; switch (res.value()) { @@ -231,7 +231,7 @@ bool parse_and_validate_key(JSContext *cx, const char *key, size_t len) { return false; } - if (strcmp(key_chars, ".") == 0 || strcmp(key_chars, "..") == 0) { + if ((len == 1 && key_chars[0] == '.') || (len == 2 && key_chars[0] == '.' && key_chars[1] == '.')) { JS_ReportErrorNumberASCII(cx, FastlyGetErrorMessage, nullptr, JSMSG_KV_STORE_KEY_RELATIVE); return false; } From 978194a1333c984e3e61a58d331e270c3420ef49 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 6 May 2026 09:28:17 +0100 Subject: [PATCH 2/3] Remove comment --- .../js-compute/fixtures/module-mode/src/kv-store.js | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js b/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js index c8f3a90c2b..98b04f307a 100644 --- a/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js +++ b/integration-tests/js-compute/fixtures/module-mode/src/kv-store.js @@ -508,7 +508,6 @@ const debug = sdkVersion.endsWith('-debug'); await assertRejects( async () => { let store = new KVStore(KV_STORE_NAME); - // Key is 'a\x00;b' — the ; is after a null byte and must still be rejected await store.put('a\x00;b', ''); }, TypeError, From edc81a022f3b2e09f5ca24f478be396b747bbe01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Fri, 8 May 2026 15:44:05 -0700 Subject: [PATCH 3/3] format fix --- runtime/fastly/builtins/kv-store.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/fastly/builtins/kv-store.cpp b/runtime/fastly/builtins/kv-store.cpp index 4872839432..f0c8d0d530 100644 --- a/runtime/fastly/builtins/kv-store.cpp +++ b/runtime/fastly/builtins/kv-store.cpp @@ -231,7 +231,8 @@ bool parse_and_validate_key(JSContext *cx, const char *key, size_t len) { return false; } - if ((len == 1 && key_chars[0] == '.') || (len == 2 && key_chars[0] == '.' && key_chars[1] == '.')) { + if ((len == 1 && key_chars[0] == '.') || + (len == 2 && key_chars[0] == '.' && key_chars[1] == '.')) { JS_ReportErrorNumberASCII(cx, FastlyGetErrorMessage, nullptr, JSMSG_KV_STORE_KEY_RELATIVE); return false; }