Back to News & Insights
JavaScript September 26, 2026 · 8 min read

Offensive Coding: Exploiting JavaScript's Tiny Door

Offensive Coding: The Four-Line Function TypeScript Says Is Broken And Isn't The...

Offensive Coding: Exploiting JavaScript's Tiny Door

The : Promise. The s ? s : undefined. Both reflexive. Both feel correct. Both close a door that has been open since async/await shipped.

Four lines. No async. No await. No types. And it does something TypeScript insists is impossible.

await is a contextual keyword. It's only the await operator inside a function explicitly marked async. Outside that context, it's an identifier. A variable name.

That's why async exists. It tells the engine: in here, await means the operator. Outside async functions, await is invisible.

And it doesn't propagate. If foo is async and calls bar, bar is not async. bar has its own execution context. If bar is a plain function, the engine never arms async protocol for it — not at entry, not when it receives a Promise as an argument, not when it returns one.

So a Promise that enters a non-async function as an opaque argument, passes through a ternary that never touches it as a Promise, and leaves the same way — is invisible to the engine. It doesn't get wrapped. It doesn't get chained. It doesn't get touched. The engine never knows it was there.

That's the door. It's been open since the language shipped. Nobody uses it because every instinct says not to.

Not instanceof Promise. Not != null. Not typeof. undefined is the only check, and undefined is the only value that means "not here yet."

Every other value — 0, '', false, null, NaN, 0n, Symbol() — is a value. It's here. The gate returns it raw. No Promise. No microtask. No wrap.

undefined is the only value that means "later." When the gate sees it, it returns Promise.resolve(undefined). The caller's await, if they use one, gets back undefined. Same value in, same value out, indistinguishable from never having gone async.

The sentinel and the payload are the same token. So the caller never has to know which branch fired. await handles the async case. Direct access handles the sync case. Same function. Same call. Both correct.

Eighty test shapes. Every falsy-but-defined value. A Promise passed through and coming out the same reference. undefined round-tripping. The gate used in arithmetic, iteration, destructuring, JSON, Map keys, Set membership, template literals, spread, closures, recursion, Promise.all, Object.assign, structuredClone, switch, while loops, reduce, filter, every way a value can flow.

The types were never the thing. The runtime is the thing. The types are a description that may or may not match, and the runtime doesn't care either way.

Open that function in a TypeScript file. Turn on strict mode. Here's what happens: TS7031 — Binding element 'value' implicitly has an 'any' type. TS7006 — Parameter 'value' implicitly has an 'any' type. Return type inferred warnings depending on config. Red squiggles up and down the function.

The editor screams. Every line is wrong. Every dev who opens the file sees red and thinks broken.

TypeScript does not validate correctness. TypeScript validates conformity to its model.

Its model does not include this shape. It has no representation for "returns a value on one branch, a Promise on the other, without the async keyword." So when it sees that shape, it doesn't say "I can't describe this." It says "this is wrong."

Those are two completely different statements. "I can't describe this" — a fact about TypeScript. "This is wrong" — a claim about your code.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation