Skip to content

Commit e259754

Browse files
committed
feat(runtime-interface-client): Add Lambda-Runtime-Invocation-Id support for cross-wiring protection
Echo the invocation ID received from RAPID on /next back on /response and /error, enabling RAPID to detect and reject stale responses from timed-out invocations. Fully backward compatible.
1 parent ba130c1 commit e259754

13 files changed

Lines changed: 170 additions & 48 deletions

File tree

aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### July 17, 2026
2+
`2.12.0`
3+
- Add `Lambda-Runtime-Invocation-Id` header support for cross-wiring protection. The RIC now echoes the invocation ID received from RAPID on `/next` back on `/response` and `/error`, enabling RAPID to detect and reject stale responses from timed-out invocations.
4+
15
### May 13, 2026
26
`2.11.0`
37
- Update aws-lambda-java-serialization dependency to 1.4.1

aws-lambda-java-runtime-interface-client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.amazonaws</groupId>
66
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
7-
<version>2.11.0</version>
7+
<version>2.12.0</version>
88
<packaging>jar</packaging>
99

1010
<name>AWS Lambda Java Runtime Interface Client</name>

aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,15 @@ private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler,
315315

316316
try {
317317
ByteArrayOutputStream payload = lambdaRequestHandler.call(request);
318-
runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray());
318+
runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray(), request.getInvocationId());
319319
// clear interrupted flag in case if it was set by user's code
320320
Thread.interrupted();
321321
} catch (Throwable t) {
322322
UserFault.filterStackTrace(t);
323323
userFault = UserFault.makeUserFault(t);
324324
shouldExit = exitLoopOnErrors && (t instanceof VirtualMachineError || t instanceof IOError || userFault.fatal);
325325
LambdaError error = createLambdaErrorFromThrowableOrUserFault(t);
326-
runtimeClient.reportInvocationError(request.getId(), error);
326+
runtimeClient.reportInvocationError(request.getId(), error, request.getInvocationId());
327327
} finally {
328328
if (userFault != null) {
329329
lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);

aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,17 @@ public interface LambdaRuntimeApiClient {
3434
* Report invocation success
3535
* @param requestId request id
3636
* @param response byte array representing response
37+
* @param invocationId invocation id for cross-wiring protection (may be null)
3738
*/
38-
void reportInvocationSuccess(String requestId, byte[] response) throws IOException;
39+
void reportInvocationSuccess(String requestId, byte[] response, String invocationId) throws IOException;
3940

4041
/**
4142
* Report invocation error
4243
* @param requestId request id
4344
* @param error error to report
45+
* @param invocationId invocation id for cross-wiring protection (may be null)
4446
*/
45-
void reportInvocationError(String requestId, LambdaError error) throws IOException;
47+
void reportInvocationError(String requestId, LambdaError error, String invocationId) throws IOException;
4648

4749
/**
4850
* SnapStart endpoint to report that beforeCheckoint hooks were executed

aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class LambdaRuntimeApiClientImpl implements LambdaRuntimeApiClient {
3434
private static final String DEFAULT_CONTENT_TYPE = "application/json";
3535
private static final String XRAY_ERROR_CAUSE_HEADER = "Lambda-Runtime-Function-XRay-Error-Cause";
3636
private static final String ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type";
37+
private static final String INVOCATION_ID_HEADER = "Lambda-Runtime-Invocation-Id";
3738
// 1MiB
3839
private static final int XRAY_ERROR_CAUSE_MAX_HEADER_SIZE = 1024 * 1024;
3940

@@ -55,7 +56,7 @@ public LambdaRuntimeApiClientImpl(String hostnameAndPort) {
5556
@Override
5657
public void reportInitError(LambdaError error) throws IOException {
5758
String endpoint = this.baseUrl + "/2018-06-01/runtime/init/error";
58-
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
59+
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, null);
5960
}
6061

6162
@Override
@@ -123,14 +124,15 @@ public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogge
123124
}
124125

125126
@Override
126-
public void reportInvocationSuccess(String requestId, byte[] response) {
127-
NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response);
127+
public void reportInvocationSuccess(String requestId, byte[] response, String invocationId) {
128+
byte[] invocationIdBytes = invocationId != null ? invocationId.getBytes(UTF_8) : null;
129+
NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response, invocationIdBytes);
128130
}
129131

130132
@Override
131-
public void reportInvocationError(String requestId, LambdaError error) throws IOException {
133+
public void reportInvocationError(String requestId, LambdaError error, String invocationId) throws IOException {
132134
String endpoint = invocationEndpoint + requestId + "/error";
133-
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
135+
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, invocationId);
134136
}
135137

136138
@Override
@@ -145,13 +147,17 @@ public void restoreNext() throws IOException {
145147
@Override
146148
public void reportRestoreError(LambdaError error) throws IOException {
147149
String endpoint = this.baseUrl + "/2018-06-01/runtime/restore/error";
148-
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
150+
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, null);
149151
}
150152

151-
void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize) throws IOException {
153+
void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize, String invocationId) throws IOException {
152154
Map<String, String> headers = new HashMap<>();
153155
headers.put(ERROR_TYPE_HEADER, error.errorType.getRapidError());
154156

157+
if (invocationId != null) {
158+
headers.put(INVOCATION_ID_HEADER, invocationId);
159+
}
160+
155161
if (error.xRayErrorCause != null) {
156162
byte[] xRayErrorCauseJson = DtoSerializers.serialize(error.xRayErrorCause);
157163
if (xRayErrorCauseJson != null && xRayErrorCauseJson.length < maxXrayHeaderSize) {

aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ static void init(String awsLambdaRuntimeApi) {
2121

2222
static native InvocationRequest next();
2323

24-
static native void postInvocationResponse(byte[] requestId, byte[] response);
24+
static native void postInvocationResponse(byte[] requestId, byte[] response, byte[] invocationId);
2525

2626
}

aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public class InvocationRequest {
4545
*/
4646
private String tenantId;
4747

48+
/**
49+
* The invocation ID for cross-wiring protection.
50+
*/
51+
private String invocationId;
52+
4853
private byte[] content;
4954

5055
public String getId() {
@@ -107,6 +112,14 @@ public void setTenantId(String tenantId) {
107112
this.tenantId = tenantId;
108113
}
109114

115+
public String getInvocationId() {
116+
return invocationId;
117+
}
118+
119+
public void setInvocationId(String invocationId) {
120+
this.invocationId = invocationId;
121+
}
122+
110123
public byte[] getContent() {
111124
return content;
112125
}

aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ static jfieldID clientContextField;
2121
static jfieldID cognitoIdentityField;
2222
static jfieldID xrayTraceIdField;
2323
static jfieldID tenantIdField;
24+
static jfieldID invocationIdField;
2425

2526

2627
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
@@ -43,6 +44,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
4344
clientContextField = env->GetFieldID(invocationRequestClass , "clientContext", "Ljava/lang/String;");
4445
cognitoIdentityField = env->GetFieldID(invocationRequestClass , "cognitoIdentity", "Ljava/lang/String;");
4546
tenantIdField = env->GetFieldID(invocationRequestClass, "tenantId", "Ljava/lang/String;");
47+
invocationIdField = env->GetFieldID(invocationRequestClass, "invocationId", "Ljava/lang/String;");
4648

4749
return JNI_VERSION;
4850
}
@@ -112,6 +114,10 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
112114
CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, tenantIdField, env->NewStringUTF(response.tenant_id.c_str())));
113115
}
114116

117+
if(response.invocation_id != ""){
118+
CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, invocationIdField, env->NewStringUTF(response.invocation_id.c_str())));
119+
}
120+
115121
bytes = reinterpret_cast<const jbyte*>(response.payload.c_str());
116122
CHECK_EXCEPTION(env, jArray = env->NewByteArray(response.payload.length()));
117123
CHECK_EXCEPTION(env, env->SetByteArrayRegion(jArray, 0, response.payload.length(), bytes));
@@ -124,7 +130,7 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
124130
}
125131

126132
JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse
127-
(JNIEnv *env, jobject thisObject, jbyteArray jrequestId, jbyteArray jresponseArray) {
133+
(JNIEnv *env, jobject thisObject, jbyteArray jrequestId, jbyteArray jresponseArray, jbyteArray jinvocationId) {
128134
std::string payload = toNativeString(env, jresponseArray);
129135
if ((env)->ExceptionOccurred()){
130136
return;
@@ -134,8 +140,16 @@ JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_run
134140
return;
135141
}
136142

143+
std::string invocationId;
144+
if (jinvocationId != nullptr) {
145+
invocationId = toNativeString(env, jinvocationId);
146+
if ((env)->ExceptionOccurred()){
147+
return;
148+
}
149+
}
150+
137151
auto response = aws::lambda_runtime::invocation_response::success(payload, "application/json");
138-
auto outcome = CLIENT->post_success(requestId, response);
152+
auto outcome = CLIENT->post_success(requestId, response, invocationId);
139153
if (!outcome.is_success()) {
140154
std::string errorMessage("Failed to post invocation response.");
141155
throwLambdaRuntimeClientException(env, errorMessage, outcome.get_failure());

aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
1717
(JNIEnv *, jobject);
1818

1919
JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse
20-
(JNIEnv *, jobject, jbyteArray, jbyteArray);
20+
(JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray);
2121

2222
#ifdef __cplusplus
2323
}

aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ struct invocation_request {
6666
*/
6767
std::string tenant_id;
6868

69+
/**
70+
* Invocation ID for cross-wiring protection.
71+
*/
72+
std::string invocation_id;
73+
6974
/**
7075
* The number of milliseconds left before lambda terminates the current execution.
7176
*/
@@ -154,20 +159,21 @@ class runtime {
154159
/**
155160
* Tells lambda that the function has succeeded.
156161
*/
157-
post_outcome post_success(std::string const& request_id, invocation_response const& handler_response);
162+
post_outcome post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id = "");
158163

159164
/**
160165
* Tells lambda that the function has failed.
161166
*/
162-
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response);
167+
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id = "");
163168

164169
private:
165170
void set_curl_next_options();
166171
void set_curl_post_result_options();
167172
post_outcome do_post(
168173
std::string const& url,
169174
std::string const& request_id,
170-
invocation_response const& handler_response);
175+
invocation_response const& handler_response,
176+
std::string const& invocation_id = "");
171177

172178
private:
173179
std::string const m_user_agent_header;

0 commit comments

Comments
 (0)