Skip to content

Commit 6b2e347

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 6b2e347

4 files changed

Lines changed: 87 additions & 9 deletions

File tree

include/aws/lambda-runtime/runtime.h

Lines changed: 17 additions & 1 deletion
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,11 +204,21 @@ class runtime {
199204
/**
200205
* Tells lambda that the function has succeeded.
201206
*/
207+
post_outcome post_success(
208+
std::string const& request_id,
209+
invocation_response const& handler_response,
210+
std::string const& invocation_id);
211+
202212
post_outcome post_success(std::string const& request_id, invocation_response const& handler_response);
203213

204214
/**
205215
* Tells lambda that the function has failed.
206216
*/
217+
post_outcome post_failure(
218+
std::string const& request_id,
219+
invocation_response const& handler_response,
220+
std::string const& invocation_id);
221+
207222
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response);
208223

209224
/**
@@ -218,7 +233,8 @@ class runtime {
218233
std::string const& url,
219234
std::string const& content_type,
220235
std::string const& payload,
221-
std::string const& xray_response);
236+
std::string const& xray_response,
237+
std::string const& invocation_id);
222238
std::string const m_user_agent_header;
223239
std::array<std::string const, 3> const m_endpoints;
224240
};

src/runtime.cpp

Lines changed: 44 additions & 8 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,42 @@ 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);
370+
}
371+
372+
runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response)
373+
{
374+
return post_success(request_id, handler_response, "");
375+
}
376+
377+
runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response)
378+
{
379+
return post_failure(request_id, handler_response, "");
350380
}
351381

352382
runtime::post_outcome runtime::post_init_error(runtime_response const& init_error_response)
@@ -356,14 +386,16 @@ runtime::post_outcome runtime::post_init_error(runtime_response const& init_erro
356386
url,
357387
init_error_response.get_content_type(),
358388
init_error_response.get_payload(),
359-
init_error_response.get_xray_response());
389+
init_error_response.get_xray_response(),
390+
"");
360391
}
361392

362393
runtime::post_outcome runtime::do_post(
363394
std::string const& url,
364395
std::string const& content_type,
365396
std::string const& payload,
366-
std::string const& xray_response)
397+
std::string const& xray_response,
398+
std::string const& invocation_id)
367399
{
368400
set_curl_post_result_options();
369401
curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str());
@@ -382,6 +414,10 @@ runtime::post_outcome runtime::do_post(
382414
headers = curl_slist_append(headers, "transfer-encoding:");
383415
headers = curl_slist_append(headers, m_user_agent_header.c_str());
384416

417+
if (!invocation_id.empty()) {
418+
headers = curl_slist_append(headers, (std::string(INVOCATION_ID_HEADER) + ": " + invocation_id).c_str());
419+
}
420+
385421
logging::log_debug(
386422
LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str());
387423
headers = curl_slist_append(headers, ("content-length: " + std::to_string(payload.length())).c_str());
@@ -479,13 +515,13 @@ void run_handler(std::function<invocation_response(invocation_request const&)> c
479515
logging::log_info(LOG_TAG, "Invoking user handler completed.");
480516

481517
if (res.is_success()) {
482-
const auto post_outcome = rt.post_success(req.request_id, res);
518+
const auto post_outcome = rt.post_success(req.request_id, res, req.invocation_id);
483519
if (!handle_post_outcome(post_outcome, req.request_id)) {
484520
return; // TODO: implement a better retry strategy
485521
}
486522
}
487523
else {
488-
const auto post_outcome = rt.post_failure(req.request_id, res);
524+
const auto post_outcome = rt.post_failure(req.request_id, res, req.invocation_id);
489525
if (!handle_post_outcome(post_outcome, req.request_id)) {
490526
return; // TODO: implement a better retry strategy
491527
}

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)