Skip to content

Commit 3a639bf

Browse files
committed
feat: Add Lambda-Runtime-Invocation-Id support for cross-wiring protection
Parse the invocation ID from /next response headers, store in invocation_request, and echo it back on /response and /error via do_post. Header only sent when non-empty (backward compatible).
1 parent 356f6ce commit 3a639bf

4 files changed

Lines changed: 73 additions & 10 deletions

File tree

‎include/aws/lambda-runtime/runtime.h‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ struct invocation_request {
7171
*/
7272
std::string tenant_id;
7373

74+
/**
75+
* The unique invocation ID for cross-wiring protection.
76+
*/
77+
std::string invocation_id;
78+
7479
/**
7580
* The number of milliseconds left before lambda terminates the current execution.
7681
*/
@@ -199,12 +204,18 @@ class runtime {
199204
/**
200205
* Tells lambda that the function has succeeded.
201206
*/
202-
post_outcome post_success(std::string const& request_id, invocation_response const& handler_response);
207+
post_outcome post_success(
208+
std::string const& request_id,
209+
invocation_response const& handler_response,
210+
std::string const& invocation_id = "");
203211

204212
/**
205213
* Tells lambda that the function has failed.
206214
*/
207-
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response);
215+
post_outcome post_failure(
216+
std::string const& request_id,
217+
invocation_response const& handler_response,
218+
std::string const& invocation_id = "");
208219

209220
/**
210221
* Tells lambda that the runtime has failed during initialization.
@@ -218,7 +229,8 @@ class runtime {
218229
std::string const& url,
219230
std::string const& content_type,
220231
std::string const& payload,
221-
std::string const& xray_response);
232+
std::string const& xray_response,
233+
std::string const& invocation_id = "");
222234
std::string const m_user_agent_header;
223235
std::array<std::string const, 3> const m_endpoints;
224236
};

‎src/runtime.cpp‎

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity
4242
static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms";
4343
static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn";
4444
static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id";
45+
static constexpr auto INVOCATION_ID_HEADER = "lambda-runtime-invocation-id";
4546
struct curl_handle_wrapper {
4647
CURL* handle;
4748
curl_handle_wrapper() : handle(curl_easy_init()) {}
@@ -318,6 +319,11 @@ runtime::next_outcome runtime::get_next()
318319
req.tenant_id = std::move(out).get_result();
319320
}
320321

322+
out = resp.get_header(INVOCATION_ID_HEADER);
323+
if (out.is_success()) {
324+
req.invocation_id = std::move(out).get_result();
325+
}
326+
321327
out = resp.get_header(DEADLINE_MS_HEADER);
322328
if (out.is_success()) {
323329
auto const& deadline_string = std::move(out).get_result();
@@ -335,18 +341,32 @@ runtime::next_outcome runtime::get_next()
335341
return {req};
336342
}
337343

338-
runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response)
344+
runtime::post_outcome runtime::post_success(
345+
std::string const& request_id,
346+
invocation_response const& handler_response,
347+
std::string const& invocation_id)
339348
{
340349
std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response";
341350
return do_post(
342-
url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response());
351+
url,
352+
handler_response.get_content_type(),
353+
handler_response.get_payload(),
354+
handler_response.get_xray_response(),
355+
invocation_id);
343356
}
344357

345-
runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response)
358+
runtime::post_outcome runtime::post_failure(
359+
std::string const& request_id,
360+
invocation_response const& handler_response,
361+
std::string const& invocation_id)
346362
{
347363
std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error";
348364
return do_post(
349-
url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response());
365+
url,
366+
handler_response.get_content_type(),
367+
handler_response.get_payload(),
368+
handler_response.get_xray_response(),
369+
invocation_id);
350370
}
351371

352372
runtime::post_outcome runtime::post_init_error(runtime_response const& init_error_response)
@@ -363,7 +383,8 @@ runtime::post_outcome runtime::do_post(
363383
std::string const& url,
364384
std::string const& content_type,
365385
std::string const& payload,
366-
std::string const& xray_response)
386+
std::string const& xray_response,
387+
std::string const& invocation_id)
367388
{
368389
set_curl_post_result_options();
369390
curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str());
@@ -382,6 +403,10 @@ runtime::post_outcome runtime::do_post(
382403
headers = curl_slist_append(headers, "transfer-encoding:");
383404
headers = curl_slist_append(headers, m_user_agent_header.c_str());
384405

406+
if (!invocation_id.empty()) {
407+
headers = curl_slist_append(headers, (std::string(INVOCATION_ID_HEADER) + ": " + invocation_id).c_str());
408+
}
409+
385410
logging::log_debug(
386411
LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str());
387412
headers = curl_slist_append(headers, ("content-length: " + std::to_string(payload.length())).c_str());
@@ -479,13 +504,13 @@ void run_handler(std::function<invocation_response(invocation_request const&)> c
479504
logging::log_info(LOG_TAG, "Invoking user handler completed.");
480505

481506
if (res.is_success()) {
482-
const auto post_outcome = rt.post_success(req.request_id, res);
507+
const auto post_outcome = rt.post_success(req.request_id, res, req.invocation_id);
483508
if (!handle_post_outcome(post_outcome, req.request_id)) {
484509
return; // TODO: implement a better retry strategy
485510
}
486511
}
487512
else {
488-
const auto post_outcome = rt.post_failure(req.request_id, res);
513+
const auto post_outcome = rt.post_failure(req.request_id, res, req.invocation_id);
489514
if (!handle_post_outcome(post_outcome, req.request_id)) {
490515
return; // TODO: implement a better retry strategy
491516
}

‎tests/unit/thread_local_curl_test.cpp‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,20 @@ TEST(ThreadLocalCurl, sequential_requests_on_same_runtime)
6060
ASSERT_FALSE(outcome.is_success());
6161
}
6262
}
63+
64+
TEST(ThreadLocalCurl, post_success_with_invocation_id_does_not_crash)
65+
{
66+
runtime rt("http://127.0.0.1:9001");
67+
auto response = invocation_response::success("test payload", "application/json");
68+
auto outcome = rt.post_success("test-request-id", response, "test-invocation-uuid-1234");
69+
// Connection will fail but the invocation_id header path should not crash
70+
ASSERT_FALSE(outcome.is_success());
71+
}
72+
73+
TEST(ThreadLocalCurl, post_failure_with_invocation_id_does_not_crash)
74+
{
75+
runtime rt("http://127.0.0.1:9001");
76+
auto response = invocation_response::failure("error msg", "TestError", "");
77+
auto outcome = rt.post_failure("test-request-id", response, "test-invocation-uuid-5678");
78+
ASSERT_FALSE(outcome.is_success());
79+
}

‎tests/unit/unit_tests.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ TEST(InvocationRequestTest, default_fields_are_empty)
333333
EXPECT_TRUE(req.cognito_identity.empty());
334334
EXPECT_TRUE(req.function_arn.empty());
335335
EXPECT_TRUE(req.tenant_id.empty());
336+
EXPECT_TRUE(req.invocation_id.empty());
336337
}
337338

338339
// --- version tests (no AWS SDK needed) ---
@@ -353,3 +354,11 @@ TEST(VersionTest, version_format)
353354
}
354355
EXPECT_EQ(2, dots);
355356
}
357+
358+
// --- invocation_id cross-wiring protection tests ---
359+
360+
TEST(InvocationRequestTest, invocation_id_default_empty)
361+
{
362+
invocation_request req;
363+
EXPECT_TRUE(req.invocation_id.empty());
364+
}

0 commit comments

Comments
 (0)