[{"data":1,"prerenderedAt":43},["ShallowReactive",2],{"doc:en:plugins\u002Fcapabilities":3},{"title":4,"lede":5,"html":6,"headings":7},"The host’s capabilities","A plugin in WASM reaches nothing by itself: no network, no disk, no program. This page says what it can ask Escouade for, what each capability allows, what Escouade refuses and what the install window says about it.","\u003Ch2 id=\"the-principle\">The principle\u003C\u002Fh2>\n\u003Cp>A capability always goes the same way, for \u003Ccode class=\"key\">run.js\u003C\u002Fcode> and \u003Ccode class=\"key\">run.wasm\u003C\u002Fcode> alike:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Declared\u003C\u002Fstrong> in the manifest, in \u003Ccode class=\"key\">permissions\u003C\u002Fcode> or \u003Ccode class=\"key\">limits\u003C\u002Fcode>. What is not declared answers the error \u003Ccode class=\"key\">-32003\u003C\u002Fcode>, and the journal keeps it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Enforced by Escouade\u003C\u002Fstrong>: the module has no socket, no file descriptor, no process. Each call goes through the host, which judges it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Said\u003C\u002Fstrong> in the install window, line by line, before anything runs. The user can take each line away.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Kept in the journal\u003C\u002Fstrong>: the method and the host or the root, never the path, the query or the headers. A command goes there with its arguments, secrets hidden.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>A plugin that asks for everything, to show the form:\u003C\u002Fp>\n\u003Cp>\u003Ccode>sdk\u002Fdoc-examples\u002Frust\u002Fescouade-plugin.json\u003C\u002Fcode> (excerpt)\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">  &quot;permissions&quot;: {\n    &quot;events&quot;: [&quot;ticket.column&quot;],\n    &quot;network&quot;: [&quot;api.example.dev&quot;, &quot;{account}&quot;],\n    &quot;secrets&quot;: [&quot;token&quot;],\n    &quot;files&quot;: [\n      { &quot;root&quot;: &quot;plugin&quot;, &quot;access&quot;: &quot;read&quot; },\n      { &quot;root&quot;: &quot;temp&quot;, &quot;access&quot;: &quot;write&quot; },\n      { &quot;root&quot;: &quot;agent&quot;, &quot;access&quot;: &quot;write&quot; },\n      { &quot;root&quot;: &quot;agentHome&quot;, &quot;access&quot;: &quot;read&quot; }\n    ],\n    &quot;commands&quot;: [&quot;git status --short&quot;, &quot;git log *&quot;]\n  },\n  &quot;limits&quot;: { &quot;fetchSeconds&quot;: 60, &quot;fetchBytes&quot;: 8388608, &quot;fileBytes&quot;: 1048576 },\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"table\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Capability\u003C\u002Fth>\n\u003Cth>In the manifest\u003C\u002Fth>\n\u003Cth>The install window says\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>A host\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">permissions.network\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>“Connects to”, then the host\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>The instances of its accounts\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">{account}\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>“Connects to the instances you give it”\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>A harness’s local server\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">local:shared\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>“Talks to the harness’s local server, started by Escouade”\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Reading or writing a folder\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">permissions.files\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>“Reads your agents’ files”, “Writes in your agents’ folders”…\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Running a program\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">permissions.commands\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>“Runs this command on your computer”, then the command\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Higher limits\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">limits\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Nothing more\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Timers\u003C\u002Ftd>\n\u003Ctd>nothing, a basic right\u003C\u002Ftd>\n\u003Ctd>Nothing\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Ch2 id=\"the-network\">The network\u003C\u002Fh2>\n\u003Cp>Everything that goes out goes through \u003Ccode class=\"key\">net\u002Ffetch\u003C\u002Fcode>, \u003Ccode class=\"key\">net\u002Fstream\u003C\u002Fcode> or \u003Ccode class=\"key\">ws\u002Fopen\u003C\u002Fcode>: see \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcalls\u002F#the-network\">the calls\u003C\u002Fa>. A plugin in JS keeps the standard \u003Ccode class=\"key\">fetch\u003C\u002Fcode>, a plugin in Rust the kit’s client.\u003C\u002Fp>\n\u003Ch3 id=\"a-named-host\">A named host\u003C\u002Fh3>\n\u003Cp>\u003Ccode>&quot;network&quot;: [&quot;api.example.dev&quot;]\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What it allows.\u003C\u002Fstrong> Reaching this host and its subdomains, over https. \u003Ccode class=\"key\">*.example.dev\u003C\u002Fcode> takes the subdomains without the host itself.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What Escouade refuses.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>http, and any other host;\u003C\u002Fli>\n\u003Cli>an IP address written in the URL, which \u003Ccode class=\"key\">permissions.network\u003C\u002Fcode> does not name;\u003C\u002Fli>\n\u003Cli>an address of the machine or of the local network, even for a declared host: loopback, private, link-local, unspecified, multicast or broadcast, in IPv4 as in IPv6. Escouade judges the addresses the connection uses, after the name is resolved;\u003C\u002Fli>\n\u003Cli>redirects: the plugin gets the 3xx response and decides.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>A refused call answers \u003Ccode class=\"key\">-32003\u003C\u002Fcode>. A call that fails on the way answers \u003Ccode class=\"key\">-32004\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Settings › Network.\u003C\u002Fstrong> The proxy, its exclusions and unverified certificates are followed, and a change reaches the next call, without restarting the plugin. Through a proxy, only the name is judged, and the journal says so.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The window\u003C\u002Fstrong> says “Connects to”, then each host, with “enforced by Escouade”.\u003C\u002Fp>\n\u003Ch3 id=\"the-instances-of-its-accounts-account\">The instances of its accounts: \u003Ccode class=\"key\">{account}\u003C\u002Fcode>\u003C\u002Fh3>\n\u003Cp>\u003Ccode>&quot;network&quot;: [&quot;{account}&quot;]\u003C\u002Fcode>, for a self-hosted service: GitHub Enterprise Server, GitLab, Gitea, Jira.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What it allows.\u003C\u002Fstrong> Reaching the host the user typed for an account this plugin serves, on the port typed, and that host alone. For it, http and the local network are allowed: the user named it.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What Escouade refuses.\u003C\u002Fstrong> Its subdomains and any other host. The list is read again at each call: an account removed closes its host.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The window\u003C\u002Fstrong> says “Connects to the instances you give it”. \u003Ccode class=\"key\">{account}\u003C\u002Fcode> is not a host: the store takes it as it is.\u003C\u002Fp>\n\u003Cp>On the bench, \u003Ccode>escouade-plugin test &lt;folder&gt; --account &lt;address&gt;\u003C\u002Fcode> stands for an account.\u003C\u002Fp>\n\u003Ch3 id=\"a-harness-s-local-server-local-shared\">A harness’s local server: \u003Ccode class=\"key\">local:shared\u003C\u002Fcode>\u003C\u002Fh3>\n\u003Cp>\u003Ccode>&quot;network&quot;: [&quot;local:shared&quot;]\u003C\u002Fcode>, for a harness whose server Escouade starts, like \u003Ccode>opencode serve\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What it allows.\u003C\u002Fstrong> http to \u003Ccode class=\"key\">127.0.0.1:&lt;port&gt;\u003C\u002Fcode>, only on a port the core read in the output of a shared process it started for this plugin, as long as that process lives. This traffic never goes through the proxy.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What Escouade refuses.\u003C\u002Fstrong> Any other local port. The plugin never names a port the core did not read, and \u003Ccode class=\"key\">local:shared\u003C\u002Fcode> serves neither a WebSocket nor another plugin.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The window\u003C\u002Fstrong> says “Talks to the harness’s local server, started by Escouade”.\u003C\u002Fp>\n\u003Ch3 id=\"the-limits-limits\">The limits: \u003Ccode class=\"key\">limits\u003C\u002Fcode>\u003C\u002Fh3>\n\u003Cp>\u003Ccode>sdk\u002Fdoc-examples\u002Frust\u002Fescouade-plugin.json\u003C\u002Fcode> (excerpt)\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">  &quot;limits&quot;: { &quot;fetchSeconds&quot;: 60, &quot;fetchBytes&quot;: 8388608, &quot;fileBytes&quot;: 1048576 },\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"table\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Key\u003C\u002Fth>\n\u003Cth>What it says\u003C\u002Fth>\n\u003Cth>Without it\u003C\u002Fth>\n\u003Cth>The host’s ceiling\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">fetchSeconds\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>How long a \u003Ccode class=\"key\">net\u002Ffetch\u003C\u002Fcode> has\u003C\u002Ftd>\n\u003Ctd>10 s\u003C\u002Ftd>\n\u003Ctd>120 s\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">fetchBytes\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>How large a response is\u003C\u002Ftd>\n\u003Ctd>1 MiB\u003C\u002Ftd>\n\u003Ctd>32 MiB\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">fileBytes\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>What an \u003Ccode class=\"key\">fs\u002Fread\u003C\u002Fcode> gives\u003C\u002Ftd>\n\u003Ctd>256 KiB\u003C\u002Ftd>\n\u003Ctd>4 MiB\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>A value past the ceiling is brought down to it by the host (the manifest refuses it for \u003Ccode class=\"key\">fileBytes\u003C\u002Fcode>). The window says nothing more: a limit opens nothing.\u003C\u002Fp>\n\u003Ch3 id=\"streams-and-the-websocket\">Streams and the WebSocket\u003C\u002Fh3>\n\u003Cp>They are not declared: they follow \u003Ccode class=\"key\">network\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Ccode class=\"key\">net\u002Fstream\u003C\u002Fcode>\u003C\u002Fstrong> applies the rules of \u003Ccode class=\"key\">net\u002Ffetch\u003C\u002Fcode> at its opening, then a credit: the host stops reading the socket past 256 KiB not consumed, so a slow plugin slows the server and not the app’s memory. 8 streams at once. 120 s without a byte closes a stream, unless it is declared \u003Ccode>&quot;sse&quot;: true\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode class=\"key\">ws\u002Fopen\u003C\u002Fcode>\u003C\u002Fstrong> is \u003Ccode class=\"key\">wss\u003C\u002Fcode> only, to the same hosts, never \u003Ccode class=\"key\">local:shared\u003C\u002Fcode>. 4 sockets, 1 MiB a message. \u003Ccode class=\"key\">ws\u002Fsend\u003C\u002Fcode> is refused when what waits would pass 256 KiB. Its headers do not reach the journal.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-files-files\">The files: \u003Ccode class=\"key\">files\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>sdk\u002Fdoc-examples\u002Frust\u002Fescouade-plugin.json\u003C\u002Fcode> (excerpt)\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">    &quot;files&quot;: [\n      { &quot;root&quot;: &quot;plugin&quot;, &quot;access&quot;: &quot;read&quot; },\n      { &quot;root&quot;: &quot;temp&quot;, &quot;access&quot;: &quot;write&quot; },\n      { &quot;root&quot;: &quot;agent&quot;, &quot;access&quot;: &quot;write&quot; },\n      { &quot;root&quot;: &quot;agentHome&quot;, &quot;access&quot;: &quot;read&quot; }\n    ],\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>What it allows.\u003C\u002Fstrong> \u003Ccode class=\"key\">fs\u002Fread\u003C\u002Fcode>, \u003Ccode class=\"key\">fs\u002Fwrite\u003C\u002Fcode>, \u003Ccode class=\"key\">fs\u002Fstat\u003C\u002Fcode>, \u003Ccode class=\"key\">fs\u002Flist\u003C\u002Fcode> and \u003Ccode class=\"key\">fs\u002Fcanon\u003C\u002Fcode>, under a root of this closed list, which Escouade resolves:\u003C\u002Fp>\n\u003Cdiv class=\"table\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Root\u003C\u002Fth>\n\u003Cth>What it is\u003C\u002Fth>\n\u003Cth>The window says\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">plugin\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Its folder, read only: what the store signed does not change.\u003C\u002Ftd>\n\u003Ctd>“Reads its own folder”\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">temp\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A folder of its own, shared by its agents.\u003C\u002Ftd>\n\u003Ctd>“Keeps files in a folder of its own”\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">agent\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The agent’s working folder.\u003C\u002Ftd>\n\u003Ctd>“Reads your agents’ files”, or “Writes in your agents’ folders” in orange\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">agentRepo\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Its git repository, for a worktree.\u003C\u002Ftd>\n\u003Ctd>The same\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">agentHome\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Its account’s folder, where a harness keeps its credentials.\u003C\u002Ftd>\n\u003Ctd>“Reads your agents’ account folder”\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>\u003Ccode class=\"key\">access\u003C\u002Fcode> is \u003Ccode class=\"key\">read\u003C\u002Fcode> or \u003Ccode class=\"key\">write\u003C\u002Fcode>. A plugin names a root and a relative path, or a path of the host under a root of this agent: that is what a harness reads. Only harness plugins get an agent, through \u003Ccode class=\"key\">agent\u002Fattach\u003C\u002Fcode>, with the roots they declare and no others; another plugin has \u003Ccode class=\"key\">plugin\u003C\u002Fcode> and \u003Ccode class=\"key\">temp\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What Escouade refuses.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>a free path, a \u003Ccode class=\"key\">~\u003C\u002Fcode>, a \u003Ccode class=\"key\">..\u003C\u002Fcode>, an NTFS stream (\u003Ccode class=\"key\">a.txt:b\u003C\u002Fcode>), a link that leads nowhere;\u003C\u002Fli>\n\u003Cli>what leaves a root once links are followed. The refusal comes before saying whether the file exists: it tells nothing;\u003C\u002Fli>\n\u003Cli>\u003Ccode class=\"key\">plugin\u003C\u002Fcode> for writing, when the manifest is read;\u003C\u002Fli>\n\u003Cli>the folder of a private project the agent is cut off from, whatever the plugin’s code;\u003C\u002Fli>\n\u003Cli>a read of more than 256 KiB, or of \u003Ccode class=\"key\">limits.fileBytes\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each write and each refusal go to the journal.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The review\u003C\u002Fstrong> asks what \u003Ccode class=\"key\">agentHome\u003C\u002Fcode> is for: this folder holds a harness’s credentials.\u003C\u002Fp>\n\u003Ch2 id=\"the-programs-commands\">The programs: \u003Ccode class=\"key\">commands\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>sdk\u002Fdoc-examples\u002Frust\u002Fescouade-plugin.json\u003C\u002Fcode> (excerpt)\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">    &quot;commands&quot;: [&quot;git status --short&quot;, &quot;git log *&quot;]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>What it allows.\u003C\u002Fstrong> \u003Ccode class=\"key\">proc\u002Fspawn\u003C\u002Fcode> runs one of these lines. The first word is a program’s name, which Escouade looks for on the PATH.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>What Escouade refuses.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>a path in place of the name;\u003C\u002Fli>\n\u003Cli>what is not an entry, word for word; more words are allowed only when the entry ends in \u003Ccode class=\"key\">*\u003C\u002Fcode>;\u003C\u002Fli>\n\u003Cli>a shell: under Windows, neither \u003Ccode class=\"key\">.cmd\u003C\u002Fcode> nor \u003Ccode class=\"key\">.bat\u003C\u002Fcode>, so no tool installed by npm;\u003C\u002Fli>\n\u003Cli>a \u003Ccode class=\"key\">dir\u003C\u002Fcode> other than a root (\u003Ccode class=\"key\">agent\u003C\u002Fcode>, \u003Ccode class=\"key\">project\u003C\u002Fcode>), and a private project where the plugin is not on. Without \u003Ccode class=\"key\">dir\u003C\u002Fcode>, the program runs in \u003Ccode class=\"key\">temp\u003C\u002Fcode>;\u003C\u002Fli>\n\u003Cli>an \u003Ccode class=\"key\">ESCOUADE_*\u003C\u002Fcode>, \u003Ccode class=\"key\">PATH\u003C\u002Fcode>, \u003Ccode class=\"key\">LD_*\u003C\u002Fcode>, \u003Ccode class=\"key\">DYLD_*\u003C\u002Fcode> or \u003Ccode class=\"key\">NODE_OPTIONS\u003C\u002Fcode> variable in \u003Ccode class=\"key\">env\u003C\u002Fcode>: it would run something else.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each program dies with the plugin’s instance or at \u003Ccode class=\"key\">proc\u002Fkill\u003C\u002Fcode>, with what it started. Four at once. Its output is no longer read past 256 KiB that have not reached the module.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The window\u003C\u002Fstrong> says “Runs this command on your computer”, then the command, in red, one line a command.\u003C\u002Fp>\n\u003Cdiv class=\"callout warning\">\u003Cp class=\"callout-title\">Warning\u003C\u002Fp>\u003Cp>The module is isolated, the program it runs is not: it has the user’s rights. An entry in \u003Ccode class=\"key\">*\u003C\u002Fcode> on a general program (\u003Ccode>git *\u003C\u002Fcode>, \u003Ccode>docker *\u003C\u002Fcode>) opens everything again, through \u003Ccode>git -c core.sshCommand=…\u003C\u002Fcode> for example. This is the strongest capability: ask for exact lines, and expect to justify a \u003Ccode class=\"key\">*\u003C\u002Fcode> at the review.\u003C\u002Fp>\n\u003C\u002Fdiv>\n\u003Cp>\u003Ccode class=\"key\">permissions.commands\u003C\u002Fcode> is not \u003Ccode class=\"key\">contributes.commands\u003C\u002Fcode>, which runs a shell line on a fact, after the user’s agreement (\u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fevents\u002F#a-command-on-a-fact\">the events\u003C\u002Fa>).\u003C\u002Fp>\n\u003Ch2 id=\"the-timers\">The timers\u003C\u002Fh2>\n\u003Cp>\u003Ccode class=\"key\">timer\u002Fset\u003C\u002Fcode>, \u003Ccode class=\"key\">timer\u002Fcancel\u003C\u002Fcode> and \u003Ccode class=\"key\">timer\u002Ffired\u003C\u002Fcode> are not declared: they are a basic right, like \u003Ccode class=\"key\">log\u003C\u002Fcode>. The floor is 10 ms, what is below is brought up to it, and 64 timers at once. They stop with the instance. \u003Ccode class=\"key\">setTimeout\u003C\u002Fcode> in JS and the kit’s \u003Ccode class=\"key\">sleep\u003C\u002Fcode> in Rust go through them.\u003C\u002Fp>\n\u003Ch2 id=\"next\">Next\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcalls\u002F\">The calls\u003C\u002Fa>: each method, with an example in TS and in Rust.\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fpermissions\u002F\">The permissions\u003C\u002Fa>: everything a plugin can ask for, as the user sees it.\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fsecurity\u002F\">Plugin security\u003C\u002Fa>: what the review does with it.\u003C\u002Fli>\n\u003C\u002Ful>\n",[8,12,15,19,22,25,28,31,34,37,40],{"id":9,"text":10,"depth":11},"the-principle","The principle",2,{"id":13,"text":14,"depth":11},"the-network","The network",{"id":16,"text":17,"depth":18},"a-named-host","A named host",3,{"id":20,"text":21,"depth":18},"the-instances-of-its-accounts-account","The instances of its accounts: {account}",{"id":23,"text":24,"depth":18},"a-harness-s-local-server-local-shared","A harness’s local server: local:shared",{"id":26,"text":27,"depth":18},"the-limits-limits","The limits: limits",{"id":29,"text":30,"depth":18},"streams-and-the-websocket","Streams and the WebSocket",{"id":32,"text":33,"depth":11},"the-files-files","The files: files",{"id":35,"text":36,"depth":11},"the-programs-commands","The programs: commands",{"id":38,"text":39,"depth":11},"the-timers","The timers",{"id":41,"text":42,"depth":11},"next","Next",1793889382159]