Skip to content
File

Blob: src/workerd/api/ser-errors-test.c++

8.7 KB
1#include <workerd/io/features.h>
2#include <workerd/jsg/ser.h>
3#include <workerd/tests/test-fixture.h>
4 
5#include <kj/test.h>
6 
7namespace workerd::api {
8namespace {
9 
10KJ_TEST("Stacks not preserved in untrusted deserialization") {
11 capnp::MallocMessageBuilder message;
12 auto flags = message.initRoot<CompatibilityFlags>();
13 flags.setEnhancedErrorSerialization(true);
14 flags.setWorkerdExperimental(true);
15 auto t = TestFixture({.featureFlags = flags.asReader()});
16 
17 t.runInIoContext([](const workerd::TestFixture::Environment& env) {
18 auto obj = KJ_ASSERT_NONNULL(env.js.typeError(""_kj).tryCast<jsg::JsObject>());
19 obj.set(env.js, "foo"_kj, env.js.str("bar"_kj));
20 obj.set(env.js, "stack"_kj, env.js.str("test stack"_kj));
21 auto stack = obj.get(env.js, "stack"_kj);
22 
23 KJ_ASSERT(FeatureFlags::get(env.js).getEnhancedErrorSerialization());
24 
25 jsg::Serializer ser(env.js);
26 ser.write(env.js, obj);
27 auto content = ser.release();
28 {
29 // Untrusted... stack must not be preserved.
30 jsg::Deserializer deser(env.js, content.data, kj::none, kj::none,
31 jsg::Deserializer::Options{.preserveStackInErrors = false});
32 auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());
33 auto checkedStack = val.get(env.js, "stack"_kj);
34 KJ_ASSERT(!checkedStack.strictEquals(stack));
35 }
36 {
37 // Trusted ... stack must be preserved.
38 jsg::Deserializer deser(env.js, content.data, kj::none, kj::none,
39 jsg::Deserializer::Options{.preserveStackInErrors = true});
40 auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());
41 auto checkedStack = val.get(env.js, "stack"_kj);
42 KJ_ASSERT(checkedStack.strictEquals(stack));
43 }
44 {
45 // When using structuredClone, stacks are preserved by default.
46 auto obj = KJ_ASSERT_NONNULL(env.js.typeError(""_kj).tryCast<jsg::JsObject>());
47 obj.set(env.js, "name"_kj, env.js.str("CustomError"_kj));
48 obj.set(env.js, "stack"_kj, env.js.str("test stack"_kj));
49 obj.set(env.js, "foo"_kj, env.js.str("bar"_kj));
50 
51 auto other = KJ_ASSERT_NONNULL(jsg::structuredClone(env.js, obj).tryCast<jsg::JsObject>());
52 auto checkedStack = other.get(env.js, "stack"_kj);
53 KJ_ASSERT(checkedStack.strictEquals(obj.get(env.js, "stack"_kj)));
54 KJ_ASSERT(other.get(env.js, "foo"_kj).strictEquals(obj.get(env.js, "foo"_kj)));
55 KJ_ASSERT(other.get(env.js, "name"_kj).strictEquals(obj.get(env.js, "name"_kj)));
56 }
57 });
58}
59 
60KJ_TEST("Stacks preserved by default when using regular deserialization") {
61 auto t = TestFixture();
62 
63 t.runInIoContext([](const workerd::TestFixture::Environment& env) {
64 auto obj = KJ_ASSERT_NONNULL(env.js.typeError(""_kj).tryCast<jsg::JsObject>());
65 obj.set(env.js, "foo"_kj, env.js.str("bar"_kj));
66 obj.set(env.js, "stack"_kj, env.js.str("test stack"_kj));
67 auto stack = obj.get(env.js, "stack"_kj);
68 
69 KJ_ASSERT(!FeatureFlags::get(env.js).getEnhancedErrorSerialization());
70 
71 jsg::Serializer ser(env.js);
72 ser.write(env.js, obj);
73 auto content = ser.release();
74 {
75 // By default, stacks are preserved.
76 jsg::Deserializer deser(env.js, content.data);
77 auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());
78 auto checkedStack = val.get(env.js, "stack"_kj);
79 KJ_ASSERT(checkedStack.strictEquals(stack));
80 }
81 {
82 // Trusted ... stack must be preserved.
83 jsg::Deserializer deser(env.js, content.data, kj::none, kj::none,
84 // The option is ignored since the compat flag is off
85 jsg::Deserializer::Options{.preserveStackInErrors = false});
86 auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());
87 auto checkedStack = val.get(env.js, "stack"_kj);
88 KJ_ASSERT(checkedStack.strictEquals(stack));
89 }
90 });
91}
92 
93KJ_TEST("Tunneled exceptions do not preserve stack by default") {
94 capnp::MallocMessageBuilder message;
95 auto flags = message.initRoot<CompatibilityFlags>();
96 flags.setEnhancedErrorSerialization(true);
97 flags.setWorkerdExperimental(true);
98 auto t = TestFixture({.featureFlags = flags.asReader()});
99 
100 t.runInIoContext([](const workerd::TestFixture::Environment& env) {
101 auto obj = KJ_ASSERT_NONNULL(env.js.typeError("abc"_kj).tryCast<jsg::JsObject>());
102 obj.set(env.js, "name"_kj, env.js.str("CustomError"_kj));
103 obj.set(env.js, "foo"_kj, env.js.str("bar"_kj));
104 obj.set(env.js, "stack"_kj, env.js.str("test stack"_kj));
105 auto stack = obj.get(env.js, "stack"_kj);
106 
107 KJ_ASSERT(FeatureFlags::get(env.js).getEnhancedErrorSerialization());
108 
109 {
110 // Untrusted... stack must not be preserved.
111 kj::Exception ex = env.js.exceptionToKj(obj);
112 auto val = env.js.exceptionToJsValue(kj::mv(ex));
113 auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());
114 auto checkedStack = obj.get(env.js, "stack"_kj);
115 KJ_ASSERT(!checkedStack.strictEquals(stack));
116 KJ_ASSERT(obj.get(env.js, "name"_kj).strictEquals(env.js.str("CustomError"_kj)));
117 KJ_ASSERT(obj.get(env.js, "message"_kj).strictEquals(env.js.str("abc"_kj)));
118 KJ_ASSERT(obj.get(env.js, "foo"_kj).strictEquals(env.js.str("bar"_kj)));
119 }
120 {
121 // Trusted... stack must be preserved.
122 kj::Exception ex = env.js.exceptionToKj(obj);
123 auto val = env.js.exceptionToJsValue(kj::mv(ex), {.trusted = true});
124 auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());
125 auto checkedStack = obj.get(env.js, "stack"_kj);
126 KJ_ASSERT(checkedStack.strictEquals(stack));
127 KJ_ASSERT(obj.get(env.js, "name"_kj).strictEquals(env.js.str("CustomError"_kj)));
128 KJ_ASSERT(obj.get(env.js, "message"_kj).strictEquals(env.js.str("abc"_kj)));
129 KJ_ASSERT(obj.get(env.js, "foo"_kj).strictEquals(env.js.str("bar"_kj)));
130 }
131 {
132 // Ignore detail means we reconstruct the error without the serialized detail
133 kj::Exception e = env.js.exceptionToKj(obj);
134 auto val = env.js.exceptionToJsValue(kj::mv(e), {.ignoreDetail = true});
135 auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());
136 auto checkedStack = obj.get(env.js, "stack"_kj);
137 KJ_ASSERT(!checkedStack.strictEquals(stack));
138 KJ_ASSERT(obj.get(env.js, "name"_kj).strictEquals(env.js.str("Error"_kj)));
139 KJ_ASSERT(obj.get(env.js, "message"_kj).strictEquals(env.js.str("CustomError: abc"_kj)));
140 KJ_ASSERT(obj.get(env.js, "foo"_kj).strictEquals(env.js.undefined()));
141 }
142 });
143}
144 
145KJ_TEST("Tunneled exceptions do not preserve stack by default") {
146 auto t = TestFixture();
147 
148 t.runInIoContext([](const workerd::TestFixture::Environment& env) {
149 auto obj = KJ_ASSERT_NONNULL(env.js.typeError("abc"_kj).tryCast<jsg::JsObject>());
150 obj.set(env.js, "name"_kj, env.js.str("CustomError"_kj));
151 obj.set(env.js, "foo"_kj, env.js.str("bar"_kj));
152 obj.set(env.js, "stack"_kj, env.js.str("test stack"_kj));
153 auto stack = obj.get(env.js, "stack"_kj);
154 
155 KJ_ASSERT(!FeatureFlags::get(env.js).getEnhancedErrorSerialization());
156 
157 {
158 kj::Exception ex = env.js.exceptionToKj(obj);
159 auto val = env.js.exceptionToJsValue(kj::mv(ex));
160 auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());
161 auto checkedStack = obj.get(env.js, "stack"_kj);
162 KJ_ASSERT(!checkedStack.strictEquals(stack));
163 KJ_ASSERT(obj.get(env.js, "name"_kj).strictEquals(env.js.str("Error"_kj)));
164 KJ_ASSERT(obj.get(env.js, "message"_kj).strictEquals(env.js.str("CustomError: abc"_kj)));
165 KJ_ASSERT(obj.get(env.js, "foo"_kj).strictEquals(env.js.undefined()));
166 }
167 {
168 kj::Exception ex = env.js.exceptionToKj(obj);
169 auto val = env.js.exceptionToJsValue(kj::mv(ex), {.ignoreDetail = true});
170 auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());
171 auto checkedStack = obj.get(env.js, "stack"_kj);
172 KJ_ASSERT(!checkedStack.strictEquals(stack));
173 KJ_ASSERT(obj.get(env.js, "name"_kj).strictEquals(env.js.str("Error"_kj)));
174 KJ_ASSERT(obj.get(env.js, "message"_kj).strictEquals(env.js.str("CustomError: abc"_kj)));
175 KJ_ASSERT(obj.get(env.js, "foo"_kj).strictEquals(env.js.undefined()));
176 }
177 {
178 kj::Exception ex = env.js.exceptionToKj(obj);
179 auto val = env.js.exceptionToJsValue(kj::mv(ex), {.trusted = true});
180 auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());
181 auto checkedStack = obj.get(env.js, "stack"_kj);
182 KJ_ASSERT(checkedStack.strictEquals(stack));
183 KJ_ASSERT(obj.get(env.js, "name"_kj).strictEquals(env.js.str("Error"_kj)));
184 KJ_ASSERT(obj.get(env.js, "message"_kj).strictEquals(env.js.str("abc"_kj)));
185 KJ_ASSERT(obj.get(env.js, "foo"_kj).strictEquals(env.js.undefined()));
186 }
187 });
188}
189 
190} // namespace
191} // namespace workerd::api