A Dependently Typed Library for Static Information-Flow Control in Idris

Safely integrating third-party code in applications while protecting the confidentiality of information is a long-standing problem. Pure functional programming languages, like Haskell, make it possible to enforce lightweight information-flow control through libraries like MAC by Russo. This work presents DepSec, a MAC inspired, dependently typed library for static information-flow control in Idris. We showcase how adding dependent types increases the expressiveness of state-of-the-art static information-flow control libraries and how DepSec matches a special-purpose dependent information-flow type system on a key example. Finally, we show novel and powerful means of specifying statically enforced declassification policies using dependent types.


Introduction
Modern software applications are increasingly built using libraries and code from multiple third parties. At the same time, protecting confidentiality of information manipulated by such applications is a growing, yet long-standing problem. Third-party libraries could in general have been written by anyone and they are usually run with the same privileges as the main application. While powerful, such privileges open up for abuse.
Traditionally, access control [7] and encryption have been the main means for preventing data dissemination and leakage, however, such mechanisms fall short when third-party code needs access to sensitive information to provide its functionality. The key observation is that these mechanisms only place restrictions on the access to information but not its propagation. Once information is accessed, the accessor is free to improperly transmit or leak the information in some form, either by intention or error.
Language-based Information-Flow Control [35] is a promising technique for enforcing information security. Traditional enforcement techniques analyze how information at different security levels flows within a program ensuring that information flows only to appropriate places, suppressing illegal flows. To achieve this, most informationflow control tools require the design of new languages, compilers, or interpreters (e.g. [12,16,21,22,25,28,38]). Despite a large, growing body of work on language-based information-flow security, there has been little adoption of the proposed techniques. For information-flow policies to be enforced in such systems, the whole system has to be written in new languages -an inherently expensive and time-consuming process for large software systems. Moreover, in practice, it might very well be that only small parts of an application are governed by information-flow policies.
Pure functional programming languages, like Haskell, have something to offer with respect to information security as they strictly separate side-effect free and side-effectful code. This makes it possible to enforce lightweight information-flow control through libraries [11,19,33,34,41] by constructing an embedded domain-specific security sub-language. Such libraries enforce a secure-by-construction programming model as any program written against the library interface is not capable of leaking secrets. This construction forces the programmer to write security-critical code in the sub-language but otherwise allows them to freely interact and integrate with non-security critical code written in the full language. In particular, static enforcement libraries like MAC [33] are appealing as no run-time checks are needed and code that exhibits illegal flows is rejected by the type checker at compile-time. Naturally, the expressiveness of Haskell's type system sets the limitation on which programs can be deemed secure and which information flow policies can be guaranteed.
Dependent type theories [23,30] are implemented in many programming languages such as Coq [13], Agda [31], Idris [8], and F * [43]. Programming languages that implement such theories allow types to dependent on values. This enables programmers to give programs a very precise type and increased confidence in its correctness.
In this paper, we show that dependent types provide a direct and natural way of expressing precise data-dependent security policies. Dependent types can be used to represent rich security policies in environments like databases and data-centric web applications where, for example, new classes of users and new kinds of data are encountered at run-time and the security level depends on the manipulated data itself [22]. Such dependencies are not expressible in less expressive systems like MAC. Among other things, with dependent types, we can construct functions where the security level of the output depends on an argument: getPassword : (u : Username) -> Labeled u String Given a user name u, getPassword retrieves the corresponding password and classifies it at the security level of u. As such, we can express much more precise security policies that can depend on the manipulated data.
Idris is a general-purpose functional programming language with full-spectrum dependent types, that is, there is no restrictions on which values may appear in types. The language is strongly influenced by Haskell and has, among others, inherited its strict encapsulation of side-effects. Idris essentially asks the question: "What if Haskell had full dependent types?" [9]. This work, essentially, asks: "What if MAC had full dependent types?" We address this question using Idris because of its positioning as a general-purpose language rather than a proof assistant. All ideas should be portable to equally expressive systems with full dependent types and strict monadic encapsulation of side-effects.
In summary, the contributions of this paper are as follows.
-We present DepSec, a MAC inspired statically enforced dependently typed informationflow control library for Idris.
-We show how adding dependent types strictly increases the expressiveness of stateof-the-art static information-flow control libraries and how DepSec matches the expressiveness of a special-purpose dependent information-flow type system on a key example. -We show how DepSec enables and aids the construction of policy-parameterized functions that abstract over the security policy. -We show novel and powerful means to specify statically-ensured declassification using dependent types for a wide variety of policies. -We show progress-insensitive noninterference [1] for the core library in a sequential setting.
Outline The rest of the paper proceeds through a presentation of the DepSec library (Section 2); a conference manager case study (Section 3) and the introduction of policyparameterized functions (Section 4) both showcasing the expressiveness of DepSec; means to specify statically-ensured declassification policies (Section 5); soundness of the core library (Section 6); and related work (Section 7). All code snippets presented in the following are extracts from the source code. All source code is implemented in Idris 1.3.1. and available at https://github.com/simongregersen/DepSec.
The source code of the core library is also available in Appendix C.

Assumptions and threat model
In the rest of this paper, we require that code is divided up into trusted code, written by someone we trust, and untrusted code, written by a potential attacker. The trusted computing base (TCB) has no restrictions, but untrusted code does not have access to modules providing input/output behavior, the data constructors of the domain specific language and a few specific functions related to declassification. In Idris, this means that we specifically do not allow access to IO functions and unsafePerformIO. In DepSec, constructors and functions marked with a TCB comment are inaccessible to untrusted code. Throughout the paper we will emphasize when this is the case.
We require that all definitions made by untrusted code are total, that is, defined for all possible inputs and are guaranteed to terminate. This is necessary if we want to trust proofs given by untrusted code. Otherwise, it could construct an element of the empty type from which it could prove anything: empty : Void empty = empty In Idris, this can be checked using the --total compiler flag. Furthermore, we do not consider concurrency nor any internal or termination covert channels.

The DepSec library
In information-flow control, labels are used to model the sensitivity of data. Such labels usually form a security lattice [14] where the induced partial ordering ⊑ specifies allowed flows of information and hence the security policy. For example, ℓ 1 ⊑ ℓ 2 specifies that data with label ℓ 1 is allowed to flow to entities with label ℓ 2 . In DepSec, labels are represented by values that form a verified join semilattice implemented as Idris interfaces 1 . That is, we require proofs of the lattice properties when defining an instance of JoinSemilattice.
interface JoinSemilattice a where join : a -> a -> a associative : (x, y, z : a) -> x join (y join z) = (x join y) join z commutative : (x, y : a) -> x join y = y join x idempotent : (x : a) -> x join x = x Dependent function types (often referred to as Π types) in Idris can express such requirements. If A is a type and B is a type indexed by a value of type A then (x : A) -> B is the type of functions that map arguments x of type A to values of type B x. A lattice induces a partial ordering, which gives a direct way to express flow constraints. We introduce a verified partial ordering together with an implementation of this for JoinSemilattice. That is, to define an instance of the Poset interface we require a concrete instance of an associated data type leq as well as proofs of necessary algebraic properties of leq.
interface Poset a where leq : a -> a -> Type reflexive : (x : a) -> x leq x antisymmetric : (x, y : a) -> x leq y -> y leq x -> x = y transitive : (x, y, z : a) -> x leq y -> y leq z -> x leq z implementation JoinSemilattice a => Poset a where leq x y = (x join y = y) ... This definition allows for generic functions to impose as few restrictions as possible on the user while being able to exploit the algebraic structure in proofs, as will become evident in Section 3 and 4. For the sake of the following case studies, we also have a definition of a BoundedJoinSemilattice requiring a least element Bottom of an instance of JoinSemilattice and a proof of the element being the unit.
The Core API Figure 1 presents the type signature of DepSec's core API. Notice that names beginning with a lower case letter that appear as a parameter or index in a type declaration will be automatically bound as an implicit argument in Idris, and the auto annotation on implicit arguments means that Idris will attempt to fill in the implicit argument by searching the calling context for an appropriate value. Abstract data type Labeled ℓ a denotes a value of type a with sensitivity level ℓ. We say that Labeled ℓ a is indexed by ℓ and parameterized by a. Abstract data type DIO ℓ a denotes a secure computation that handles values with sensitivity level ℓ and results in a value of type a. It is internally represented as a wrapper around the regular IO monad that, similar to the one in Haskell, can be thought of as a state monad where the state is the entire world. Notice that both data constructors MkLabeled and MkDIO are not available to untrusted code as this would allow pattern matching and uncontrolled unwrapping of protected entities. As a consequence, we introduce functions label and unlabel for labeling and unlabeling values. Like Rajani and Garg [32], but unlike MAC, the type signature of label imposes no lattice constraints on the computation context. This does not leak information as, if l ⊑ l ′ and a computation c has type DIO l ′ (Labeled l V) for any type V, then there is no way for the labeled return value of c to escape the computation context with label l ′ . As in MAC, the API contains a function plug that safely integrates sensitive computations into less sensitive ones. This avoids the need for nested computations and label creep, that is, the raising of the current label to a point where the computation can no longer perform useful tasks [33,46]. Finally, we also have functions run and lift that are only available to trusted code for unwrapping of the DIO ℓ monad and lifting of the IO monad into the DIO ℓ monad.
Labeled resources Data type Labeled ℓ a is used to denote a labeled Idris value with type a. This is an example of a labeled resource [33]. By itself, the core library does not allow untrusted code to perform any side effects but we can safely incorporate, for example, file access and mutable references as other labeled resources. Figure 2 presents type signatures for files indexed by security levels used for secure file handling while mutable references are available in Appendix C. Abstract data type SecFile ℓ denotes a secure file with sensitivity level ℓ. As for Labeled ℓ a, the data constructor MkSecFile is not available to untrusted code.
The function readFile takes as input a secure file SecFile l' and returns a computation with sensitivity level l that returns a labeled value with sensitivity level l'. Notice that the l ⊑ l' flow constraint is required to enforce the no read-up policy [7]. That is, the result of the computation returned by readFile only involves data with sensitivity at most l. The function writeFile takes as input a secure file SecFile l'' and a labeled value of sensitivity level l', and it returns a computation with sensitivity level l that returns a labeled value with sensitivity level l''. Notice that both the l ⊑ l' and l' ⊑ l'' flow constraints are required, essentially enforcing the no write-down policy [7], that is, the file never receives data more sensitive than its sensitivity level.
Finally, notice that the standard library functions for reading and writing files in Idris used to implement the functions in Figure 2 do not raise exceptions. Rather, both functions return an instance of the sum type Either. We stay consistent with Idris' choice for this instead of adding exception handling as done in MAC.

Case study: Conference manager system
This case study showcases the expressiveness of DepSec by reimplementing a conference manager system with a fine-grained data-dependent security policy introduced by Lourenço and Caires [22]. Lourenço and Caires base their development on a minimal λ-calculus with references and collections and they show how secure operations on relevant scenarios can be modelled and analysed using dependent information flow types. Our reimplementation demonstrates how DepSec matches the expressiveness of such a special-purpose built dependent type system on a key example.
In this scenario, a user is either a regular user, an author user, or a program committee (PC) member. The conference manager contains information about the users, their submissions, and submission reviews. This data is stored in lists of references to records, and the goal is to statically ensure, by typing, the confidentiality of the data stored in the conference manager system. As such, the security policy is: -A registered user's information is not observable by other users.
-The content of a paper can be seen by its authors as well as its reviewers.
-Comments to the PC of a submission's review can only be seen by other members that are also reviewers of that submission. -The only authors that are allowed to see the grade and the review of the submission are those that authored that submission.
To achieve this security policy, Lourenço and Caires make use of indexed security labels [21]. The security level U is partitioned into a number of security compartments such that U(uid) represents the compartment of the registered user with id uid. Similarly, the security level A is indexed such that A(uid, sid) stands for the compartment of data belonging to author uid and their submission sid, and PC is indexed such that PC(uid, sid) stands for data belonging to the PC member with user id uid assigned to review the submission with id sid. Furthermore, levels ⊤ and ⊥ are introduced such that, for example, U(⊥) ⊑ U(uid) ⊑ U(⊤). Now, the security lattice is defined using two equations: Lourenço and Caires are able to type a list of submissions with a dependent sum type that assigns the content of the paper the security level A(uid, sid), where uid and sid are fields of the record. For example, if a concrete submission with identifier 2 was made by the user with identifier 1, the content of the paper gets classified at security level A (1,2). In consequence, A(1, 2) ⊑ PC(n, 2) for any uid n and the content of the paper is only observable by its assigned reviewers. Similar types are given for the list of user information and the list of submission reviews, enforcing the security policy described in the above.
To express this policy in DepSec, we introduce abstract data types Id and Compartment (cf. Figure 3) followed by an implementation of the BoundedJoinSemilattice interface that satisfies equations (1) and (2).  Using the above, the required dependent sum types can easily be encoded with DepSec in Idris as presented in Figure 4. With these typings in place, implementing the examples from Lourenço and Caires [22] is straightforward. For example, the function viewAuthorPapers takes as input a list of submissions and a user identifier uid1 from which it returns a computation that returns a list of submissions authored by the user with identifier uid1. Notice that uid denotes the automatically generated record projection function that retrieves the field uid of the record, and that (x: A ** B) is notation for a dependent pair (often referred to as a Σ type) where A and B are types and B may depend on x.
viewAuthorPapers : Submissions -> (uid1 : Id) -> DIO Bottom (List (sub : Submission ** uid1 = (uid sub))) The addCommentSubmission operation is used by the PC members to add comments to the submissions. The function takes as input a list of reviews, a user identifier of a PC member, a submission identifier, and a comment with label A uid1 sid1. It returns a computation that updates the PC only field in the review of the paper with identifier sid1.
addCommentSubmission : Reviews -> (uid1 : Id) -> (sid1 : Id) -> Labeled (A uid1 sid1) String -> DIO Bottom () Notice that to implement this specific type signature, up-classification is necessary to assign the comment with type Labeled (A uid1 sid1) String to a field with type Labeled (PC uid sid1) String. This can be achieved soundly with the relabel primitive introduced by Vassena et al. [46] as A uid1 sid1 ⊑ PC uid sid1. We include this primitive in Appendix C.
Several other examples are available in the accompanying source code. The entire case study amounts to about 300 lines of code where half of the lines implement and verify the lattice.

Policy-parameterized functions
A consequence of using a dependently typed language, and the design of DepSec, is that functions can be defined such that they abstract over the security policy while retaining precise security levels. This makes it possible to reuse code across different applications and write other libraries on top of DepSec. We can exploit the existence of a lattice join, the induced poset, and their verified algebraic properties to write such functions.  Figure 5 presents the function readTwoFiles that is parameterized by a bounded join semilattice. It takes two secure files with labels l and l' as input and returns a computation that concatenates the contents of the two files labeled with the join of l and l'. To implement this, we make use of the unlabel and readFile primitives from Figure 1 and 2, respectively. This computation unlabels the contents of the files and returns the concatenation of the contents if no file error occurred. Notice that pure is the Idris function for monadic return, corresponding to the return function in Haskell. Finally, this computation is plugged into the surrounding computation. Notice how the usage of readFile and unlabel introduces several proof obligations, namely ⊥ ⊑ l, l', l ⊔ l' and l, l' ⊑ l ⊔ l'. When working on a concrete lattice these obligations are usually fulfilled by Idris' automatic proof search but, currently, such proofs need to be given manually in the general case. All obligations follow immediately from the algebraic properties of the bounded semilattice and are given in three auxiliary lemmas leq bot x, join x xy, and join y xy available in Appendix C (amounting to 10 lines of code).
Writing functions operating on a fixed number of resources is limiting. However, the function in Figure 5 can easily be generalized to a function working on an arbitrary data structure containing files with different labels from an arbitrary lattice. Similar to the approach taken by Buiras et al. [11] that hide the label of a labeled value using a data type definition, we hide the label of a secure file with a dependent pair GenFile : Type -> Type GenFile label = (l : label ** SecFile l) that abstracts away the concrete sensitivity level of the file. Moreover, we introduce a specialized join function joinOfFiles : BoundedJoinSemilattice label => List (GenFile label) -> label that folds the join function over a list of file sensitivity labels. Now, it is possible to implement a function that takes as input a list of files, reads the files, and returns a computation that concatenates all their contents (if no file error occurred) where the return value is labeled with the join of all their sensitivity labels. When implementing this, one has to satisfy non-trivial proof obligations as, for example, that l ⊑ joinOfFiles(files) for all secure files f ∈ files where the label of f is l. While provable (in 40 lines of code in our development), if equality is decidable for elements of the concrete lattice we can postpone such proof obligations to a point in time where it can be solved by reflexivity of equality. By defining a decidable lattice order decLeq : JoinSemilattice a => DecEq a => (x, y : a) -> Dec (x leq y) decLeq x y = decEq (x join y) y we can get such a proof "for free" by inserting a dynamic check of whether the flow is allowed. With this, a readFiles' function with the exact same functionality as the original readFiles function can be implemented with minimum effort. In the below, prf is the proof that the label l of file may flow to joinOfFiles files .
The downside of this is the introduction of a negative case, the No-case, that needs handling even though it will never occur if joinOfFiles is implemented correctly.
In combination with GenFile, decLeq can be used to implement several other interesting examples. For instance, a function that reads all files with a sensitivity label below a certain label to a string labeled with that label. The accompanying source code showcases multiple such examples that exploit decidable equality.

Declassification
Realistic applications often release some secret information as part of their intended behavior; this action is known as declassification.
In DepSec, trusted code may declassify secret information without adhering to any security policy as trusted code has access to both the DIO ℓ a and Labeled ℓ a data constructors. However, only giving trusted code the power of declassification is limiting as we want to allow the use of third-party code as much as possible. The main challenge we address is how to grant untrusted code the right amount of power such that declassification is only possible in the intended way.
Sabelfeld and Sands [37] identify four dimensions of declassification: what, who, where, and when. In this section, we present novel and powerful means for static declassification with respect to three of the four dimensions and illustrate these with several examples. To statically enforce different declassification policies we take the approach of Sabelfeld and Myers [36] and use escape hatches, a special kind of functions. In particular, we introduce the notion of a hatch builder; a function that creates an escape hatch for a particular resource and which can only be used when a certain condition is met. Such an escape hatch can therefore be used freely by untrusted code.

The what dimension
Declassification policies related to the what dimension place restrictions on exactly "what" and "how much" information is released. It is in general difficult to statically predict how data to be declassified is manipulated or changed by programs [34] but exploiting dependent types can get us one step closer.
To control what information is released, we introduce the notion of a predicate hatch builder only available to trusted code for producing hatches for untrusted code. Intuitively, the hatch builder takes as input a data structure d of type D followed by a predicate P upon d and something of type E. It returns a dependent pair of the initial data structure and a declassification function from sensitivity level l to l'. To actually declassify a labeled value e of type E one has to provide a proof that P d e holds. Notice that this proof may be constructed in the context of the sensitivity level l that we are declassifying from. The reason for parameterizing the predicate P by a data structure of type D is to allow declassification to be restricted to a specific context or data structure. This is used in the following example of an auction system, in which only the highest bid of a specific list of bids can be declassified.
Example Consider a two point lattice where L ⊑ H, H L and an auction system where participants place bids secretly. All bids are labeled H and are put into a data structure BidLog. In the end, we want only the winning bid to be released and hence declassified to label L. To achieve this, we define a declassification predicate HighestBid.
Informally, given a log log of labeled bids and a bid b, the predicate states that the bid is in the log, Elem (label b) log, and that it is the maximum bid, MaxBid b log. We apply predicateHatchBuilder to a log of bids and the HighestBid predicate to obtain a specialized escape hatch of type BidHatch that enforces the declassification policy defined by the predicate. To show the HighestBid predicate (which in our implementation comprises 40 lines of code), untrusted code will need a generalized unlabel function that establishes the relationship between label and the output of unlabel. The only difference is its return type: a computation that returns a value and a proof that when labeling this value we will get back the initial input. This definition poses no risk to soundness as the proof is protected by the computation sensitivity level. Limiting hatch usage Notice how escape hatches, generally, can be used an indefinite number of times. The Control.ST library [10] provides facilities for creating, reading, writing, and destroying state in the type of Idris functions and, especially, allows tracking of state change in a function type. This allows us to limit the number of times a hatch can be used. Based on a concept of resources, a dependent type STrans tracks how resources change when a function is invoked. Specifically, a value of type STrans m returnType in res out res represents a sequence of actions that manipulate state where m is an underlying computation context in which the actions will be executed, returnType is the return type of the sequence, in res is the required list of resources available before executing the sequence, and out res is the list of resources available after executing the sequence.
To represent state transitions more directly, ST is a type level function that computes an appropriate STrans type given a underlying computation context, a result type, and a list of actions, which describe transitions on resources. Actions can take multiple forms but the one we will make use of is of the form lbl ::: ty in :-> ty out that expresses that the resource lbl begins in state ty in and ends in state ty out. By instantiating ST with DIO l as the underlying computation context: and use it together with a resource Attempts, we can create a function limit that applies its first argument f to its second argument arg with Attempts (S n) as its initial required state and Attempts n as the output state. That is, we encode that the function consumes "an attempt." With the limit function it is possible to create functions where users are forced, by typing, to specify how many times it is used.
As an example, consider a variant of an example by Russo et al. [34] where we construct a specialized hatch passwordHatch that declassifies the boolean comparison of a secret number with an arbitrary number. To use this hatch, untrusted code is forced to specify how many times it is used.

The who and when dimensions
To handle declassification policies related to who may declassify information and when declassification may happen we introduce the notion of a token hatch builder only available to trusted code for producing hatches for untrusted code to use. The hatch builder takes as input a predicate Q on something of type S and returns a declassification function from sensitivity level l to l' given that the user can prove the existence of some s such that Q s holds. As such, by limiting when and how untrusted can obtain a value that satisfy predicate Q, we can construct several interesting declassification policies. The rest of this section discusses how predicate hatches can be used for time-based and authority-based control of declassification; the use of the latter is demonstrated on a case study.

Time-based hatches
To illustrate the idea of token hatches for the when dimension of declassification, consider the following example. Let Time be an abstract data type with a data constructor only available to trusted code and tick : DIO l Time a function that returns the current system time wrapped in the Time data type such that this is the only way for untrusted code to construct anything of type Time. Notice that this does not expose an unrestricted timer API as untrusted code can not inspect the actual value. Now, we instantiate the token hatch builder with a predicate that demands the existence of a Time token that is greater than some specific value.
TimeHatch : Time -> Type TimeHatch t = (t' ** t <= t' = True) -> Labeled H Nat -> Labeled L Nat As such, TimeHatch t can only be used after a specific point in time t has passed as only then untrusted code will be able to satisfy the predicate.

Authority-based hatches
The Decentralized Labeling Model (DLM) [26] marks data with a set of principals who owns the information. While executing a program, the program is given authority, that is, it is authorized to act on behalf of some set of principals. Declassification simply makes a copy of the released data and marks it with the same set of principals but excludes the authorities.
Similarly to Russo et al. [34], we adapt this idea such that it works on a security lattice of Principals, assign authorities with security levels from the lattice, and let authorities declassify information at that security level.
To model this, we define the abstract data type Authority with a data constructor available only to trusted code so that having an instance of Authority s corresponds to having the authority of the principal s. Notice how assignment of authorities to pieces of code consequently is a part of the trusted code. Now, we instantiate the token hatch builder with a predicate that demands the authority of s to declassify information at that level. That is, authHatch makes it possible to declassify information at level l to l' given an instance of the Authority l data type.
Example Consider the scenario of an online dating service that has the distinguishing feature of allowing its users to specify the visibility of their profiles at a fine-grained level. To achieve this, the service allows users to provide a discovery agent that controls their visibility. Consider a user, Bob, whose implementation of the discovery agent takes as input his own profile and the profile of another user, say Alice. The agent returns a possibly side-effectful computation that returns an option type indicating whether Bob wants to be discovered by Alice. If that is the case, a profile is returned by the computation with the information about Bob that he wants Alice to be able to see. When Alice searches for candidate matches, her profile is run against the discovery agents of all candidates and the result is added to her browsing queue.
To implement this dating service, we define the record type ProfileInfo A that contains personal information related to principal A. The interesting part of the dating service is the implementation of discovery agents. Figure 6 presents a sample discovery agent that matches all profiles with the opposite gender and only releases information about the name and gender. The discovery agent demands the authority of A and takes as input two profiles a : ProfileInfo A and b : ProfileInfo B. The resulting computation security level is B so to incorporate information from a into the result, declassification is needed. This is achieved by providing authHatch with the authority proof of A. The discovery agent sampleDiscoverer in Figure 6 unlabels B's gender, declassifies and unlabels A's gender and name, and compares the two genders. If the genders match, a profile with type ProfileInfo B only containing the name and gender of A is returned. Otherwise, Nothing is returned indicating that A does not want to be discovered. Notice that Refl is the constructor for the built-in equality type in Idris and it is used to construct the proof of equality between principals required by the hatch.

Soundness
Recent works [45,46] present a mechanically-verified model of MAC and show progress-insensitive noninterference (PINI) for a sequential calculus. We use this work as a starting point and discuss necessary modification in the following. Notice that this work does not consider any declassification mechanisms and neither do we; we leave this as future work. The proof relies on the two-steps erasure technique, an extension of the term erasure [20] technique that ensures that the same public output is produced if secrets are erased before or after program execution. The technique relies on a type-driven erasure function ε ℓ A on terms and configurations where ℓ A denotes the attacker security level. A configuration consists of an ℓ-indexed compartmentalized store Σ and a term t. A configuration Σ, t is erased by erasing t and by erasing Σ pointwise, i.e. ε ℓ A (Σ) = λℓ.ε ℓ A (Σ(ℓ)). On terms, the function essentially rewrites data and computations above ℓ A to a special • value. The full definition of the erasure function is available in Appendix A.5. From this definition, the definition of low-equivalence of configurations follows. Definition 1. Let c 1 and c 2 be configurations. c 1 and c 2 are said to be ℓ A -equivalent, After defining the erasure function, the noninterference theorem follows from showing a single-step simulation relationship between the erasure function and a small-step reduction relation: erasing sensitive data from a configuration and then taking a step is the same as first taking a step and then erasing sensitive data. This is the content of the following proposition.
The main theorem follows by repeated applications of Proposition 1.
Both the statement and the proof of noninterference for DepSec are mostly similar to the ones for MAC and available in Appendix B. Nevertheless, one has to be aware of a few subtleties.
First, one has to realize that even though dependent types in a language like Idris may depend on data, the data itself is not a part of a value of a dependent type. Recall the type Vect n Nat of vectors of length n with components of type Nat and consider the following program. This example may lead one to believe that it is possible to extract data from a dependent type. This is not the case. Both n and a are implicit arguments to the length function that the compiler is able to infer. The actual type is length : {n : Nat} -> {a : Type} -> Vect n a -> Nat As a high-level dependently typed functional programming language, Idris is elaborated to a low-level core language based on dependent type theory [9]. In the elaboration process, such implicit arguments are made explicit when functions are defined and inferred when functions are invoked. This means that in the underlying core language, only explicit arguments are given. Our modeling given in Appendix A.1 reflects this fact soundly. Second, to model the extended expressiveness of DepSec, we extend both the semantics and the type system with compile-time pure-term reduction and higher-order dependent types. These definitions are standard (defined for Idris by Brady [9]) and available in Appendix A.2 and A.3. Moreover, as types now become first-class terms, the definition of ε ℓ A has to be extended to cover the new kinds of terms. As before, primitive types are unaffected by the erasure function, but dependent and indexed types, such as the type DIO, have to be erased homomorphically, e.g., ε ℓ A (DIO ℓ τ : Type) DIO ε ℓ A (ℓ) ε ℓ A (τ). The intuition of why this is sensible comes from the observation that indexed dependent types considered as terms may contain values that will have to be erased. This is purely a technicality of the proof. If defined otherwise, the erasure function would not commute with capture-avoiding substitution on terms, which is vital for the remaining proof.

Related work
Security libraries The pioneering and formative work by Li and Zdancewic [19] shows how arrows [17], a generalization of monads, can provide information-flow control without runtime checks as a library in Haskell. Tsai et al. [44] further extend this work to handle side-effects, concurrency, and heterogeneous labels. Russo et al. [34] eliminate the need for arrows and implement the security library SecLib in Haskell based solely on monads. Rather than labeled values, this work introduces a monad which statically label side-effect free values. Furthermore, it presents combinators to dynamically specify and enforce declassification policies that bear a resemblance to the policies that DepSec are able to enforce statically.
The security library LIO [40,41] dynamically enforces information-flow control in both sequential and concurrent settings. Stefan et al. [39] extend the security guarantees of this work to also cover exceptions. Similar to this work, Stefan et al. [41] present a simple API for implementing secure conference reviewing systems in LIO with support for data-dependent security policies.
Inspired by the design of SecLib and LIO, Russo [33] introduces the security library MAC. The library statically enforces information-flow control in the presence of advanced features like exceptions, concurrency, and mutable data structures by exploiting Haskell's type system to impose flow constraints. Vassena and Russo [45], Vassena et al. [46] show progress-insensitive noninterference for MAC in a sequential setting and progress-sensitive noninterference in a concurrent setting, both using the two-steps erasure technique.
The flow constraints enforcing confidentiality of read and write operations in DepSec are identical to those of MAC. This means that the examples from MAC that do not involve concurrency can be ported directly to DepSec. To the best of our knowledge, data-dependent security policies like the one presented in Section 3 cannot be expressed and enforced in MAC, unlike LIO that allows such policies to be enforced dynamically. DepSec allows for such security policies to be enforced statically. Moreover, Russo [33] does not consider declassification. To address the static limitations of MAC, HLIO [11] takes a hybrid approach by exploiting advanced features in Haskell's type-system like singleton types and constraint polymorphism. Buiras et al. [11] are able to statically enforce information-flow control while allowing selected security checks to be deferred until run-time.
Dependent types for security Several works have considered the use of dependent types to capture the nature of data-dependent security policies. Zheng and Myers [50,51] proposed the first dependent security type system for dealing with dynamic changes to runtime security labels in the context of Jif [28], a full-fledged IFC-aware compiler for Java programs, where similar to our work, operations on labels are modeled at the level of types. Zhang et al. [49] use dependent types in a similar fashion for the design of a hardware description language for timing-sensitive information-flow security.
A number of functional languages have been developed with dependent type systems and used to encode value-dependent information flow properties, e.g. Fine [42]. These approaches require the adoption of entirely new languages and compilers where DepSec is embedded in an already existing language. Morgenstern and Licata [24] encode an authorization and IFC-aware programming language in Agda. However, their encoding does not consider side-effects. Nanevski et al. [29] use dependent types to verify information flow and access control policies in an interactive manner.
Lourenço and Caires [22] introduce the notion of dependent information-flow types and propose a fine-grained type system; every value and function have an associated security level. Their approach is different to the coarse-grained approach taken in our work where only some computations and values have associated security labels. Rajani and Garg [32] show that both approaches are equally expressive for static IFC techniques and Vassena et al. [47] show the same for dynamic IFC techniques.
Principles for Information Flow Bastys et al. [6] put forward a set of informal principles for information flow security definitions and enforcement mechanisms: attackerdriven security, trust-aware enforcement, separation of policy annotations and code, language-independence, justified abstraction, and permissiveness.
DepSec follows the principle of trust-aware enforcement, as we make clear the boundary between the trusted and untrusted components in the program. Additionally, the design of our declassification mechanism follows the principle of separation of policy annotations and code. The use of dependent types increases the permissiveness of our enforcement as we discuss throughout the paper. While our approach is not fully language-independent, we posit that the approach may be ported to other programming languages with general-purpose dependent types.
Declassification enforcement Our hatch builders are reminiscent of downgrading policies of Li and Zdancewic [18]. For example, similar to them, DepSec's declassification policies naturally express the idea of delimited release [35] that provides explicit characterization of the declassifying computation. Here, DepSec's policies can express a broad range of policies that can be expressed through predicates, an improvement over simple expression-based enforcement mechanisms for delimited release [4,5,35].
An interesting point in the design of declassification policies is robust declassification [48] that demands that untrusted components must not affect information release. Qualified robustness [2,27] generalizes this notion by giving untrusted code a limited ability to affect information release through the introduction of an explicit endorsement operation. Our approach is orthogonal to both notions of robustness as the intent is to let the untrusted components declassify information but only under very controlled circumstances while adhering to the security policy.

Conclusion and future work
In this paper, we have presented DepSec -a library for statically enforced informationflow control in Idris. Through several case studies, we have showcased how the DepSec primitives increase the expressiveness of state-of-the-art information-flow control libraries and how DepSec matches the expressiveness of a special-purpose dependent information-flow type system on a key example. Moreover, the library allows programmers to implement policy-parameterized functions that abstract over the security policy while retaining precise security levels.
By taking ideas from the literature and by exploiting dependent types, we have shown powerful means of specifying statically enforced declassification policies related to what, who, and when information is released. Specifically, we have introduced the notion of predicate hatch builders and token hatch builders that rely on the fulfillment of predicates and possession of tokens for declassification. We have also shown how the ST monad [10] can be used to limit hatch usage statically.
Finally, we have discussed the necessary means to show progress-insensitive noninterference in a sequential setting for a dependently typed information-flow control library like DepSec.
Future work There are several avenues for further work. Integrity is vital in many security policies and is not considered in MAC nor DepSec. It will be interesting to take integrity and the presence of concurrency into the dependently typed setting and consider internal and termination covert channels as well. It also remains to prove our declassification mechanisms sound. Here, attacker-centric epistemic security conditions [3,15] that intuitively express many declassification policies may be a good starting point.

A The Calculus
This section formalizes DepSec as TT sec , a dependently typed call-by-value λ-calculus extended with conditional expressions, references, unit, integer, and boolean values, as well as higher order dependent types and security primitives. Figure 7 shows the formal syntax of the pure calculus underlying TT sec where meta variables t, c, b, and τ denote terms, constants, binders, and types, respectively. The syntax closely resembles the syntax of TT, the underlying calculus of Idris, but with the addition of a conditional construct and base types Int, Bool, and (). We extend this standard calculus with the security primitives of DepSec. Figure 8 presents the extensions to Figure 7 that forms the formal syntax of TT sec . We introduce the security monad DIO v t as well as type DIO τ ℓ t and monadic operators pure t, t > > = t, and plug t. We introduce a labeled value Labeled v t, a type Labeled τ ℓ t, and labeling and unlabeling functions label t and unlabel t. As an example of a labeled resource we introduce references as values Ref n v as well as means for allocating, reading, and writing to references.

A.2 Operational semantics
Definition 2 (Small-step pure semantics). Let Term be the set of terms in TT sec and let t 1 , t 2 ∈ Term. The relation denotes the small-step operational semantics of the TT sec calculus. The relation t 1 t 2 denotes that t 1 reduces to t 2 in one reduction step according to the inference rules in Figure 9.
We explicitly distinguish pure-term evaluation from top-level monadic-term evaluation. The extended semantics is represented as the relation c 1 −→ c 2 introduced in Definition 4 which extends the pure semantics via Lift.

Definition 3 (Store). Let Label be a set of security labels. The function
Σ : Label → List Term denotes a store compartmentalized into isolated labeled segments, one for each label. We write Σ(ℓ)[n] to retrieve the nth cell in the ℓ-memory and Σ(ℓ) [n] t for the store obtained by performing the update Σ(ℓ)[n → t].
Definition 4 (Monadic-term semantics). Let Σ, t be a configuration consisting of a store Σ and a term t ∈ Term. Let Conf be the set of all such configurations. The relation denotes the monadic-term evaluation according to the inference rules of Figure 9. Σ, t −→ * Σ ′ , t ′ denotes the reflexive transitive closure of −→, and we write Σ, t ⇓ Σ ′ , v if and only if v is a value and Σ, t −→ * Σ ′ , v .
Note that we consider all non-reducible terms to be values and that constructors Labeled v , DIO v , and Ref v are not available to the user but only introduced in the semantics to model the run-time value produced by e.g. label and pure.

A.3 Typing rules
Similar to TT, type checking and the dynamic semantics are defined mutually since evaluation relies on terms to be well-typed, and type checking relies on evaluation as equivalence of terms or types is determined by comparing their normal forms. Compiletime evaluation of TT sec is defined by the pure reductions rules in Figure 9 relative to a context Γ. Conversion (≃) is the smallest equivalence relation closed under reduction, that is, if Γ ⊢ x ≃ y then x and y reduce to the same normal form. The type inference rules for TT sec is presented in Figure 10. These rules use the cumulativity ( ) relation defined in Figure 11. In TT, the type of types, Type, is parameterized by a universe level (constructing an infinite hierarchy of universes) to prevent Girard's paradox. As universe levels are transparent to the user, this is not relevant for our noninterference proof and we ignore this matter in the following. As for TT, we also conjecture that TT sec respects usual properties such as type preservation and uniqueness of typing at compile-time.  Fig. 10. Typing rules for TT sec . Fig. 11. Cumulativity.

A.4 Example: Concatenating strings
This example illustrates the adequacy of the TT sec calculus. The concrete example immitates the readTwoFiles function presented in Section 4. It takes two labeled strings as input and returns the concatenated result of the content of these, labeled with the join of the original labels. We assume having a well-typed string concatenation function + + and a well-defined join function for which the following rules hold: ℓ : Label ℓ ′ : Label ℓ ⊑ join ℓ ℓ ′ join : ∀x, y : Label.Label The implementation of a concatenation function for labeled strings TT sec is presented in Figure 12.
In most cases the definition of the erasure function is straightforward as we simply collapse sensitive information and computations to • if they are above the security level of the attacker and otherwise apply the function homomorphically. In one particular case this idea fails, namely the erasure of the term plug t. Consider plug t : DIO τ ℓ (Labeled τ ℓ ′ τ) for some ℓ, ℓ ′ , and τ. If the adversary is not allowed to see ℓ, i.e. ℓ ℓ A , the computation should not be visible to the adversary and therefore it should be completely collapsed into •. Unfortunately, this approach of rewriting entire computations fails if ℓ ⊑ ℓ A and ℓ ′ ℓ A as it would not be possible to • and it does therefore not have a normal form. Hence, we need a context-sensitive erasure function as the idea about simply erasing computations above the level of an attacker is too simple. To handle this case soundly we make use of two-steps erasure that works by introducing an extra semantic step for plug • introduced by the erasure function. The extension is presented in Figure 13. Note that this, from an attackers point of view, still looks exactly like one would expect when erasing data and this is therefore purely a technicality of the proof. T-Hole Γ ⊢ τ : Type The definition of ε ℓ A on stores is straightforward as we have a compartmentalized memory. If the a store is erased up to a security level ℓ A then all levels above this should simply be collapsed entirely.
Definition 6 (Erasure function on configurations). Let ℓ A be the attacker's security level. The function ε ℓ A : Conf → Conf • denotes the erasure function for configurations defined by where the store Σ is erased pointwise at each security level and in every cell, i.e.
Definition 7 (ℓ A -equivalence). Let c 1 , c 2 ∈ Conf. c 1 and c 2 are said to be indistinguishable from security level ℓ A , written c 1 ≈ ℓ A c 2 , if and only if ε ℓ A c 1 and ε ℓ A c 2 are structurally equivalent, written ε ℓ A c 1 ≡ ε ℓ A c 2 .

B Results
Proof. The statement follows by case splitting on t and v and the definition of ε ℓ A .
Lemma 2 (Distributivity on pure term reduction). Let t 1 , t 2 ∈ Term. If t 1 t 2 then Proof. The proof goes by structural induction in the derivation of t 1 t 2 .
If t 1 t 2 has type DIO τ ℓ τ ′ and ℓ ℓ A , the statement follows from the definition of ε ℓ A and Hole. Otherwise, t 1 t ′ 1 holds by App 1 and by the induction hypothesis ε ℓ A t 1 ε ℓ A t ′ 1 . By App 1 and definition of ε ℓ A it holds that ε ℓ A t 1 t 2 ε ℓ A t ′ 1 t 2 . App 2 : The argument is identical to the App 1 case.
The argument is identical to the App 1 case. If 2 and If 3 : If t 1 and t 2 have type DIO τ ℓ τ ′ and ℓ ℓ A , the statement follows from the definition of ε ℓ A and Hole. Otherwise, the statement follows directly by the definition ε ℓ A and If i . Bind 1 : Assume pure t 1 > > = t 2 t 2 t 1 . As we assume well-typed terms, pure t 1 > > = t 2 has type DIO τ ℓ τ for some ℓ and τ. If ℓ ℓ A the statement follows from the definition of ε ℓ A and Hole. Otherwise, the statement follows from Bind-Pure and the definition of ε ℓ A . Pure 1 : Assume pure t pure t ′ . As we assume well-typed terms, pure t and pure t ′ have type DIO τ ℓ τ for some ℓ and τ. If ℓ ⊑ ℓ A then the statement follows from the induction hypothesis, the definition of ε ℓ A and Pure 1 . If ℓ ℓ A then the statement follows from the definition ε ℓ A and Hole. Pure 2 : Assume pure v DIO v v. As we assume well-typed terms, pure v and DIO v v have type DIO τ ℓ τ for some ℓ and τ. If ℓ ⊑ ℓ A then the statement follows from the definition of ε ℓ A and Pure 2 . If ℓ ℓ A then the statement follows from the definition ε ℓ A and Hole. Label 1 : Assume label t label t ′ . As we assume well-typed terms, label t and label t ′ have type Labeled τ ℓ τ for some ℓ and τ. If ℓ ⊑ ℓ A then the statement follows from the induction hypothesis, the definition of ε ℓ A , and Label 1 . If ℓ ℓ A then the statement follows from the definition ε ℓ A Hole and Label 1 . Label 2 : Assume label t Labeled v t. As we assume well-typed terms, label t and Labeled v t have type Labeled τ ℓ τ for some ℓ, ℓ ′ , and τ. If ℓ ℓ A the statement follows from the definition of ε ℓ A Hole and Label 1 . Otherwise the statement follows by Label 2 , the definition of ε ℓ A and the induction hypothesis. Unlabel 1 : Assume unlabel t unlabel t ′ . As we assume well-typed terms, unlabel t and unlabel t ′ have type DIO τ ℓ τ for some ℓ and τ. If ℓ ℓ A the statement follows from the definition of ε ℓ A and Hole. Otherwise, the statement follows from the induction hypothesis, Unlabel 1 , and the definition of ε ℓ A . Unlabel 2 : Assume unlabel (Labeled v t) pure t. As we assume well-typed terms, unlabel (Labeled v t) and pure t have type DIO τ ℓ τ where Labeled v t have type Labeled τ ℓ ′ τ such that ℓ ′ ⊑ ℓ. If ℓ ℓ A the statement follows from the definition of ε ℓ A and Hole. If ℓ ⊑ ℓ A then by transitivity of the partial ordering ℓ ′ ⊑ ℓ A holds and the statement then follows by Unlabel 2 and the definition of ε ℓ A . New 1 : Assume new ℓ t new ℓ t ′ . As we assume well-typed terms, new ℓ t and new ℓ t ′ have type DIO τ ℓ τ for some ℓ and τ. If ℓ ℓ A then the statements follows from the definition of ε ℓ A and Hole. If ℓ ⊑ ℓ A the statement follows from the induction hypothesis, the definition of ε ℓ A , and New 1 . Write 1 : Assume write t 1 t 2 write t ′ 1 t 2 . As we assume well-typed terms, write t 1 t 2 and write t ′ 1 t 2 have type DIO τ ℓ () and t 2 has type Labeled τ ℓ ′ τ. If ℓ ℓ A the statement follows from the definition of ε ℓ A and Hole. If ℓ ⊑ ℓ A then the statement follows by the induction hypothesis, Write 1 , and the definition of ε ℓ A . Write 2 : The argument is identical to the Write 1 case. Read 1 : Assume read t read t ′ . As we assume well-typed terms read t and read t ′ have type DIO τ ℓ ′ (Labeled τ ℓ τ). If ℓ ′ ℓ A the statement follows from the definition of ε ℓ A and Hole. Otherwise, if ℓ ′ ⊑ ℓ A then consider whether ℓ ⊑ ℓ A holds. If ℓ ℓ A the statement follows by Read 1 and Hole. If ℓ ⊑ ℓ A then the statement follows by the induction hypothesis, Read 1 , and the definition of ε ℓ A . DIO i , Labeled i , Ref i , Forall i : In all cases, the statement follows directly from the induction hypothesis, the definition of ε ℓ A , and the inference rules. Plug • : Assume plug • t pure (Labeled v •). As we assume well-typed terms, plug • t has type DIO τ ℓ (Labeled τ ℓ ′ τ). Consider whether ℓ ′ ⊑ ℓ A . In both cases, the statement follows by the definition ε ℓ A and Plug • . Hole: The statement follows directly from the definition of ε ℓ A and Hole.
Lemma 3 (Erasure of a computation). Let t ∈ Term. If t has type DIO τ ℓ τ and ℓ ℓ A then ε ℓ A t ≡ •.
Proof. The statement follows directly by case splitting on t and the definition of ε ℓ A .
Plug: Assume Σ, plug t −→ Σ ′ , pure (Labeled v t ′ ) . As we assume welltyped terms, t has type DIO τ ℓ ′ τ ′ for some ℓ ′ and τ ′ where ℓ ⊑ ℓ ′ . By transitivity of ⊑ it follows that ℓ ′ ℓ A and then the statement follows by Lemma 5 as t is structurally smaller than plug t.
. As we assume well-typed terms, new ℓ ′ (Labeled v t) and pure (Ref By transitivity of ⊑ it follows that ℓ ′ ℓ A . Note that the only memory compartment changed is the one of ℓ ′ and as writing to an erased cell makes no update the statement follows. Write 3 : The argument is identical to the New case. Plug: Assume Σ, plug t −→ Σ ′ , pure (Labeled v t ′ ) . As we assume well-typed terms, t has type DIO τ ℓ ′ τ ′ for some ℓ ′ and τ ′ . From Plug it holds that Σ, t ⇓ Σ ′ , DIO v t ′ . If ℓ ′ ⊑ ℓ A , cf. Lemma 9 and that t is structurally smaller than plug t, it follows ε ℓ A Σ, t ⇓ ε ℓ A Σ ′ , DIO v t ′ , and from the definition of ε ℓ A and Plug the statement holds. If ℓ ′ ℓ A , it follows from Lemma 5 that ε ℓ A Σ ≡ ε ℓ A Σ ′ , and using Lift, the definition of ε ℓ A and Plug • the statement follows.
As we assume well-typed terms, Ref ℓ ′ v n has type Ref τ ℓ ′ τ and Labeled v t has type Labeled τ ℓ ′′ τ for some ℓ ′ , ℓ ′′ , and τ where ℓ ′′ ⊑ ℓ ′ . If ℓ ′ ⊑ ℓ A then by transitivity ℓ ′′ ⊑ ℓ A and hence by definition of ε ℓ A . The statement now follows from Lemma 6, Write 3 , and the definition of ε ℓ A . If ℓ ′ ℓ A then consider whether ℓ ′′ ⊑ ℓ A . In either case, the statement follows from Lemma 6, Write 3 , and the definition of ε ℓ A .
. As we assume well-typed terms, Ref ℓ v n has type Ref τ ℓ τ and pure (Labeled v Σ(ℓ)[n]) has type DIO τ ℓ ′ (Labeled τ ℓ τ) for some ℓ, ℓ ′ and τ where ℓ ′ ⊑ ℓ. If ℓ ⊑ ℓ A then by transitivity ℓ ′ ⊑ ℓ A and the statement follows from Lemma 7, Read 2 , and the definition of ε ℓ A . If ℓ ℓ A the statement follows by the fact that reading from an erased compartment yields •, the definition of ε ℓ A , and Read 2 .
Proof. The statement follows from repeated applications of Proposition 2.      % access export ||| Creating a reference to a labeled value. 15 ||| @ flow evidence that l may flow to l' 16 ||| @ flow' evidence that l' may flow to l'' 17 ||| @ value The initial value for the reference. ||| Reading a secure reference. 28 ||| @ flow evidence that l may flow to l' 29 ||| @ ref The reference which we wish to read. ||| Wrting a labeled value to a secure reference. 39 ||| @ flow evidence that l may flow to l' 40 ||| @ flow' evidence that l' may flow to l'' 41 ||| @ ref The reference which we wish to write to. 42 ||| @ content The content which we wish too read.