Commit 8e9aeb9
fix(executor): stop resolved data from breaking out of generated condition and function code (#7300)
* fix(executor): stop resolved data from breaking out of generated code
A Condition expression is compiled by inlining each resolved reference as
source text, and the literal that gets emitted only ever anticipated the
quoting it chose itself. The author's quoting decides the real context, so
`"<start.input>".includes('urgent')` — a shape that works correctly with
benign data — let webhook, chat, or form data close the author's string and
run as JavaScript in the condition sandbox, which receives the workspace's
whole decrypted environment as the `environmentVariables` global. Template
literals, regex literals, and a crafted object key reached the same place.
Both condition formatters now escape every terminator of every JavaScript
string context rather than the one they open. `\"`, `` \` ``, `\$` and `\/`
are identity escapes, so a condition compares exactly what it compared
before; only its ability to parse as anything but data changes. A quoted
object reference additionally escapes its JSON, the only reading of that
shape that was not already a syntax error.
Function blocks bind block outputs as context variables but inlined the
remaining resolved values as literals, so a workflow variable a Variables
block had assigned from trigger data, or a loop item, could close the
string it landed in. Those bind now too. Numbers, booleans, null, and
strings that name an environment variable stay inline: the first three
cannot terminate a literal, and the last has to stay in source because the
placeholder — never the secret — is what is inlined, and the
execution-boundary compiler binds it downstream.
Condition evaluation also stops shipping the full secret map to the
sandbox: it mounts only the names its script references, so a future defect
in this path reaches nothing the condition did not already name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): parse condition objects instead of splicing their JSON
Escaping a quoted object's JSON left the quote scanner load-bearing for
injection: it does not track regex literals, so a quote inside one
desynchronizes it and a later object reference is reported as unquoted,
which put raw attacker-shaped JSON back into source. A reference inside a
regex literal reached the same place through its unescaped slashes.
Objects outside a string are now parsed at runtime from a fully escaped
literal. The value is identical to the object literal it replaces, and the
emitted form carries no quote, slash, backtick or `${`, so it stays inert
whichever context the scanner reports.
Scoping now reads the expressions for a direct `environmentVariables`
access rather than the whole generated script, so a source block's output
containing that word can no longer widen the mounted secret set. The
placeholder scan still reads the built script, which is the text the
execution-boundary compiler substitutes over.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): widen the condition environment read to any mention
A member-access pattern decides whether a condition keeps the full secret
map, and every shape it fails to anticipate — `environmentVariables?.FLAG`,
a read through `Object.keys` — silently narrows what that expression can
see and routes the run down a branch the author did not write. Matching the
bare identifier inside the expressions costs only the narrowing, and never
mounts more than this path mounted before it existed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): teach the code scanner about regex literals
A regex body is the one place a lone quote is not a string delimiter, and
the scanner did not track regex literals at all: `/['"]/` left it believing
everything after it sat inside a string. Every later reference was then
formatted for a context it was not in — a quoted object reference stayed
raw source, and after the previous commit a bare one was emitted as escaped
JSON, which cannot parse, so a valid condition threw instead of routing.
The scan now enters regex mode where a `/` can only be a regex — division
always follows a value, so the preceding token decides — and tracks escapes
and character classes until the closing delimiter. A reference inside a
regex reports its own context, so its JSON is escaped as pattern text
rather than spliced with delimiters the data could forge.
The same scanner decides how function-block references are spliced, so this
also repairs quoting for code that matches on a quote-bearing pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): keep resolved data out of the condition secret decision
The built script carries the source block's output as data, so scanning it
for placeholders let a caller choose which secret materializes beside its
own payload: `{{SECRET}}` in trigger data mounted that secret and had the
compiler expand it into the serialized context. Both scans now read the
expressions, which is where every legitimate route to a secret runs —
including a workflow variable holding `{{NAME}}`, since the resolver inlines
that value into the expression before this handler sees it.
`throw` joins the keywords a regex may follow. `throw /re/` is legal, and
without it the scan reads the pattern body as code and mis-reports the
context of everything after it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): read what a closing parenthesis closed
`)` ends a value in `(a + b) / 2` and a control-flow head in
`if (a) /re/.test(b)`, and the character alone does not say which. Treating
it as one or the other unconditionally misreads the other: as a value, a
statement-position regex is scanned as code, and a quote inside it decides
how every reference sharing that line is spliced; as a head, ordinary
division opens a regex that swallows the quotes after it.
The scan now records which kind of parenthesis each `)` closed, by reading
the keyword in front of its opener, and answers from that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): decide the environment read from the author's own text
Resolved data is quoted inside the expression the handler sees, so a
payload containing `environmentVariables` read exactly like the author
reaching for the map, and a caller could restore the full secret set by
sending the word. The resolver now records that answer per branch from the
pre-resolution expression, where only the author's text exists, and the
handler reads the record — falling back to scanning only when a caller did
not resolve through the resolver, since narrowing a real read would route
the run silently.
A closed regex literal also counts as a value now, so the division in
`/re/.source.length / 2` no longer opens a second regex that swallows the
quotes after it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): do not read a keyword-named method as a control-flow head
`p.catch(fn)` is a call whose name happens to be a keyword, and what
follows its `)` is an operator rather than a statement — so the division in
`p.catch(fn) / 2` opened regex mode and ran over the quotes around any
reference later on that line. A control-flow head is never a property
access, so the check now refuses one that follows a dot.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): keep a caller from choosing which secret expands
A placeholder-bearing string stays in source so the boundary compiler can
expand it, which is how a workflow variable holding `{{NAME}}` reaches its
value. Any run value took that path too, so text arriving from a trigger or
a loop item could name a secret and have the compiler materialize it beside
the payload that named it. Only a workflow variable — a surface an author
configures — keeps the inline form now; every other value binds.
A block comment can also stand between a property-access dot and a
keyword-named method, hiding the dot from the control-flow-head check. A
comment ending there now reads as the method call it almost always is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): step over comments instead of reading them as tokens
Treating a comment end as the answer was too blunt: `/* c */ if (x)` is a
control-flow head, and calling it a method left a statement-position regex
scanned as division — the failure the comment guard was added to prevent,
moved one shape over. The scan now steps back over comments to the token
that precedes them, so the dot in `p./* c */catch(fn)` is still found and
a keyword after a comment is still a head.
A condition's environment read is placed the same way references are: an
occurrence inside a string, a template, or a regex is text and mounts
nothing, while any executable read — whatever its shape — keeps the map.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): take the preceding token from the scan, not from a walk back
Reading backwards cannot tell which characters were code: a block comment
opens at its first delimiter, so `p./* a /* b */catch(fn)` defeated a
search for the nearest `/*` and the call read as a control-flow head again.
The scan already knows — it stepped over that comment on the way in — so
the two facts the check needs, the token before the parenthesis and whether
it followed a property access, are now recorded as it passes and read from
there. No search back through the source, and nothing left for a comment
body to imitate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): divide after a postfix update
`+` and `-` precede a regex as operators, but doubled they end a value, so
`i++ / 2` was scanning a regex from the division and swallowing whatever
quotes followed it on that line. The check now reads the pair rather than
the single character; a lone `+` still admits `params.n + /re/.test(x)`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(executor): start a new identifier at the character before it
A token continues only when the character immediately before it belongs to
the same token. Asking the previous *significant* character instead made a
name after a line break look like a continuation, so it kept whatever
property-access answer the last token had: `const seen = params.a.b` on one
line left the `if` on the next carrying `b`'s, which turned the statement
head into a method call and the regex after it into division.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 70783dc commit 8e9aeb9
7 files changed
Lines changed: 936 additions & 56 deletions
File tree
- apps/sim/executor
- handlers/condition
- utils
- variables
- resolvers
Lines changed: 101 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
205 | 306 | | |
206 | 307 | | |
207 | 308 | | |
| |||
Lines changed: 60 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| 20 | + | |
19 | 21 | | |
20 | 22 | | |
21 | 23 | | |
| |||
28 | 30 | | |
29 | 31 | | |
30 | 32 | | |
| 33 | + | |
| 34 | + | |
31 | 35 | | |
32 | 36 | | |
33 | 37 | | |
| |||
88 | 92 | | |
89 | 93 | | |
90 | 94 | | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
91 | 139 | | |
92 | 140 | | |
93 | 141 | | |
| |||
100 | 148 | | |
101 | 149 | | |
102 | 150 | | |
| 151 | + | |
103 | 152 | | |
104 | 153 | | |
105 | 154 | | |
| 155 | + | |
106 | 156 | | |
107 | 157 | | |
108 | 158 | | |
109 | 159 | | |
110 | 160 | | |
111 | 161 | | |
| 162 | + | |
| 163 | + | |
112 | 164 | | |
113 | 165 | | |
114 | 166 | | |
| |||
143 | 195 | | |
144 | 196 | | |
145 | 197 | | |
146 | | - | |
| 198 | + | |
147 | 199 | | |
148 | 200 | | |
149 | 201 | | |
| 202 | + | |
150 | 203 | | |
151 | 204 | | |
152 | 205 | | |
| 206 | + | |
153 | 207 | | |
154 | 208 | | |
155 | 209 | | |
| |||
229 | 283 | | |
230 | 284 | | |
231 | 285 | | |
232 | | - | |
| 286 | + | |
233 | 287 | | |
234 | 288 | | |
235 | 289 | | |
| 290 | + | |
236 | 291 | | |
237 | | - | |
| 292 | + | |
238 | 293 | | |
239 | 294 | | |
240 | 295 | | |
| |||
418 | 473 | | |
419 | 474 | | |
420 | 475 | | |
421 | | - | |
422 | | - | |
423 | 476 | | |
424 | 477 | | |
425 | | - | |
| 478 | + | |
426 | 479 | | |
427 | 480 | | |
428 | 481 | | |
| |||
474 | 527 | | |
475 | 528 | | |
476 | 529 | | |
477 | | - | |
| 530 | + | |
478 | 531 | | |
479 | 532 | | |
480 | 533 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
0 commit comments