@Rubén suggests to use const
, which should work – the docs say only enumerable global properties are visible to library users.
However, for me functions declared using const
(or let
) currently still appear in the autocomplete list. This is probably a bug in the editor, because their value is undefined
.
As a workaround until that bug is fixed, it is possible to define a non-enumerable property like this:
Object.defineProperty(globalThis, "onOpen", {
enumerable: false,
value: function() {
...
},
});
For me this hides the function from the autocomplete list and prevents the app from calling the function. (Contrast with globalThis.onOpen = function() { ... };
, which currently hides the function from the autocomplete list but allows the app to call the function.)
The question is about hiding functions defined in a library from apps that use the library. I originally misinterpreted the question and thought it was about hiding functions defined in a web app from being called from the client side using google.script.run. I am leaving the answer for the misinterpreted version of the question below in case it is useful to future readers.
How can I protect functions from being called from the client side?
Background: By default, functions declared in a web app can be called from the client side (web browser) using google.script.run.myFunction()
. This feature is very powerful and convenient, but can have unexpected security implications.
Here are some ways to protect a function from being called from the client side:
A. Make the function private by appending "_" to its name (docs), e.g.
function myFunction_() {
...
}
B. Make the function a property of an object, e.g.
var obj = {
myFunction: function() {
...
}
};
(This function can be called on the server side using obj.myFunction()
and cannot be called from the client side; ref.)
C. Write your code inside an immediately-invoked function expression:
(function() {
function myFunction() {
...
}
globalThis.onOpen = function() {
...
};
})();
(With this approach, special functions such as onOpen
will need to be declared as shown, using the syntax globalThis.onOpen = ...
rather than function onOpen() { ... }
, in order expose them publicly while allowing them to call the private functions.)
Ways that don't work:
Declaring a web app function with const
does not prevent it from being called from the client side, so this cannot be used to protect functions.
Declaring a web app function using globalThis.myFunction = () => { ... };
will prevent its name from being sent to the client (so running google.script.run.myFunction()
in the web console will fail), however functions declared this way can still be called from the client side – it just requires a little more effort. So this is not a good security measure (see "security through obscurity"); also this behaviour is undocumented and may change in future versions of Google Apps Script.
How can I protect special functions such as onOpen
from being called from the client side?
There are some special functions such as onOpen which must have that exact name. The approaches above (A, B, C) are not suitable for these special functions.
As far as I know there is no general-purpose way to define these special functions in a way that prevents them from being called from the client side.
However, there is a trick that can be used in some cases to detect whether the function was triggered normally or called from the client side. The idea is to look for properties on the event object that can't be created using google.script.run
. Example:
function onOpen(e) {
if (!e || !e.source || typeof e.source.getId !== "function") {
throw new Error("Invalid argument");
}
...
}
This code verifies that e.source
is really a Document object by checking that it has a getId() function. Since the arguments passed to google.script.run
are serialised as JSON (which doesn't support functions), it should be impossible for a call from the client side to spoof this function.
(A related idea is to check the prototype of e.source
, however that doesn't work because Document
isn't a real class – documents are represented as plain objects.)
An attempt that failed:
I tried to use new Error().stack
to distinguish between legitimate calls and calls from the client side, but the value was identical in both cases (also when I used a custom getter to intercept the function lookup).