Skip to content
Eduardo Santos Rodriguez edited this page Mar 13, 2026 · 7 revisions

This description is old and should be disregarded unless you really, really know why you are here!

Compiling with foreseeti compiler

Installation

Instructions regarding the MAL compiler and the MAL project structure can be found in the exampleLang README.

Compilation primer

For detailed instructions, see building exampleLang and running unit tests (exampleLang).

For instructions regarding how to write tests, see Instantiating Language Models.

Compile and run unit tests

mvn test

Generates .java files from the MAL specifications and outputs them into <project>/target/generated-test-sources. Also compiles the language source files together with any unit test code found. Finally runs the unit tests.

Compile and run a specific test file

mvn test -Dtest=TestMyLanguage

Same as mvn test except only the test cases defined in TestMyLanguage.java are run.

Compile and run a specific test case

mvn test -Dtest=TestMyLanguage#myTestCase

Same as mvn test except only the test case myTestCase defined in TestMyLanguage.java is run.

Generate Java source

mvn generate-test-sources

This solely compiles MAL specifications into .java source code files. The files are found under <project>/target/generated-test-sources.

Generate and compile Java source

mvn test-compile

This compiles MAL specifications into .java source code files. Also compiles those into .class files together with any unit test files found. The Java source files are found under <project>/target/generated-test-sources and the class files under <project>/target/test-classes. No tests are run with this command.

Compiling with legacy compiler

Versioning

This page documents instructions for the legacy compiler. The documentation for the current foreseeti compiler can be found here


The compiler generates Java code from the MAL specification. More details regarding compilation can be found in the MAL Compiler User Guide and in the GIT repository. MAL is compiled with the following commands:

Assertion method Description
assertUncompromised() Check for unsuccessful compromise of the attack step.
assertUncompromisedFrom(parent) Check for unsuccessful compromise of the attack step from the specified parent attack step.
assertCompromisedInstantaneously() Check for successful and immediate compromise of the attack step.
assertCompromisedWithEffort() Check for successful compromise of the attack step but only after some effort/time is spent.
assertCompromisedInstantaneouslyFrom() Same as assertCompromisedInstantaneously from specified parent attack step.
assertCompromisedWithEffortFrom() Same as assertCompromisedWithEffort from specified parent attack step.
java -cp mal-compiler-0.0.X.jar se.kth.mal.Master -i <input_path>  
-o <ouput_path> -p <package_name> -t <tests_path>


usage: 
 X to be replaced by the version number. 
 -f,--foreseeti       flag to use foreseeti backend
 -i,--input <arg>     input mal file path
 -o,--output <arg>    output folder path for generated code
 -p,--package <arg>   package name of generated code
 -t,--tests <arg>     output folder path for generated test code
 -v,--visual <arg>    icons for visualization

The output of a successful compilation will look like:

>>> Using MAL Compiler <<<
Reading from <input>.malinc
...
Attempting to write the TTC Config File...
Complete

Several files will be created in the specified output folder. One of these is the html file, which can be opened in a web browser to visually represent the attack graph. The attack graph's large circles are the assets, the small circles are the attack steps and the squares are defences.

Instantiating Language Models

Table of Contents

Overview

The Test File

- Setting up

-- Complete Test File Skeleton

-- Dissecting the Test File Skeleton

--- Package Definition

--- Import Statements

--- Test Class Declaration

--- Test Case Definition

- Writing Test Cases

-- Complete Test Case Example

-- Dissecting the Test Case Example

--- Instantiating Assets

--- Define Associations

--- Specify Attacker Starting Point(s)

--- Run the Simulation and Test Assertions

- Assertion Methods

- Test Case Styles

-- Waterfall

--- Waterfall Example

-- Model-based

--- Model-based Example

Overview

After the MAL specifications are created, it is time to instantiate models and verify the domain-specific language. A model is an instantiation of some assets and the connections between them. Each model created is considered one test case. In the case of MAL, models are instantiated using Java and tests are executed with JUnit. Test case files must, therefore, be structured accordingly. Additionally, test case files must be placed under the <project>/src/test/java/<Defined>/<Project>/<ID> directory in accordance with the Apache Maven project structure. Finally, test case filenames must always begin with Test, e.g., TestExampleLang.java.

MAL test cases conventionally follow a 4-part structure:

  1. Instantiate assets as Java objects.
  2. Define associations.
  3. Specify the attacker's starting point(s).
  4. Run the simulation and test assertions.

There are two distinct styles of test case definitions, waterfall and model-based. The waterfall style follows the 4-part structure for every test case. They are easy to write and follow, making it good for beginners. The waterfall style, however, has a lot of redundancy and takes longer to write. The model-based style is more streamlined but potentially more difficult to follow when starting out.

The Test File

Setting Up

Complete Test File Skeleton

package <SameAsLanguageID>.test
import core.Attacker;
import core.Asset;
import core.AttackStep;
import core.Defense;
import core.AttackStepMax;
import core.AttackStepMin;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;

public class Test<SameAsFileName> {

    @Test
    @DisplayName("An example test case")
    public void testExampleCase() {
        //Test code here...
    }

    @AfterEach
    public void deleteModel() {
        // Clean up before each test
        Asset.allAssets.clear();
        AttackStep.allAttackSteps.clear();
        Defense.allDefenses.clear();
    }
}

Dissecting the Test File Skeleton

Package Definition
// Generic syntax
//package <SameAsLanguageID>.test;

// Example
package com.lang.example.test;

The first line in each file should be the package definition. This is the same as the project ID for the MAL project excecpt .test is appended.

Import Statements
import core.*;

Every test file typically begins by importing fundamental MAL components. These are Attacker, AttackStep, Asset, Defense, AttackStepMin, and AttackStepMax. Importing all of them is not necessary. Only import the ones actually used.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;

The appropriate JUnit components must be imported to run tests. The Test component is necessary for defining tests. AfterEach is typically used to remove lingering objects before each test case. DisplayName allows for setting custom display names, commonly shown in IDEs. Any JUnit components may be imported but the three mentioned are the most common.

Test Class Declaration
// Generic syntax
// public class Test<SameAsFileName> {...}

public class TestExampleLang {...}

Defines the class containing test case definitions. The class name must be identical to the file name, excluding the file extension.

Test Case Definition
@Test
@DisplayName("An example test case")
public void testExampleCase() {
    //Test code here...
}

@Test designates the function as a test case. @DisplayName sets a custom name for the case. JUnit test cases must always be declared as public functions without return types (void). The test code is regular java code following the 4-part MAL test case structure. Test cases can be defined in any order since JUnit randomizes the order each time tests are run.

@AfterEach
public void deleteModel() {
    Asset.allAssets.clear();
    AttackStep.allAttackSteps.clear();
    Defense.allDefenses.clear();
}

This function is executed after each test case. The code above clears out all lingering assets between each test.

Writing Test Cases

Complete Test Case Example

@Test
@DisplayName("An example test case")
public void testExampleCase() {
    // Create assets
    Computer computer = new Computer("Linux", false); // Disable some defense
    // Computer computer = new Computer("Linux", true); // Enable some defense
    User alice = new User("Alice");

    // Create associations
    Computer.addUsers(alice);

    // Create attacker
    Attacker attacker = new Attacker();
    attacker.addAttackPoint(computer.attemptToHack);

    // Run simulation and test assertions
    attacker.attack();

    computer.compromised.assertCompromisedInstantaneously();
    alice.hacked.assertCompromisedInstantaneously();
}

Dissecting the Test Case Example

Instantiating Assets
// Generic syntax
// MyAsset assetName = new MyAsset("NameForTestPrintOuts", defense1, [...], defensen);

// Example
Computer computer = new Computer("Linux", false);
// Computer computer = new Computer("Linux", true);

Each non-abstract Asset defined with MAL has its own Java class definition. They are instantiated like regular Java objects. The string NameForTestPrintOuts represents the object instance in test log printouts.

Define Associations
Computer computer = new Computer("Linux");
User alice = new User("Alice");

// Generic syntax
// asset1.add<Role>(asset2);


// Example
Computer.addUsers(alice);
// alice.addComputers(computer); // Same association, reverse direction.

Every asset has an automatically generated add function for each association. The function names are determined by the roles defined in the MAL specifications. Add functions always have the form add<Role>, where the first letter of the role name is always capitalized.

Specify Attacker Starting Point(s)
Attacker attacker = new Attacker();
attacker.addAttackPoint(computer.attemptToHack);

One attacker object must be instantiated for every test case. The addAttackPoint function is used to specify which attack steps are compromised upon starting the simulation. In this example, attemptToHack is an attack step for an instantiated Computer asset.

Run the Simulation and Test Assertions
Attacker attacker = new Attacker();
attacker.addAttackPoint(computer.attemptToHack);
attacker.attack();

computer.hacked.assertCompromisedInstantaneously();
alice.hacked.assertCompromisedInstantaneously();

Calling attack() for the Attacker object without parameters starts the simulation. Nothing will happen unless this is called. The last step is to write assertion clauses. These check whether the specified attack steps were reachable or unreachable. The test succeeds if all results conformed to the assertions. The test fails if at least one contradiction is produced. In this example, the attacker starts by attempting to hack a computer. The assertion checks if the corresponding compromised step was reached as expected. Then, it checks whether the user, Alice, was hacked as a result.

Assertion Methods

Assertion method Description
assertUncompromised() Check for unsuccessful compromise of the attack step.
assertUncompromisedFrom(parent) Check for unsuccessful compromise of the attack step from the specified parent attack step.
assertCompromisedInstantaneously() Check for successful and immediate compromise of the attack step.
assertCompromisedWithEffort() Check for successful compromise of the attack step but only after some effort/time is spent.
assertCompromisedInstantaneouslyFrom() Same as assertCompromisedInstantaneously from specified parent attack step.
assertCompromisedWithEffortFrom() Same as assertCompromisedWithEffort from specified parent attack step.

Test Case Styles

Waterfall

The waterfall style is when the 4-step structure is repeated for every test case. It is also the same one demonstrated on this page. Waterfall test cases are easy to write and follow. However, they tend to be lengthy and contain a lot of redundancy. The waterfall style is, therefore, only recommended for practicing the basics. Other styles, such as the model-based style, should be used eventually.

Waterfall Example
@Test
@DisplayName("A waterfall example")
public void testExampleCase() {
    // Create assets
    Computer computer = new Computer("Linux", false);
    User alice = new User("Alice");

    // Create associations
    Computer.addUsers(alice);

    // Create attacker
    Attacker attacker = new Attacker();
    attacker.addAttackPoint(computer.attemptToHack);

    // Run simulation and test assertions
    attacker.attack();

    computer.compromised.assertCompromisedInstantaneously();
    alice.hacked.assertCompromisedInstantaneously();
}

Model-based

The model-based, or object-oriented, style creates additional private classes within the test class. These are referred to as models. Models can, for example, instantiate and associate any number of pre-defined assets according to parameters sent to constructors. This style is very efficient when many test cases require similar, and possibly complex, setups. The drawback with this style is that test cases can become more difficult to follow, especially when handed to other developers.

Model-based Example
private class ComputerModel{
    // Basic assets + associations model
    public final User alice = new User("Alice");
    public Computer computer;

    public ComputerModel(boolean isDefended){
        computer = new Computer("Linux", isDefended)
        computer.addUsers(alice);
    }
}

@Test
@DisplayName("A model-based example")
public void testExampleCase() {
    // Create model (assets + associations)
    ComputerModel model = new ComputerModel(false);

    // Create attacker
    Attacker attacker = new Attacker();
    attacker.addAttackPoint(model.computer.attemptToHack);

    // Run simulation and test assertions (models can also include attackers and assertions!)
    attacker.attack();

    model.computer.compromised.assertCompromisedInstantaneously();
    model.alice.hacked.assertCompromisedInstantaneously();
}

MAL Syntax (Foreseeti Compiler)

This is the developers' guide to MAL. For technical details, see the MAL compiler wiki.

This section documents the latest changes based on the foreseeti MAL compiler. The syntax for the legacy compiler can be found here.

Language Constituents

The following are commonly used keywords in writing MAL specification:

  • define Defines information for entire MAL projects. The syntax is '#key: "value"', for example '#version: "1.0.0". The keys #id and #version must be present in every project. For more information, see the MAL compiler wiki.

  • include "path/to/specification.mal" Imports another MAL specification. Enables references to assets and elements defined in separate specifications. Paths are relative to the specification with the include statement.

  • category Similar to a package in Java. A category consists of one or more assets. The category does not bear semantics, it is only there to enable structure for the language developer.

  • asset Similar to a class in Java. An asset could represent physical objects (e.g., a network router) or logical objects (e.g., Credentials, or a Network Service). When the MAL compiler generates the Java code from the MAL specifications, an asset is translated into a java class.

  • extends Similar to inheritance in Java. A child asset inherits attack steps and defense mechanisms from its parent. Child steps and mechanisms override their parent counterparts by default if the same names are used. Let expressions and associations are also inherited by extended assets (e.g. we don't need to create the association for an extended asset Linux to another asset Application, if its parent asset Machine is already associated with Application.).

  • abstract asset Similar to asset except it cannot be instantiated, only extended. An abstract asset need not have a class but its properties are inherited by its children. So, in Java generated code, an abstract asset will not have a class, and its properties are included in each child asset.

  • user info This keyword is used in MAL to provide information for end users about the asset and/or the associated attack step and defense mechanism.

  • developer info This keyword is used by developers of MAL specifications. It tells other MAL writers why the attack step is used and other related information.

  • modeler info This keyword is used to provide information to modellers or parser developers. It can be used to communicate assumptions or parsing requirements, which might otherwise be ambiguous.

  • let This keyword is used to associate a given expression with a specific reusable name. The syntax is 'let <nameHere> = <MAL expression>'. Let expressions can be defined within the scope of entire assets, specific attack steps, and defense mechanisms. Let expressions can be referred to across separate assets like attack steps, assuming appropriate associations exist.

  • association Association is analogous to UML class diagram associations. It denotes a potential bidirectional structural relationship (physical or logical) between two assets. The syntax is 'Asset1 [role1] <multiplicity1> <-- AssociationName --> <multiplicity2> [role2] Asset2'. The multiplicity of a composite association can be [1], [1..*] or [*] on the composite end. Role names determine the directional references to other assets used in code. For example, Asset1 references Asset2 as role2.A, where A is an attack step. Conversely, Asset2 references Asset1 as role1.A.

MAL Symbols

Symbol Meaning Description
-> Leads to The successful compromise of this attack step allows the attacker to consequently compromise further attack steps. Also specifies steps affected by defenses and existence checks.
+> Append Child assets only. When parent and child assets have overlapping elements, e.g. | access, the expressions defined for the child access are appended to those of the parent access. The child expressions will otherwise override those of the parent.
| OR An OR attack step A can be reached if any of the attack steps which refers to A is reached.
& AND An AND attack step A can be reached only if all of the attack steps which refer to A are reached.
# Defense As opposed to attack steps defenses are boolean. A defense step represents the countermeasure of an attack step (e.g., passwordBruteForce attack step can be defended by twoFactorAuthentications defense step). While declaring an asset, it is possible to either enable or disable a defense step by setting the defense step to either TRUE or FALSE. It is also possible to assign a Bernoulli distribution to govern the activation.
E Existence It is used when the existence of connected/associated assets, given by <-, must be checked. The specified attack steps are reachable when at least one designated asset exist.
!E Non-existence Same as Existence, except the specified attack steps are reachable when at least one designated asset does NOT exist.
<- Require Denotes which associated assets are required by the current expression. Used to specify preconditions for Existence and Non-existence.
[ ] TypeOf Reaching an attack step that traverses a set of assets can now be further specified by child asset if a parent asset has been extended. For example, if Dataflow has children assets Inbound and Outbound, we specify as dataflows[Outbound]
\/ Union Union operations of same-typed sets. (Backslash + forward slash)
/\ Intersection Intersection operations on same-typed sets. (Forward slash + backslash)
- Difference Difference operations on same-typed sets.
X.A Collect operator Attack step A of the asset X is referenced. Other asset X is referenced to reach the attack step A.
X*.A Transitive operator Recursively follow X until the end is reached, then, perform A. For example, folder.subFolder.subFolder.subFolder.access = folder.subFolder*.access. Hanging transition are allowed for let expression, e.g., let allFolders = folders.subFolder*.
X().A Let substitution The parentheses after X denote that it refers to the let expression labeled 'X'. A is performed on the assets given by X.
TTC Time-To-Compromise TTC is a probability distribution reflects an attacker's ability of compromising an asset. Attack steps can have a TTC to execute. the MAL simulations calculate/aggregate TTC for models/scenarios.

Probability Distributions

For detailed information regarding distributions, see Supported distribution functions (MAL compiler).

Attack steps may have probability distributions set to express their uncertainties. MAL has the following to choose from:

  • Bernoulli(p)
  • Binomial(n, p)
  • Exponential(λ)
  • Gamma(k, θ)
  • LogNormal(μ, σ)
  • Pareto(x, α)
  • TruncatedNormal(μ, σ^2)
  • Uniform(a, b)

MAL Coding Standard

MAL specification follows a coding standard similar to Java. Please consider the following coding standard when creating your MAL language.

  • Asset names are PascalCased.
  • Attack steps, defense mechanims, and role names are camelCased.
  • The attack step definition syntax is '<\type> nameHere @tag [ProbabilityDistribution(params)]'. Tags and probability distributions are optional.
  • Multiple expressions can be written under both -> and <- operators. They should be delimited by a comma and line break. Furthermore, the first expression should be on the same line as the operator itself.
  • The defense mechanism definition syntax is '# nameHere @tag [Bernoulli(p)]'. The tags and Bernoulli distribution are optional.
  • Comments in MAL files can be introduced by "//"
  • It is recommended that asset names are not repeated in the attack step
  • Associations are defined at the end of each MAL specification.
  • Inline documentation using the three info elements is preferred over other formats.

MAL Syntax (Legacy Compiler)

This sections documents the syntax for the legacy compiler. The syntax for the foreseeti compiler can be found here.

Language Constituents

The following are commonly used keywords in writing MAL specification:

  • category Similar to a package in Java. A category consists of one or more assets. The category does not bear semantics, it is only there to enable structure for the language developer.

  • asset Similar to a class in Java. An asset could represent physical objects (e.g., a network router) or logical objects (e.g., Credentials, or a Network Service). When the MAL compiler generates the Java code from the MAL specifications, an asset is translated into a java class.

  • abstractAsset Similar to asset, however, it refers to an asset which need not have a class but its properties are inherited by its children. So, in Java generated code, an abstract asset will not have a class, and its properties are included in each child asset.

  • extends Similar to inheritance in Java. A extended asset inherits attack steps and defense steps from its parent asset. Also the asset associations are inherited by its extended assets (e.g. we don't need to create the association for an extended asset X1 to another asset X2, if its parent asset X is already associated with X2.).

  • info This keyword is used in MAL to provide information for users (not MAL writers) about the asset and/or the associated attack step and defense mechanism.

  • rationale This keyword is used by developers of MAL specification. It tells MAL writers why the attack step is used and other related information.

  • association Association is similar to UML association. It is a connection between two assets, and the name of the association is written near the asset. The multiplicity of a composite association can be [1..1], [1..] or [..*] on the composite end.

Basic MAL Example

MAL, like other languages, has its own syntax and alphabet of symbols. Each asset may have attack or defense steps, and can have associations between each other. The MAL symbols can be seen below. The associations between assets follow the structure of UML class diagrams, where associations are bound to types with cardinalities.

category User {
    asset UserAccount {
        | impersonate
            -> compromise
        | compromise 
            -> computer.stealSecret
    }
}
category Hardware {
    asset Computer {
        & compromise
            -> communicate,
                folder.subFolder+.accessFolder
        | communicate
            -> connect
        | authenticate
            -> compromise
        | vulnerability
            -> compromise
        & access
            -> compromise
        | connect
            -> access
        | stealSecret
            -> compromise
        | firewallBypass
            -> firewall.bypass
        E firewall
            <- firewall
            -> access
    }
    asset Firewall {
        & bypass [probability]
            -> computer.access
        # protected
            -> bypass
    }
    asset Folder {
        | accessFolder
            -> findCredentials
        | findCredentials
    }
}

associations {
    Computer [studentComputer] 1-* <-- Use --> 1 [student] UserAccount
    Computer [teacherComputer] 1 <-- Use --> 1 [teacher] UserAccount
    Computer [computer] 1-* <-- Use --> 1-* [person] UserAccount
    Computer [computer] * <-- Protect --> 1 [firewall] Firewall
    Computer [computer] * <-- Contains --> * [folder] Folder
    Folder [folder] 1 <-- Contains --> * [subFolder] Folder
}

MAL Symbols

Symbol Meaning Description
--> Leads to The successful compromise of this attack step allows the attacker to consequently compromise further attack steps.
| OR An OR attack step A can be reached if any of the attack steps which refers to A is reached.
& AND An AND attack step A can be reached only if all of the attack steps which refer to A are reached.
# Defense As opposed to attack steps defences are boolean. A defense step represent the countermeasure of an attack step (e.g., passwordBruteForce attack step can be defended by twoFactorAuthentications defense step). While declaring an asset, it is possible to either enable or disable a defense step by setting the defense step to either TRUE or FALSE.
E Existence This symbol is related to $&lt;$-- operator. It is used when the existence of a connected/associated asset must be checked. It acts like a defense but the boolean value is automatically assigned based on the existence of the asset. The example can be seen in Appendix, the firewallProtected attack step under \texttt{Computer} asset.
3 Non-existence It is the same as above but this time a check for non-existence is being made (i.e. when the associated asset does not exist, the boolean value will be true).
[ ] TypeOf Reaching an attack step that traverses a set of assets can now be further specified by child asset if a parent asset has been extended. For example, if Dataflow has children assets Inbound and Outbound, we specify as dataflows[Outbound]
\/ Union Union operations of same-typed sets.
/\ Intersection Intersection operations on same-typed sets.
X.A Collect operator Attack step A of the asset X is referenced. Other asset X is referenced to reach the attack step A.
X+.A Transitive operator It can be used to refer many attack steps without writing all of them.
+> Add operator This happens to the child asset. One attack step (e.g. access) under the child asset leads to its specific attack steps besides its corresponding attack step of the parent asset.
TTC Time-To-Compromise TTC is a probability distribution reflects an attacker's ability of compromising an asset. Attack steps can have a TTC to execute. the MAL simulations calculate/aggregate TTC for models/scenarios.

Probability Distributions

Some attack steps have probability distributions to express their uncertainties. MAL has four probability distributions to choose from:

  • ExponentialDistribution
  • UniformDistribution
  • GammaDistribution
  • BernoulliDistribution

MAL Coding Standard

MAL specification follows a coding standard similar to Java. Please consider the following coding standard when creating your MAL language.

  • Asset names start with a capital letter
  • Attack steps are following camelCase
  • Role names are lower case
  • Comments in MAL files can be introduced by "//"
  • It is recommended that asset names are not repeated in the attack step