PM> Install-Package Delegate.Sandbox The Delegate.Sandbox library can be installed from NuGet

Delegate.Sandbox is library that provides a Computation Expression named SandboxBuilder , sandbox{ return 42 } , which ensures that values returned from the computation are I/O side-effects safe ( IOSafe ) and if not, they are marked as unsafe ( Unsafe ) and returning an exception.

The library allows to bind >>= several sandbox computations together in order to create side-effect free code and based on the final result, then proceed to perform the desired side-effects.

The following example shows that even though there is a call to printfn , the output is not passed to the console and hereby, no side-effect is generated:

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: open System open System . IO open Delegate . Sandbox let inline ( > > = ) m f = IOEffect . bind f m let addition x y = sandbox { return x + y } let power2 x = sandbox { printfn "Injected side-effect" ; return x * x } let result = addition 21 21 > > = power2 printfn "Sum of x and y, then power2: %A " result

Evaluates to the following output:

Sum of x and y and then power2: IOSafe 1764

Remark: No output is written to the console

In the next example, we add System.Console.ReadLine() in order to block the function until somebody presses enter. Additionally, if side-effects were allowed, the entered input would affect return value of the function:

1: 2: 3: let fooBar = sandbox { return Console . ReadLine () + "FooBar" } printfn "Prints only 'IOSafe FooBar': %A " fooBar

Evaluates to the following output:

Prints only 'IOSafe FooBar': IOSafe FooBar

Remark: No blocking readline or input from console.

The next example show how we try to get access to the current file directory, count the amount of files and add it to the final result. This action will try to perform an File IO which is not allowed in the sandbox . Due to this, the whole function is evaluated to an Unsafe value, which contain the Exception thrown at runtime:

1: 2: 3: 4: let addition' x y = sandbox { return ( Directory . EnumerateFiles ( "." ) |> Seq . length ) + x + y } printfn "Sum of x and y, then power2 (with error msg): %A " ( addition' 21 21 > > = power2 )

Evaluates to the following output:

Sum of x and y (with error msg): Unsafe System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet) at System.Security.CodeAccessPermission.Demand() at System.IO.FileSystemEnumerableIterator 1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler 1 resultHandler, Boolean checkHost) at System.IO.Directory.EnumerateFiles(String path) at Program.addition'@12-1.Invoke(Unit unitVar) at Delegate.Sandbox.GlobalValues.SandboxBuilder.Delaya The action that failed was: Demand The type of the first permission that failed was: System.Security.Permissions.FileIOPermission The first permission that failed was: <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" PathDiscovery="D:\...\."/> The demand was for: <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" PathDiscovery="D:\...\."/> The granted set of the failing assembly was: <PermissionSet class="System.Security.PermissionSet" version="1"> <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, Execution"/> </PermissionSet> The assembly or AppDomain that failed was: Sandbox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null The method that caused the failure was: IOEffect`1 Invoke(Microsoft.FSharp.Core.Unit) The Zone of the assembly that failed was: MyComputer The Url of the assembly that failed was: file:///D:/.../bin/Release/Sandbox.EXE

Remark: The computation and bindings works like the Either Monad where you either have a value of the type IOSafe or you have an Exception of the type Unsafe . The main point here is that the I/O side-effect is NOT performed and the computation catches the attempt by tainting the whole expression and providing the thrown Exception which can be re-thrown or logged in order to revise and fix the code.

A few words on the SandboxBuilder works: The library is built on top of the AppDomain Class which allows to Run Partially Trusted Code in a Sandbox. The SandboxBuilder is only allowed to execute code (SecurityPermissionFlag.Execution) , which is the minimum permission that can be granted (Principle of least privilege). sandbox is implemented as a computation expression that only implements

return ( Return : v:'b -> 'b IOEffect ), which ensures that values returning from the computation are of the desired value type, and delay ( Delay : f:(unit -> 'a IOEffect) -> 'a IOEffect ), which tries to evaluate the function at the newly created domain ( AppDomain ) with the minimum granted permission instead of the executing AppDomain.CurrentDomain . If the function evaluation is successful then an IOSafe 'a value is returned, otherwise an Unsafe Exception is returned. In order to ensure that IOEffect types are only instantiated from inside the computation expression, a few examples: IOSafe "42" or IOSafe (fun _ -> Directory.EnumerateFiles(".") |> Seq.length) , we use type encapsulation and we afterwards expose them with the help of active patterns . For more info on this matter, please see this Gist from Scott Wlaschin. To remove System.Console I/O side-effects, we need to execute some SecurityPermissionFlag.UnmanagedCode before we instantiate the SandboxBuilder . This is handled by RemoveConsoleInOutEffects . When the type is instantiated, the System.Console.SetIn , System.Console.SetOut and System.Console.SetError are set to Stream.Null . Once this task is performed, the SecurityPermissionFlag.UnmanagedCode flag is removed in order for the new AppDomain runs with the minimal permission possible. For more information, please look into the code (about +80 lines) at GitHub

works: We describe a few limitations we found while we were making the library: No code optimization : When a project that refers to the library is built in Release mode, default is set to Optimize code , then it will not work as some of the code is transformed to use Reflection which is not supported in the AppDomain . Unit tests : As stated before, Reflection is not supported and because NUnit uses this approach to execute the test, then it will not work either. This makes it really difficult to test code, mostly because Unsafe types are runtime and not compile time. F# Interactive (fsiAnyCpu.exe) : As the computation expression is built on top of the AppDomain , it will not be possible to use this library in interactive mode (scripts, ...).

we found while we were making the library:

The project is hosted on GitHub where you can report issues, fork the project and submit pull requests.

The library is available under an Open Source MIT license, which allows modification and redistribution for both commercial and non-commercial purposes. For more information see the License file in the GitHub repository.

namespace System

namespace System.IO

type Delegate =

member Clone : unit -> obj

member DynamicInvoke : params args:obj[] -> obj

member Equals : obj:obj -> bool

member GetHashCode : unit -> int

member GetInvocationList : unit -> Delegate[]

member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit

member Method : MethodInfo

member Target : obj

static member Combine : params delegates:Delegate[] -> Delegate + 1 overload

static member CreateDelegate : type:Type * method:MethodInfo -> Delegate + 9 overloads

...



Full name: System.Delegate

namespace Delegate.Sandbox

val m : 'a IOEffect

val f : ('a -> 'b IOEffect)

Multiple items

module IOEffect



from Delegate.Sandbox.GlobalValues



--------------------

type 'a IOEffect =

private | IOSafe of 'a

| Unsafe of exn

override ToString : unit -> string



Full name: Delegate.Sandbox.GlobalValues.IOEffect<_>

val bind : f:('a -> 'b IOEffect) -> _arg1:'a IOEffect -> 'b IOEffect



Full name: Delegate.Sandbox.GlobalValues.IOEffect.bind

val addition : x:int -> y:int -> int IOEffect



Full name: Index.addition

val x : int

val y : int

val sandbox : SandboxBuilder



Full name: Delegate.Sandbox.GlobalValues.sandbox

val power2 : x:int -> int IOEffect



Full name: Index.power2

val printfn : format:Printf.TextWriterFormat<'T> -> 'T



Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn

val result : int IOEffect



Full name: Index.result

val fooBar : string IOEffect



Full name: Index.fooBar

type Console =

static member BackgroundColor : ConsoleColor with get, set

static member Beep : unit -> unit + 1 overload

static member BufferHeight : int with get, set

static member BufferWidth : int with get, set

static member CapsLock : bool

static member Clear : unit -> unit

static member CursorLeft : int with get, set

static member CursorSize : int with get, set

static member CursorTop : int with get, set

static member CursorVisible : bool with get, set

...



Full name: System.Console

Console.ReadLine() : string

val addition' : x:int -> y:int -> int IOEffect



Full name: Index.addition'

type Directory =

static member CreateDirectory : path:string -> DirectoryInfo + 1 overload

static member Delete : path:string -> unit + 1 overload

static member EnumerateDirectories : path:string -> IEnumerable<string> + 2 overloads

static member EnumerateFileSystemEntries : path:string -> IEnumerable<string> + 2 overloads

static member EnumerateFiles : path:string -> IEnumerable<string> + 2 overloads

static member Exists : path:string -> bool

static member GetAccessControl : path:string -> DirectorySecurity + 1 overload

static member GetCreationTime : path:string -> DateTime

static member GetCreationTimeUtc : path:string -> DateTime

static member GetCurrentDirectory : unit -> string

...



Full name: System.IO.Directory

Directory.EnumerateFiles(path: string) : Collections.Generic.IEnumerable<string>

Directory.EnumerateFiles(path: string, searchPattern: string) : Collections.Generic.IEnumerable<string>

Directory.EnumerateFiles(path: string, searchPattern: string, searchOption: SearchOption) : Collections.Generic.IEnumerable<string>

module Seq



from Microsoft.FSharp.Collections

val length : source:seq<'T> -> int



Full name: Microsoft.FSharp.Collections.Seq.length