Repository: localstack/localstack

Issue:
bug: local node lambda executor unable to handle payloads with single quotes
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

When invoking a lambda with a nodejs runtime and local executor, localstack is unable to handle payloads containing a single quote.

### Expected Behavior

Locally executed nodejs lambdas should be able to handle any stringified json payload

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

https://github.com/alxyuu/localstack-issue

### Environment

```markdown
- OS: MacOS 11.5.2
- LocalStack: latest (0.12.18)
```


### Anything else?

The culprit appears to be in how node gets executed locally, with a single quoted eval:
https://github.com/localstack/localstack/blob/812acc1226537ee92477bd91596de200b8888a6c/localstack/services/awslambda/lambda_executors.py#L1262-L1270

doing something like `event_json_string.replace("'", "'\\''")` may be a simple resolution to the issue.


Discussion:
Happened to me as well

Resolving patch:
diff --git a/localstack/services/awslambda/lambda_executors.py b/localstack/services/awslambda/lambda_executors.py
--- a/localstack/services/awslambda/lambda_executors.py
+++ b/localstack/services/awslambda/lambda_executors.py
@@ -1112,7 +1112,7 @@ def execute_in_container(
 
 class LambdaExecutorLocal(LambdaExecutor):
     def _execute_in_custom_runtime(
-        self, cmd: str, lambda_function: LambdaFunction = None
+        self, cmd: Union[str, List[str]], lambda_function: LambdaFunction = None
     ) -> InvocationResult:
         """
         Generic run function for executing lambdas in custom runtimes.
@@ -1313,15 +1313,17 @@ def execute_javascript_lambda(
         function = handler.split(".")[-1]
         event_json_string = "%s" % (json.dumps(json_safe(event)) if event else "{}")
         context_json_string = "%s" % (json.dumps(context.__dict__) if context else "{}")
-        cmd = (
-            "node -e 'require(\"%s\").%s(%s,%s).then(r => process.stdout.write(JSON.stringify(r)))'"
+        cmd = [
+            "node",
+            "-e",
+            'require("%s").%s(%s,%s).then(r => process.stdout.write(JSON.stringify(r)))'
             % (
                 main_file,
                 function,
                 event_json_string,
                 context_json_string,
-            )
-        )
+            ),
+        ]
         LOG.info(cmd)
         result = self._execute_in_custom_runtime(cmd, lambda_function=lambda_function)
         return result


Test patch:
diff --git a/tests/integration/test_lambda.py b/tests/integration/test_lambda.py
--- a/tests/integration/test_lambda.py
+++ b/tests/integration/test_lambda.py
@@ -1527,6 +1527,37 @@ def test_invoke_nodejs_lambda(self):
             # clean up
             testutil.delete_lambda_function(TEST_LAMBDA_NAME_JS)
 
+    def test_invoke_nodejs_lambda_with_payload_containing_quotes(self):
+        handler_file = os.path.join(THIS_FOLDER, "lambdas", "lambda_handler.js")
+        function_name = "test_lambda_%s" % short_uid()
+        testutil.create_lambda_function(
+            func_name=function_name,
+            zip_file=testutil.create_zip_file(handler_file, get_content=True),
+            runtime=LAMBDA_RUNTIME_NODEJS14X,
+            handler="lambda_handler.handler",
+        )
+
+        test_string = "test_string' with some quotes"
+        body = '{"test_var": "%s"}' % test_string
+        try:
+            rs = self.lambda_client.invoke(
+                FunctionName=function_name,
+                Payload=body,
+            )
+            assert 200 == rs["ResponseMetadata"]["HTTPStatusCode"]
+
+            payload = rs["Payload"].read()
+            response = json.loads(to_str(payload))
+            assert "response from localstack lambda" in response["body"]
+
+            events = get_lambda_log_events(function_name)
+            assert len(events) > 0
+            assert test_string in str(events[0])
+
+        finally:
+            # clean up
+            testutil.delete_lambda_function(function_name)
+
 
 class TestCustomRuntimes(LambdaTestBase):
     @classmethod