refactor(dynamic): add SQS loopback HTTP emulator with real SDK compatibility, extend stub event recording and endpoint rewriting logic across Java and Python

This commit is contained in:
elipeter 2026-05-27 09:34:02 -05:00
parent 0903231189
commit 433036aead
6 changed files with 765 additions and 39 deletions

View file

@ -3803,25 +3803,27 @@ fn emit_message_handler_harness(
JavaBroker::Sqs => (
crate::dynamic::stubs::SQS_PUBLISH_MARKER,
format!(
r#" NyxSqsLoopback brokerRef = new NyxSqsLoopback();
System.out.println({publish_marker:?} + " " + {queue:?});
nyxRecordBrokerPublish("NYX_SQS_LOG", {queue:?}, payload);
brokerRef.publish({queue:?}, payload);
for (java.util.Map<String, String> env : brokerRef.receiveMessage({queue:?}, 1)) {{
nyxRecordBrokerEvent("NYX_SQS_LOG", "deliver", {queue:?}, env.getOrDefault("Body", ""));
System.out.println("__NYX_SINK_HIT__");
boolean success = false;
try {{
java.lang.reflect.Method m = entryInst.getClass().getDeclaredMethod({handler:?}, java.util.Map.class);
m.setAccessible(true);
m.invoke(entryInst, env);
success = true;
}} catch (Exception e) {{
Throwable c = (e instanceof java.lang.reflect.InvocationTargetException && e.getCause() != null) ? e.getCause() : e;
System.err.println("NYX_EXCEPTION: " + c.getClass().getName() + ": " + c.getMessage());
}}
if (success && brokerRef.deleteMessage({queue:?}, env.getOrDefault("ReceiptHandle", ""))) {{
nyxRecordBrokerEvent("NYX_SQS_LOG", "ack", {queue:?}, env.getOrDefault("ReceiptHandle", ""));
r#" if (!nyxTryRealSqs({queue:?}, payload, entryInst, {handler:?})) {{
NyxSqsLoopback brokerRef = new NyxSqsLoopback();
System.out.println({publish_marker:?} + " " + {queue:?});
nyxRecordBrokerPublish("NYX_SQS_LOG", {queue:?}, payload);
brokerRef.publish({queue:?}, payload);
for (java.util.Map<String, String> env : brokerRef.receiveMessage({queue:?}, 1)) {{
nyxRecordBrokerEvent("NYX_SQS_LOG", "deliver", {queue:?}, env.getOrDefault("Body", ""));
System.out.println("__NYX_SINK_HIT__");
boolean success = false;
try {{
java.lang.reflect.Method m = entryInst.getClass().getDeclaredMethod({handler:?}, java.util.Map.class);
m.setAccessible(true);
m.invoke(entryInst, env);
success = true;
}} catch (Exception e) {{
Throwable c = (e instanceof java.lang.reflect.InvocationTargetException && e.getCause() != null) ? e.getCause() : e;
System.err.println("NYX_EXCEPTION: " + c.getClass().getName() + ": " + c.getMessage());
}}
if (success && brokerRef.deleteMessage({queue:?}, env.getOrDefault("ReceiptHandle", ""))) {{
nyxRecordBrokerEvent("NYX_SQS_LOG", "ack", {queue:?}, env.getOrDefault("ReceiptHandle", ""));
}}
}}
}}"#,
handler = handler,
@ -3926,6 +3928,97 @@ public class NyxHarness {{
}}
}}
static boolean nyxTryRealSqs(String queue, String payload, Object entryInst, String handler) {{
String endpoint = System.getenv("NYX_SQS_ENDPOINT");
if (endpoint == null || !(endpoint.startsWith("http://") || endpoint.startsWith("https://"))) {{
return false;
}}
Object client = null;
try {{
Class<?> sqsClientClass = Class.forName("software.amazon.awssdk.services.sqs.SqsClient");
Object builder = sqsClientClass.getMethod("builder").invoke(null);
Class<?> regionClass = Class.forName("software.amazon.awssdk.regions.Region");
Object region = regionClass.getMethod("of", String.class).invoke(null, "us-east-1");
builder.getClass().getMethod("endpointOverride", java.net.URI.class)
.invoke(builder, java.net.URI.create(endpoint));
builder.getClass().getMethod("region", regionClass).invoke(builder, region);
Class<?> basicCredentialsClass = Class.forName("software.amazon.awssdk.auth.credentials.AwsBasicCredentials");
Class<?> credentialsClass = Class.forName("software.amazon.awssdk.auth.credentials.AwsCredentials");
Class<?> providerClass = Class.forName("software.amazon.awssdk.auth.credentials.StaticCredentialsProvider");
Class<?> providerInterface = Class.forName("software.amazon.awssdk.auth.credentials.AwsCredentialsProvider");
Object credentials = basicCredentialsClass.getMethod("create", String.class, String.class)
.invoke(null, "nyx", "nyx");
Object provider = providerClass.getMethod("create", credentialsClass).invoke(null, credentials);
builder.getClass().getMethod("credentialsProvider", providerInterface).invoke(builder, provider);
client = builder.getClass().getMethod("build").invoke(builder);
String queueUrl = endpoint.replaceAll("/+$", "") + "/" + queue.replaceAll("^/+", "");
System.out.println({sqs_publish_marker:?} + " " + queue);
Class<?> sendReqClass = Class.forName("software.amazon.awssdk.services.sqs.model.SendMessageRequest");
Object sendBuilder = sendReqClass.getMethod("builder").invoke(null);
sendBuilder.getClass().getMethod("queueUrl", String.class).invoke(sendBuilder, queueUrl);
sendBuilder.getClass().getMethod("messageBody", String.class).invoke(sendBuilder, payload);
Object sendReq = sendBuilder.getClass().getMethod("build").invoke(sendBuilder);
sqsClientClass.getMethod("sendMessage", sendReqClass).invoke(client, sendReq);
Class<?> receiveReqClass = Class.forName("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest");
Object receiveBuilder = receiveReqClass.getMethod("builder").invoke(null);
receiveBuilder.getClass().getMethod("queueUrl", String.class).invoke(receiveBuilder, queueUrl);
receiveBuilder.getClass().getMethod("maxNumberOfMessages", Integer.class).invoke(receiveBuilder, Integer.valueOf(1));
receiveBuilder.getClass().getMethod("waitTimeSeconds", Integer.class).invoke(receiveBuilder, Integer.valueOf(0));
Object receiveReq = receiveBuilder.getClass().getMethod("build").invoke(receiveBuilder);
Object receiveResp = sqsClientClass.getMethod("receiveMessage", receiveReqClass).invoke(client, receiveReq);
java.util.List<?> messages = (java.util.List<?>) receiveResp.getClass().getMethod("messages").invoke(receiveResp);
if (messages == null || messages.isEmpty()) {{
return false;
}}
for (Object msg : messages) {{
String body = String.valueOf(msg.getClass().getMethod("body").invoke(msg));
String receipt = String.valueOf(msg.getClass().getMethod("receiptHandle").invoke(msg));
String messageId = String.valueOf(msg.getClass().getMethod("messageId").invoke(msg));
java.util.Map<String, String> env = new java.util.HashMap<>();
env.put("Body", body);
env.put("ReceiptHandle", receipt);
env.put("MessageId", messageId);
System.out.println("__NYX_SINK_HIT__");
boolean success = false;
try {{
java.lang.reflect.Method m = entryInst.getClass().getDeclaredMethod(handler, java.util.Map.class);
m.setAccessible(true);
m.invoke(entryInst, env);
success = true;
}} catch (Exception e) {{
Throwable c = (e instanceof java.lang.reflect.InvocationTargetException && e.getCause() != null) ? e.getCause() : e;
System.err.println("NYX_EXCEPTION: " + c.getClass().getName() + ": " + c.getMessage());
}}
if (success && receipt != null && !receipt.isEmpty()) {{
Class<?> deleteReqClass = Class.forName("software.amazon.awssdk.services.sqs.model.DeleteMessageRequest");
Object deleteBuilder = deleteReqClass.getMethod("builder").invoke(null);
deleteBuilder.getClass().getMethod("queueUrl", String.class).invoke(deleteBuilder, queueUrl);
deleteBuilder.getClass().getMethod("receiptHandle", String.class).invoke(deleteBuilder, receipt);
Object deleteReq = deleteBuilder.getClass().getMethod("build").invoke(deleteBuilder);
sqsClientClass.getMethod("deleteMessage", deleteReqClass).invoke(client, deleteReq);
}}
}}
return true;
}} catch (ClassNotFoundException missingSdk) {{
return false;
}} catch (Throwable e) {{
System.err.println("NYX_REAL_SQS_FALLBACK: " + e.getClass().getName() + ": " + e.getMessage());
return false;
}} finally {{
if (client instanceof AutoCloseable) {{
try {{
((AutoCloseable) client).close();
}} catch (Exception ignored) {{
}}
}}
}}
}}
static String nyxPayload() {{
String v = System.getenv("NYX_PAYLOAD");
if (v != null && !v.isEmpty()) return v;
@ -3959,6 +4052,7 @@ public class NyxHarness {{
"#,
entry_class = entry_class,
dispatch_block = dispatch_block,
sqs_publish_marker = crate::dynamic::stubs::SQS_PUBLISH_MARKER,
);
HarnessSource {
source,
@ -3966,7 +4060,7 @@ public class NyxHarness {{
command: vec![
"java".to_owned(),
"-cp".to_owned(),
".".to_owned(),
".:lib/*".to_owned(),
"NyxHarness".to_owned(),
],
extra_files: {

View file

@ -945,6 +945,53 @@ function _nyxRecordBrokerPublish(envName, destination, body) {{
_nyxRecordBrokerEvent(envName, 'publish', destination, body);
}}
async function _nyxTryRealSqs(queue, body) {{
const endpoint = process.env.NYX_SQS_ENDPOINT || '';
if (!/^https?:\/\//.test(endpoint)) return false;
let sqs;
try {{
sqs = require('@aws-sdk/client-sqs');
}} catch (_) {{
return false;
}}
try {{
const client = new sqs.SQSClient({{
endpoint,
region: 'us-east-1',
credentials: {{ accessKeyId: 'nyx', secretAccessKey: 'nyx' }},
maxAttempts: 1,
}});
const queueUrl = endpoint.replace(/\/$/, '') + '/' + String(queue).replace(/^\//, '');
process.stdout.write({publish_marker:?} + ' ' + queue + '\n');
await client.send(new sqs.SendMessageCommand({{
QueueUrl: queueUrl,
MessageBody: String(body),
}}));
const response = await client.send(new sqs.ReceiveMessageCommand({{
QueueUrl: queueUrl,
MaxNumberOfMessages: 1,
WaitTimeSeconds: 0,
AttributeNames: ['ApproximateReceiveCount'],
}}));
const messages = response.Messages || [];
if (messages.length === 0) return false;
for (const envelope of messages) {{
const ok = await _nyxDispatchEnvelope(envelope);
if (ok && envelope.ReceiptHandle) {{
await client.send(new sqs.DeleteMessageCommand({{
QueueUrl: queueUrl,
ReceiptHandle: envelope.ReceiptHandle,
}}));
}}
}}
if (typeof client.destroy === 'function') client.destroy();
return true;
}} catch (e) {{
process.stderr.write('NYX_REAL_SQS_FALLBACK: ' + (e && e.message ? e.message : String(e)) + '\n');
return false;
}}
}}
async function _nyxDispatchEnvelope(envelope) {{
try {{
// Sink-reachability sentinel — runner's `vuln_fired && sink_hit`
@ -959,6 +1006,7 @@ async function _nyxDispatchEnvelope(envelope) {{
}}
(async () => {{
if (await _nyxTryRealSqs({queue:?}, payload)) return;
process.stdout.write({publish_marker:?} + ' ' + {queue:?} + '\n');
_nyxRecordBrokerPublish('NYX_SQS_LOG', {queue:?}, payload);
_broker.publish({queue:?}, payload);

View file

@ -958,22 +958,23 @@ fn emit_message_handler(spec: &HarnessSpec, queue: &str) -> HarnessSource {
let register_and_publish = match broker {
PythonBroker::Sqs => format!(
r#"_loop = NyxSqsLoopback()
def _nyx_sqs_dispatch(envelope):
_h = getattr(_entry_mod, {handler:?}, None)
if _h is None:
print("NYX_HANDLER_NOT_FOUND: " + {handler:?}, file=sys.stderr, flush=True)
sys.exit(78)
_h(envelope)
_loop.subscribe({queue:?}, _nyx_sqs_dispatch)
print({publish_marker:?} + " " + {queue:?}, flush=True)
_nyx_record_broker_publish("NYX_SQS_LOG", {queue:?}, payload)
_loop.publish({queue:?}, payload)
for _env in _loop.receive_message({queue:?}, max_number=1):
_nyx_record_broker_event("NYX_SQS_LOG", "deliver", {queue:?}, _env.get("Body", ""))
_nyx_sqs_dispatch(_env)
if _loop.delete_message({queue:?}, _env.get("ReceiptHandle", "")):
_nyx_record_broker_event("NYX_SQS_LOG", "ack", {queue:?}, _env.get("ReceiptHandle", ""))"#,
r#"if not _nyx_try_real_sqs({queue:?}, payload, {handler:?}):
_loop = NyxSqsLoopback()
def _nyx_sqs_dispatch(envelope):
_h = getattr(_entry_mod, {handler:?}, None)
if _h is None:
print("NYX_HANDLER_NOT_FOUND: " + {handler:?}, file=sys.stderr, flush=True)
sys.exit(78)
_h(envelope)
_loop.subscribe({queue:?}, _nyx_sqs_dispatch)
print({publish_marker:?} + " " + {queue:?}, flush=True)
_nyx_record_broker_publish("NYX_SQS_LOG", {queue:?}, payload)
_loop.publish({queue:?}, payload)
for _env in _loop.receive_message({queue:?}, max_number=1):
_nyx_record_broker_event("NYX_SQS_LOG", "deliver", {queue:?}, _env.get("Body", ""))
_nyx_sqs_dispatch(_env)
if _loop.delete_message({queue:?}, _env.get("ReceiptHandle", "")):
_nyx_record_broker_event("NYX_SQS_LOG", "ack", {queue:?}, _env.get("ReceiptHandle", ""))"#,
handler = handler,
queue = queue,
publish_marker = crate::dynamic::stubs::SQS_PUBLISH_MARKER,
@ -1063,6 +1064,61 @@ def _nyx_record_broker_event(env_name, action, destination, body):
def _nyx_record_broker_publish(env_name, destination, body):
_nyx_record_broker_event(env_name, "publish", destination, body)
def _nyx_try_real_sqs(queue, body, handler_name):
endpoint = os.environ.get("NYX_SQS_ENDPOINT", "")
if not (endpoint.startswith("http://") or endpoint.startswith("https://")):
return False
try:
import boto3
try:
from botocore.config import Config
_cfg = Config(
retries={{"max_attempts": 0}},
connect_timeout=1,
read_timeout=2,
)
except Exception:
_cfg = None
except Exception:
return False
_h = getattr(_entry_mod, handler_name, None)
if _h is None:
print("NYX_HANDLER_NOT_FOUND: " + handler_name, file=sys.stderr, flush=True)
sys.exit(78)
try:
_kwargs = {{
"endpoint_url": endpoint,
"region_name": "us-east-1",
"aws_access_key_id": "nyx",
"aws_secret_access_key": "nyx",
}}
if _cfg is not None:
_kwargs["config"] = _cfg
_client = boto3.client("sqs", **_kwargs)
_queue_url = endpoint.rstrip("/") + "/" + str(queue).strip("/")
print({sqs_publish_marker:?} + " " + str(queue), flush=True)
_client.send_message(QueueUrl=_queue_url, MessageBody=str(body))
_resp = _client.receive_message(
QueueUrl=_queue_url,
MaxNumberOfMessages=1,
WaitTimeSeconds=0,
AttributeNames=["ApproximateReceiveCount"],
)
_messages = _resp.get("Messages", [])
if not _messages:
return False
for _msg in _messages:
_h(_msg)
_receipt = _msg.get("ReceiptHandle", "")
if _receipt:
_client.delete_message(QueueUrl=_queue_url, ReceiptHandle=_receipt)
return True
except SystemExit:
raise
except Exception as _e:
print(f"NYX_REAL_SQS_FALLBACK: {{type(_e).__name__}}: {{_e}}", file=sys.stderr, flush=True)
return False
try:
{register_and_publish}
except SystemExit as _e:
@ -1075,6 +1131,7 @@ except Exception as _e:
pubsub_src = pubsub_src,
rabbit_src = rabbit_src,
register_and_publish = indent_lines(&register_and_publish, " "),
sqs_publish_marker = crate::dynamic::stubs::SQS_PUBLISH_MARKER,
);
HarnessSource {
source: format!("{preamble}\n{body}\n{postamble}"),