Delegate.Sandbox
PM> Install-Package Delegate.Sandbox
What is it?
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.
Examples
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: |  | 
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: |  | 
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: |  | 
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.FileSystemEnumerableIterator1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler1 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
EitherMonad where you either have a value of the typeIOSafeor you have anExceptionof the typeUnsafe. 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 thrownExceptionwhich can be re-thrown or logged in order to revise and fix the code.
How it works and limitations
- A few words on the SandboxBuilderworks:- The library is built on top of the AppDomain Class which 
allows to Run Partially Trusted Code in a Sandbox. The
SandboxBuilderis only allowed to execute code(SecurityPermissionFlag.Execution), which is the minimum permission that can be granted (Principle of least privilege).
- sandboxis 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 'avalue is returned, otherwise an- Unsafe- Exceptionis returned.
- In order to ensure that IOEffecttypes are only instantiated from inside the computation expression, a few examples:IOSafe "42"orIOSafe (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.ConsoleI/O side-effects, we need to execute someSecurityPermissionFlag.UnmanagedCodebefore we instantiate theSandboxBuilder. This is handled byRemoveConsoleInOutEffects. When the type is instantiated, theSystem.Console.SetIn,System.Console.SetOutandSystem.Console.SetErrorare set toStream.Null. Once this task is performed, theSecurityPermissionFlag.UnmanagedCodeflag is removed in order for the newAppDomainruns with the minimal permission possible.
- For more information, please look into the code (about +80 lines) at GitHub 
 
- The library is built on top of the AppDomain Class which 
allows to Run Partially Trusted Code in a Sandbox. The
- 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
Releasemode, default is set toOptimize code, then it will not work as some of the code is transformed to useReflectionwhich is not supported in theAppDomain.
- Unit tests: As stated before, Reflectionis 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 becauseUnsafetypes 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, ...).
 
- No code optimization: When a project that refers to the library is built in
Contributing and copyleft
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.
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
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<_>
Full name: Delegate.Sandbox.GlobalValues.IOEffect.bind
Full name: Index.addition
Full name: Delegate.Sandbox.GlobalValues.sandbox
Full name: Index.power2
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Index.result
Full name: Index.fooBar
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
Full name: Index.addition'
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, searchPattern: string) : Collections.Generic.IEnumerable<string>
Directory.EnumerateFiles(path: string, searchPattern: string, searchOption: SearchOption) : Collections.Generic.IEnumerable<string>
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.length
 
          