SAFE(3tcl)
β¨ SAFE β¨ β creating and manipulating safe interpreters π
okay bestie, welcome to Safe Tcl, straight out of the Tcl Built-In Commands manual (the page is literally titled Safe Tcl(3tcl)), and this is part 1 of 3 so buckle up, we're not even done yet π«Ά
πΈ SYNOPSIS πΈ
::safe::interpCreate ?child? ?options...?
::safe::interpInit child ?options...?
::safe::interpConfigure child ?options...?
::safe::interpDelete child
::safe::interpAddToAccessPath child directory
::safe::interpFindInAccessPath child directory
::safe::setSyncMode ?newValue?
::safe::setLogCmd ?cmd arg...?
π OPTIONS
?-accessPath pathList??-autoPath pathList??-statics boolean??-noStatics??-nested boolean??-nestedLoadOk??-deleteHook script?
π DESCRIPTION π
so, the whole vibe of Safe Tcl? it's a mechanism for executing untrusted Tcl scripts safely and for giving those scripts mediated access to functionality that is, frankly, potentially dangerous. it's giving bodyguard energy π«Ά
Safe Tcl makes sure that untrusted Tcl scripts cannot harm the hosting application. literally. it prevents integrity and privacy attacks. untrusted Tcl scripts get blocked from corrupting the state of the hosting application or the computer, and they're also stopped from disclosing information stored on the hosting computer or in the hosting application to any party. no snitching, no leaking, periodt π
Safe Tcl lets a parent interpreter create safe, restricted interpreters. these restricted babes contain a set of predefined aliases for the source, load, file, encoding, and exit commands, and they're able to use the auto-loading and package mechanisms. slay β¨
here's the iconic part: no knowledge of the file system structure is leaked to the safe interpreter, because it only has access to a virtualized path containing tokens. when the safe interpreter asks to source a file, it uses the token in the virtual path as part of the file name to source; the parent interpreter transparently translates that token into a real directory name and executes the requested operation (peep the SECURITY section below for the deets, iykyk). and you can pick different levels of security using the optional flags of the commands described below. customize ur safety, it's giving main character π
and one more thing bestie: all the commands that Safe Tcl provides in the parent interpreter? they all live in the safe namespace. house rules π
π COMMANDS π
the following commands are provided in the parent interpreter:
π¦ ::safe::interpCreate ?child? ?options...?
creates a safe interpreter, installs the aliases described in the ALIASES section, and initializes the auto-loading and package mechanism as specified by the options you hand it. check the OPTIONS section below for what those optional arguments actually do. if you omit the child argument, a name will be generated for you (the universe provides β¨). ::safe::interpCreate always returns the interpreter name.
the interpreter name child may include namespace separators, but it may not have leading or trailing namespace separators, or excess colon characters in namespace separators. also β important β the interpreter name is qualified relative to the global namespace ::, not the namespace in which the ::safe::interpCreate command is evaluated. read that twice, bestie π
π¦ ::safe::interpInit child ?options...?
this one is similar to interpCreate except it does not create the safe interpreter. child must have been created by some other means, like interp create -safe. the interpreter name child may include namespace separators, subject to the exact same restrictions as for interpCreate.
π¦ ::safe::interpConfigure child ?options...?
if you give it no options, it returns the settings for all options for the named safe interpreter, as a list of options and their current values for that child. if you provide a single additional argument, it returns a list of 2 elements, name and value, where name is the full name of that option and value is the current value for that option and the child. if you provide more than two additional arguments, it reconfigures the safe interpreter and changes each and only the provided options. see the OPTIONS section below for the option descriptions. example of use:
# Create new interp with the same configuration as "$i0":
set i1 [safe::interpCreate {*}[safe::interpConfigure $i0]]
# Get the current deleteHook
set dh [safe::interpConfigure $i0 -del]
# Change (only) the statics loading ok attribute of an
# interp and its deleteHook (leaving the rest unchanged):
safe::interpConfigure $i0 -delete {foo bar} -statics 0
π¦ ::safe::interpDelete child
deletes the safe interpreter and cleans up the corresponding parent interpreter data structures. and if a deleteHook script was specified for this interpreter, it gets evaluated before the interpreter is deleted, with the name of the interpreter passed in as an additional argument. bye bestie, we'll run ur hook first π₯²
π¦ ::safe::interpFindInAccessPath child directory
finds and returns the token for the real directory directory in the safe interpreter's current virtual access path. if the directory isn't found, it generates an error. example of use:
$child eval [list set tk_library \
[::safe::interpFindInAccessPath $name $tk_library]]
π¦ ::safe::interpAddToAccessPath child directory
adds directory to the virtual path maintained for the safe interpreter in the parent, and returns the token that the safe interpreter can use to obtain access to files in that directory. and if the directory is already in the virtual path? it just returns the token without adding it again. efficient queen π example of use:
$child eval [list set tk_library \
[::safe::interpAddToAccessPath $name $tk_library]]
π¦ ::safe::setSyncMode ?newValue?
this one gets or sets the "Sync Mode" of the Safe Base. when you supply an argument, the command returns an error if the argument isn't a boolean value, or if any Safe Base interpreters exist. typically you set this as part of initialization β boolean true for "Sync Mode" on (that's the default), false for "Sync Mode" off. with "Sync Mode" on, the Safe Base keeps each child interpreter's ::auto_path synchronized with its access path. see the SYNC MODE section below for the details.
π¦ ::safe::setLogCmd ?cmd arg...?
this installs a script that gets called when interesting life cycle events happen for a safe interpreter. call it with no arguments and it returns the currently installed script. call it with one argument, an empty string, and the currently installed script is removed and logging is turned off. the script will be invoked with one additional argument β a string describing the event of interest. the main purpose here is helping you debug safe interpreters π. with this facility you get complete error messages while the safe interpreter itself only gets generic ones. this prevents a safe interpreter from seeing messages about failures and other events that might contain sensitive information such as real directory names. we keep the tea away from the baby interp π
example of use:
::safe::setLogCmd puts stderr
below is the output of a sample session in which a safe interpreter attempted to source a file that wasn't found in its virtual access path. note that the safe interpreter only received an error message saying the file was not found:
NOTICE for child interp10 : Created
NOTICE for child interp10 : Setting accessPath=(/foo/bar) staticsok=1 nestedok=0 deletehook=()
NOTICE for child interp10 : auto_path in interp10 has been set to {$p(:0:)}
ERROR for child interp10 : /foo/bar/init.tcl: no such file or directory
π OPTIONS π
the following options are common to ::safe::interpCreate, ::safe::interpInit, and ::safe::interpConfigure. any option name can be abbreviated to its minimal non-ambiguous name, and option names are not case sensitive. so go off with ur shorthand, bestie β¨
-accessPath directoryListβ sets the list of directories from which the safe interpreter cansourceandloadfiles. if you don't specify this option, or you give it as the empty list, the safe interpreter will use the same directories as its parent for auto-loading. see the SECURITY section below for more on virtual paths, tokens, and access control.-autoPath directoryListβ sets the list of directories in the safe interpreter's::auto_path. heads up though: this option is undefined if the Safe Base has "Sync Mode" on β in that case the safe interpreter's::auto_pathis managed by the Safe Base and is a tokenized form of its access path. see the SYNC MODE section below for details.-statics booleanβ specifies whether the safe interpreter will be allowed to load statically linked packages (likeload {} Tk). the default value is true: safe interpreters are allowed to load statically linked packages.-noStaticsβ a convenience shortcut for-statics false, so it specifies that the safe interpreter will not be allowed to load statically linked packages.-nested booleanβ specifies whether the safe interpreter will be allowed to load packages into its own sub-interpreters. the default value is false: safe interpreters are not allowed to load packages into their own sub-interpreters.-nestedLoadOkβ a convenience shortcut for-nested true, so it specifies the safe interpreter will be allowed to load packages into its own sub-interpreters.-deleteHook scriptβ when you give this a non-empty script, it will be evaluated in the parent with the name of the safe interpreter as an additional argument, just before actually deleting the safe interpreter. giving an empty value removes any currently installed deletion hook script for that safe interpreter. the default value ({}) is to not have any deletion call back. no callback, no drama π
π«Ά that's the whole OPTIONS section bestie β and no, I'm not signing off, there is more coming in parts 2 and 3, stay tuned π
π« ALIASES π«
Okay so these are the little helper aliases that come pre-loaded in a safe interpreter. Think of them as the supervised versions of the commands you know and love π
source fileNameβ so you wanna pull a Tcl source file into the safe interpreter? This does that, if the file can be found. But bestie, listen: thesourcealias can only source files from directories in the virtual path for the safe interpreter. That means the safe interpreter has to use one of the token names in its virtual path to denote the directory where the file you're sourcing lives. Want the full tea on valid filenames? Check the SECURITY section below, it's giving β¨gatekeepingβ¨.load fileNameβ this one dynamically loads a shared object file into the safe interpreter, if it's found. The filename must contain a token name mentioned in the virtual path for the safe interpreter or it will not be found successfully, periodt. And on top of that, the shared object file has to contain a safe entry point β peep the manual page for theloadcommand for the deets π«Άfile ?subcommand args...?β thefilealias gives you a safe subset of thefilecommand's subcommands. It only allowsdirname,join,extension,root,tail,pathtypeandsplit. That's it, that's the list π Wanna know what those actually do? The manual page for thefilecommand has you covered.encoding ?subcommand args...?β same energy: a safe subset of theencodingcommand's subcommands. It disallows setting of the system encoding, but allows all other subcommands β includingsystem, so you can still check the current encoding π no cap.exitβ calling this deletes the calling interpreter and stops its computation. But here's the thing: the Tcl process in which this interpreter exists is not terminated. So it's a little goodbye, not a whole funeral π
π SECURITY π
Real talk time, bestie. Safe Tcl does not attempt to completely prevent annoyance and denial of service attacks. Those forms of attack stop the application or user from temporarily using the computer to do useful work β for example, by consuming all available CPU time or all available screen real estate π And yeah, they're aggravating, but they're deemed to be of lesser importance in general than the integrity and privacy attacks that Safe Tcl is here to prevent. Priorities, you know?
So what's actually available in a safe interpreter? On top of the safe set as defined in the interp manual page, you get mediated aliases for source, load, exit, and safe subsets of file and encoding. The safe interpreter can also auto-load code, and it can request that packages be loaded. Slay β¨
Now, because some of these commands touch the local file system, there's a potential for information leakage about its directory structure π¬ To prevent that, commands that take file names as arguments in a safe interpreter use tokens instead of the real directory names. These tokens get translated to the real directory name while a request to, e.g., source a file is mediated by the parent interpreter.
This virtual path system is maintained in the parent interpreter for each safe interpreter created by ::safe::interpCreate or initialized by ::safe::interpInit, and the path maps tokens accessible in the safe interpreter into real path names on the local file system. So basically it prevents safe interpreters from gaining knowledge about the structure of the file system of the host on which the interpreter is executing. The vibes: you can knock, but you can't see inside πͺ
The only valid file names arguments for thesourceandloadaliases provided to the child are path in the form of[file join token filename](i.e. when using the native file path formats:token/filenameon Unix andtoken\filenameon Windows), where token is representing one of the directories of the accessPath list and filename is one file in that directory (no sub directories access are allowed).
When a token is used in a safe interpreter in a request to source or load a file, the token is checked and translated to a real path name, and the file to be sourced or loaded is located on the file system. The safe interpreter never gains knowledge of the actual path name under which the file is stored on the file system. Iconic behavior honestly π
And to further prevent potential information leakage from sensitive files that are accidentally included in the set of files that can be sourced by a safe interpreter, the source alias restricts access to files meeting these constraints:
- the file name must be fourteen characters or shorter
- it must not contain more than one dot (
".") - it must end up with the extension (
".tcl") or be called ("tclIndex".)
Each element of the initial access path list will be assigned a token that will be set in the child auto_path, and the first element of that list will be set as the tcl_library for that child.
If the access path argument is not given to ::safe::interpCreate or ::safe::interpInit or is the empty list, the default behavior is to let the child access the same packages as the parent has access to. (Or, to be more precise: only packages written in Tcl β which by definition cannot be dangerous since they run in the child interpreter β and C extensions that provides a _SafeInit entry point.) For that purpose, the parent's auto_path will be used to construct the child access path.
In order that the child successfully loads the Tcl library files (which includes the auto-loading mechanism itself), the tcl_library will be added or moved to the first position if necessary, in the child access path, so the child tcl_library will be the same as the parent's (its real path will still be invisible to the child though π»).
And in order that auto-loading works the same for the child and the parent in this by-default case, the first-level sub directories of each directory in the parent auto_path will also be added (if not already included) to the child access path.
You can always specify a more restrictive path for which sub directories will never be searched by explicitly specifying your directory list with the -accessPath flag instead of relying on this default mechanism. Your call, bestie π
When the accessPath is changed after the first creation or initialization (i.e. through interpConfigure -accessPath list), an auto_reset is automatically evaluated in the safe interpreter to synchronize its auto_index with the new token list. Fresh start energy πΈ
π TYPICAL USE π
In many cases, the properties of a Safe Base interpreter can be specified when the interpreter is created, and then left unchanged for the lifetime of the interpreter. Set it and forget it, honestly π
If you wish to use Safe Base interpreters with "Sync Mode" off, evaluate the command:
safe::setSyncMode 0
Use ::safe::interpCreate or ::safe::interpInit to create an interpreter with the properties that you require. The simplest way is not to specify -accessPath or -autoPath, which means the safe interpreter will use the same paths as the parent interpreter. However, if -accessPath is specified, then -autoPath must also be specified, or else it will be set to {}. No skipping steps on this one π«Ά
The value of -autoPath will be that required to access tclIndex and pkgIndex.tcl files according to the same rules as an unsafe interpreter (see pkg_mkIndex(3tcl) and library(3tcl)).
With "Sync Mode" on, the option -autoPath is undefined, and the Safe Base sets the child's ::auto_path to a tokenized form of the access path. In addition to the directories present if "Safe Mode" is off, the ::auto_path includes the numerous subdirectories and module paths that belong to the access path. β¨
π SYNC MODE π
Okay bestie, buckle up, because this is the lore π
Before Tcl version 9.0, the Safe Base kept every safe interpreter's ::auto_path synchronized with a tokenized form of its access path. The limits of Tcl 8.4 and earlier literally made this feature a whole necessity. That definition of ::auto_path did not conform to its specification in library(3tcl) and pkg_mkIndex(3tcl), but ngl it still worked absolutely perfectly for discovering and loading packages β¨
Then Tcl modules showed up in Tcl 8.5 and added a large number of directories to the access path, and it's just so inconvenient to have all those extra directories unnecessarily appended to the ::auto_path. It's giving clutter, honestly.
To preserve compatibility with existing code, this synchronization of the ::auto_path and access path (that's "Sync Mode" on) is still the default. But the Safe Base does offer you the option of limiting the safe interpreter's ::auto_path to the much shorter list of directories it actually needs to do its job (that's "Sync Mode" off). Use the command ::safe::setSyncMode to pick your mode before creating any Safe Base interpreters. Vibe check first, always π«Ά
In either mode, the most convenient way to initialize a safe interpreter is to call ::safe::interpCreate or ::safe::interpInit without the -accessPath or -autoPath options (or with the -accessPath option set to the empty list), which gives the safe interpreter the same access as the parent interpreter to packages, modules, and autoloader files. Bestie behavior.
With "Sync Mode" off, the Safe Base will set the value of -autoPath to the parent's ::auto_path, and will set the child's ::auto_path to a tokenized form of the parent's ::auto_path. Iconic.
With "Sync Mode" off, if a value is specified for -autoPath, even the empty list, in a call to ::safe::interpCreate, ::safe::interpInit, or ::safe::interpConfigure, it gets tokenized and used as the safe interpreter's ::auto_path. Any directories that don't also belong to the access path cannot be tokenized and will be silently ignored π. However! The value of -autoPath will stay as specified, and it'll be used to re-tokenize the child's ::auto_path if ::safe::interpConfigure is called to change the value of -accessPath.
With "Sync Mode" off, if the access path gets reset to the values in the parent interpreter by calling ::safe::interpConfigure with arguments -accessPath {}, then the ::auto_path will also be reset β unless the argument -autoPath is supplied to specify a different value. Conditional queen π
With "Sync Mode" off, if a non-empty value of -accessPath is supplied, the safe interpreter's ::auto_path will be set to {} (by ::safe::interpCreate, ::safe::interpInit) or left unchanged (by ::safe::interpConfigure). And if the same command specifies a new value for -autoPath, it will be applied after the -accessPath argument has been processed. Order matters, no cap.
β¨ Examples of use with "Sync Mode" off β¨
Any of these commands will set the ::auto_path to a tokenized form of its value in the parent interpreter:
safe::interpCreate foo
safe::interpCreate foo -accessPath {}
safe::interpInit bar
safe::interpInit bar -accessPath {}
safe::interpConfigure foo -accessPath {}
β¨ Example of use with "Sync Mode" off β¨
When initializing a safe interpreter with a non-empty access path, the ::auto_path will be set to {} unless its own value is also specified:
safe::interpCreate foo -accessPath {
/usr/local/TclHome/lib/tcl9.0
/usr/local/TclHome/lib/tcl9.0/http1.0
/usr/local/TclHome/lib/tcl9.0/opt0.4
/usr/local/TclHome/lib/tcl9.0/msgs
/usr/local/TclHome/lib/tcl9.0/encoding
/usr/local/TclHome/lib
}
# The child's ::auto_path must be given a suitable value:
safe::interpConfigure foo -autoPath {
/usr/local/TclHome/lib/tcl9.0
/usr/local/TclHome/lib
}
# The two commands can be combined:
safe::interpCreate foo -accessPath {
/usr/local/TclHome/lib/tcl9.0
/usr/local/TclHome/lib/tcl9.0/http1.0
/usr/local/TclHome/lib/tcl9.0/opt0.4
/usr/local/TclHome/lib/tcl9.0/msgs
/usr/local/TclHome/lib/tcl9.0/encoding
/usr/local/TclHome/lib
} -autoPath {
/usr/local/TclHome/lib/tcl9.0
/usr/local/TclHome/lib
}
β¨ Example of use with "Sync Mode" off β¨
The command safe::interpAddToAccessPath does not change the safe interpreter's ::auto_path, so any necessary change has to be made by the script itself. You're the main character now, bestie, handle it π
safe::interpAddToAccessPath foo /usr/local/TclHome/lib/extras/Img1.4.11
lassign [safe::interpConfigure foo -autoPath] DUM childAutoPath
lappend childAutoPath /usr/local/TclHome/lib/extras/Img1.4.11
safe::interpConfigure foo -autoPath $childAutoPath
π SEE ALSO π
interp(3tcl)library(3tcl)load(3tcl)package(3tcl)pkg_mkIndex(3tcl)source(3tcl)tm(3tcl)unknown(3tcl)
π KEYWORDS π
alias, auto-loading, auto_mkindex, load, parent interpreter, safe interpreter, child interpreter, source
and that's the whole page bestie, you ate it UP πβ¨
This one's from Tcl 8.0 β Safe Tcl(3tcl) β and yeah, she's been slaying since the 8.0 era π go forth and make your safe interpreters, ily, bye π¦
π¦ where this page comes from
- debian trixie: tcl9.0-doc 9.0.1+dfsg-2
- ubuntu questing: tcl9.0-doc 9.0.2+dfsg-1
- ubuntu resolute: tcl9.0-doc 9.0.3+dfsg-1
- ubuntu stonking: tcl9.0-doc 9.0.4+dfsg-1
- Copyright (c) 1995-1996 Sun Microsystems, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms