Summary
lintClosedTerm (shared/compiler/lint.ts) is the only guard that keeps connector fns "closed terms", but it can be bypassed in two ways. A fn that passes the lint can reach the host's real globals (fetch, process, Deno, ...). engine/link.ts then instantiates it with new Function(...), which runs in the global scope, so nothing else stops it.
This matters because the auth inject fn receives the resolved credentials in params, and the repo accepts community connector PRs.
Bypass 1: .constructor reaches Function
The lint treats obj.prop names as non-references, and the whitelist exposes Object, Array, String, etc. So Function is reachable without any free identifier:
(data) => [].constructor.constructor("return globalThis")()
(data) => Object.constructor("return globalThis")()Bypass 2: scope over-approximation
The lint treats any name declared anywhere inside the fn as bound everywhere inside it, so a decoy declaration hides a real global:
(data) => {
const unused = () => { const fetch = 1; return fetch; };
return typeof fetch; // the real global fetch; lint passes
}Reproduction
Running the unmodified lint.ts over these sources (TypeScript AST only, run under Node) gives:
| Source | Lint | When executed |
|---|---|---|
Deno.env.get("X") (control) | rejected, as intended | n/a |
[].constructor.constructor("return typeof process")() | passes | "object" |
Object.constructor("return typeof Deno")() | passes | runs |
decoy const fetch + typeof fetch | passes | "function" |
I grepped shared/compiler, engine and shared/core for constructor, __proto__ and globalThis and found no other mitigation. CI runs only deno task check and deno task test.
Impact
A connector merged with such a fn can read credentials in auth.inject and send them out via fetch, or run arbitrary code wherever the compiled bundle is executed. It needs a merged connector, so it is a supply-chain problem rather than remotely exploitable. I don't know whether the hosted Relay sandboxes fns separately.
Hey @ooctoo777 @FeiyouG, I found a bypass in the closed-term lint (#178) that lets a fn reach real globals like
fetchorprocess, and opened a fix in #183. All existing connectors still compile and the full test suite passes. Please take a look and let me know if you'd like any changes?