Skip to main content

Policy Reader and Director

Simple Story

Somebody has to read the filled-in checklist and turn it into actual checks.

That is these two parts. One reads the checklist and understands it. The other decides which checks have to be built from what it read. This page explains how they work and why they are separate.

Audience: IT-Education experts with no security background. Scope: All classes inside SecurityPolicyReaderAndDirector.java, the reader and director packages. Ares Version: 2.1.5

Related documentation:


1. Prerequisites

  • Java 17 or later
  • Gradle or Maven 3.8+ for building, with versions compatible with the AspectJ and test plugins used by the project
  • JUnit 5 (Jupiter) for test execution
  • Ares 2

2. Purpose — What Problem Does This Solve?

When students submit programming exercises, instructors need to make sure the submitted code does not perform dangerous operations (e.g. deleting files, opening network connections, executing shell commands). Ares 2 automates this by:

  1. Letting the instructor write a security policy file (currently YAML) that declares which operations are allowed and which are forbidden.
  2. Reading that file into an in-memory data model.
  3. Directing (orchestrating) the automatic generation, writing, and execution of security test cases that enforce the policy.

A security test case is either:

  • An Architecture test that statically analyses student bytecode to check whether it calls forbidden APIs (e.g., java.io.File, Runtime.exec()). These tests use ArchUnit (rule-based) or T. J. Watson Libraries for Analysis (WALA) (call-graph-based) to detect violations without running the student code.
  • An aspect-oriented programming (AOP) test that dynamically intercepts forbidden operations at runtime. These tests configure the Ares agent (ByteBuddy Instrumentation or AspectJ) to block actual I/O calls when the student code executes.

Security test cases are systematically generated from the policy to cover all declared permissions and restrictions.

The classes described in this manual implement steps 2 and 3.


3. Architecture Overview

The architecture follows multiple well-known software design patterns. The table below summarises them for reference, understanding these patterns is not required to use Ares.

Click to expand: Design Pattern Reference
PatternWhere it is usedWhy
Builder PatternSecurityPolicy
SupervisedCode
SecurityPolicyReaderAndDirector
SecurityPolicyYAMLReader
SecurityPolicyJavaDirector
All *Permission records
Ares security policies involve many optional fields (file permissions, network permissions, thread permissions, etc.). The Builder pattern lets instructors configure only the permissions they need, in any order, while guaranteeing that the resulting objects are immutable and fully validated, preventing misconfigured policies from reaching the test-generation stage.
Strategy PatternSecurityPolicyReaderSecurityPolicyYAMLReader
SecurityPolicyDirectorSecurityPolicyJavaDirector
Ares must support different policy file formats, currently YAML and potentially JavaScript Object Notation (JSON) or TOML in the future, and different target programming languages (currently Java, potentially Python or other languages). The Strategy pattern allows each combination to be implemented as an independent, swappable subclass without modifying the core orchestration logic, adhering to the Open/Closed Principle.
Abstract Factory + BuilderSecurityPolicyDirector.createTestCases() returns TestCaseAbstractFactoryAndBuilderAres supports multiple test-generation toolchains, ArchUnit or WALA for architecture tests, and AspectJ or Instrumentation for AOP tests. The Abstract Factory pattern allows the director to produce the correct set of test artefacts for each toolchain combination through a unified interface, so the rest of the system remains agnostic to the specific toolchain in use.
FacadeSecurityPolicyReaderAndDirectorInternally, Ares must select a reader, parse the policy, select a director, configure a factory, generate tests, write them to disk, and execute them. The Facade pattern hides all of this behind a small four-method application programming interface (API) (createTestCases, writeTestCases, writeTestCasesAndContinue, executeTestCases), so that instructors and integration code can enforce security policies with minimal boilerplate.
Fluent API / Method ChainingMethods in SecurityPolicyReaderAndDirector and builder classes return thisAres is designed to be embedded into automated grading pipelines. The Fluent API enables the entire security-enforcement workflow to be expressed as a single, readable method chain (e.g., .createTestCases().writeTestCasesAndContinue(folder).executeTestCases()), reducing integration effort and making the call sequence self-documenting.
Factory MethodselectSecurityPolicyReader(Path)
selectSecurityPolicyDirector(SecurityPolicy)
Ares needs to automatically pick the correct reader and director at runtime, based on the policy file extension and the programming language declared in the policy. These factory methods centralise that selection logic so that callers never need to know which concrete reader or director class to instantiate.
Dependency Injection & Null-SafetyConstructor injection in SecurityPolicyDirector and subclasses
@Nonnull and @Nullable annotations throughout
The director depends on five collaborators (Creator, Writer, Executer, EssentialDataReader, ProjectScanner) that can vary across toolchains. Injecting them via the constructor makes each dependency explicit, allows unit tests to substitute mock implementations, and enables compile-time null-safety checks to prevent runtime crashes during security enforcement.
Immutable Value Objects (Java Records)All permission types in the policySubComponents package are records (ProgrammingLanguageConfiguration is an enum)
Key data containers like ResourceAccesses
Security policies must not be accidentally modified after parsing, a mutated permission list could silently weaken security enforcement. Java Records guarantee immutability, provide automatic equals(), hashCode(), and toString(), and make the policy data model inherently thread-safe and self-documenting.

See the accompanying draw.io diagrams for visual class and sequence diagrams:


4. The User Input, Security Policy YAML File

The educator provides a YAML file such as SecurityConfiguration.yaml. Its structure maps 1-to-1 to the Java data model. Example:

thisPolicyFileCompliesToThePolicyVersion: 1
regardingTheSupervisedCode:
theFollowingProgrammingLanguageConfigurationIsUsed: JAVA_USING_GRADLE_ARCHUNIT_AND_INSTRUMENTATION
theSupervisedCodeUsesTheFollowingPackage: anonymous
theMainClassInsideThisPackageIs: "ReproducibilityCli"
theFollowingClassesAreTestClasses: []
theFollowingResourceAccessesArePermitted:
regardingFileSystemInteractions: []
regardingNetworkConnections: []
regardingCommandExecutions: []
regardingThreadCreations: []
regardingPackageImports: []
regardingTimeouts:
- timeout: 3000

The field names are deliberately chosen to read like English sentences (e.g. regardingTheSupervisedCode.theFollowingResourceAccessesArePermitted.regardingFileSystemInteractions), making the configuration self-documenting.


5. Reading the Policy — The reader Package

The reader is responsible for loading the YAML policy file and turning it into a SecurityPolicy Java object. The reader is selected at runtime based on the file extension — for .yaml / .yml files this is SecurityPolicyYAMLReader. If you add a new file format in the future, you only implement readSecurityPolicyFrom and add a new branch in the factory method.

5.1. SecurityPolicyReader (Abstract Class)

AspectDetail
RoleDefines a strategy interface for reading SecurityPolicy objects from any file format. It is designed as an abstract class so that different concrete readers (currently SecurityPolicyYAMLReader, potentially SecurityPolicyJSONReader in the future) can implement file-format-specific parsing logic.
PatternStrategy Pattern. This abstract class with concrete subclasses allows runtime selection of the appropriate reader based on file extension.
Key dependenciesNone. This abstract class has no injected dependencies of its own. Its single attribute ObjectMapper objectMapper is set by concrete subclasses (e.g., SecurityPolicyYAMLReader provides a YAMLMapper).
Key attributeObjectMapper objectMapper, Jackson's polymorphic serialisation engine, used by concrete subclasses to deserialise file content into SecurityPolicy Java objects.
Key methodsreadSecurityPolicyFrom(Path) is the main abstract method that concrete subclasses implement to read and parse a policy file, returning a fully populated SecurityPolicy object. The static factory method selectSecurityPolicyReader(Path) inspects the file extension and returns the matching concrete reader instance (currently .yaml or .ymlSecurityPolicyYAMLReader; other extensions throw an exception). This design allows new file formats to be added simply by implementing readSecurityPolicyFrom and adding a new case branch in selectSecurityPolicyReader.

This design makes it trivial to add, say, a SecurityPolicyJSONReader in the future: implement readSecurityPolicyFrom, then add a case "json" branch in selectSecurityPolicyReader.

5.2. SecurityPolicyYAMLReader (Concrete Class)

AspectDetail
ExtendsExtends the abstract SecurityPolicyReader class, inheriting its strategy pattern interface and objectMapper attribute.
LibraryUses Jackson's YAML library (YAMLMapper) to perform polymorphic deserialisation of YAML policy files into strongly typed Java objects. Jackson handles the mapping from untyped YAML text to the SecurityPolicy record structure.
ConstructionUses the Builder pattern via a fluent API (e.g., yamlBuilder().yamlMapper(…).build()), allowing flexible configuration of the reader's Jackson serialiser at construction time.

What it does step by step:

  1. Receives the file system Path to the YAML policy file.
  2. Reads the raw file content via FileTools.readFile(path).
  3. Deserialises the content into a SecurityPolicy Java object via FileTools.readYamlFile(…, SecurityPolicy.class).
  4. Returns the fully populated, validated, immutable SecurityPolicy instance.

6. Directing Test-Case Creation — The director Package

The director receives the parsed SecurityPolicy and orchestrates the multi-step process of generating, writing, and executing security tests. Think of the reader as fetching the instructions and the director as carrying them out: the reader says "here is what the policy contains," and the director decides which test toolchain to build and how to configure it.

6.1. SecurityPolicyDirector (Abstract Class)

AspectDetail
RoleThis abstract class orchestrates (directs) the entire workflow for creating security test cases from a parsed SecurityPolicy. It is designed as a Strategy, with subclasses per target programming language, so that each language-specific director can customise the test-generation pipeline (build mode, architecture-checking mode, AOP mode) while the overall orchestration remains language-agnostic.
PatternStrategy Pattern (one concrete subclass per target language) combined with the Director pattern (orchestrating a multi-step workflow). The class acts as a builder-like component in the SecurityPolicyReaderAndDirector facade.
Key dependenciesFive collaborator interfaces are injected via the constructor (Creator, Writer, Executer, EssentialDataReader, ProjectScanner), each representing a distinct responsibility (see tables below). Two file-system paths (essentialPackagesPath, essentialClassesPath) point to the essential-data YAML files. This separation of concerns allows each collaborator to be tested and extended independently.
Key attributesCreator creator, generates test-case artefacts. Writer writer, serialises artefacts to disk. Executer executer, configures the runtime agent and runs the generated tests in-process. EssentialDataReader essentialDataReader, reads essential packages/classes files (the YAML implementation is EssentialDataYAMLReader). ProjectScanner projectScanner, discovers student project structure. Path essentialPackagesPath and Path essentialClassesPath, paths to essential-data YAML files (see defaults below).
Key methodscreateTestCases(SecurityPolicy, Path) is the main abstract method that all concrete directors must implement. It receives the parsed policy and the student project path, and returns a TestCaseAbstractFactoryAndBuilder that is ready to generate, write, and execute test cases. The static factory method selectSecurityPolicyDirector(SecurityPolicy) reads the ProgrammingLanguageConfiguration enum from the policy and instantiates the matching director implementation (currently always SecurityPolicyJavaDirector, but the pattern allows future SecurityPolicyPythonDirector or other language directors to be added).

Collaborator Interfaces (injected into SecurityPolicyDirector):

InterfaceResponsibility
CreatorGenerates test-case source-code artefacts (architecture checks and AOP configuration) from the parsed policy.
WriterSerialises the generated artefacts to disk in the appropriate test folder.
ExecuterExecutes the generated test cases in-process: it writes the advice settings (modes, allowed packages/classes, restricted package, main class) into the agent configuration, then runs the architecture test cases and finally the AOP test cases. No compilation and no build tool invocation takes place.
EssentialDataReaderReads files that list packages and classes Ares itself needs at runtime (e.g. JUnit, ByteBuddy). These are always allowed regardless of the policy. The concrete implementation for YAML files is EssentialDataYAMLReader.
ProjectScannerScans the student project directory to discover its package structure and locate compiled bytecode.

Essential-data file paths (defaults):

ConstantResolves toContent
DEFAULT_ESSENTIAL_PACKAGES_PATH<Ares code-source location>/de/tum/cit/ase/ares/api/configuration/essentialFiles/java/EssentialPackages.yamlPackages that Ares and its dependencies need, always permitted.
DEFAULT_ESSENTIAL_CLASSES_PATH<Ares code-source location>/de/tum/cit/ase/ares/api/configuration/essentialFiles/java/EssentialClasses.yamlIndividual classes that Ares needs, always permitted.

Both defaults are resolved by FileTools.resolveFileOnSourceDirectory(…), which takes the directory containing the Ares classes (the code-source location, e.g. the classes directory or the extracted JAR location) as the base and appends de/tum/cit/ase/ares/api before the relative path shown above. They are therefore not relative to the student project.

These files are read by the EssentialDataYAMLReader collaborator during test-case creation so that Ares's own framework classes are never accidentally blocked by the student's security policy.

This design makes it trivial to add, say, a SecurityPolicyPythonDirector in the future: extend SecurityPolicyDirector, implement createTestCases, add a new enum value to ProgrammingLanguageConfiguration, then add a case branch in selectSecurityPolicyDirector.

6.2. SecurityPolicyJavaDirector (Concrete Class)

AspectDetail
ExtendsExtends the abstract SecurityPolicyDirector class, inheriting its abstract createTestCases method and static factory selectSecurityPolicyDirector, along with all five injected collaborators.
LibraryDepends on the five injected collaborators: Creator (JavaCreator) generates the architecture and AOP test cases from the policy, Writer (JavaWriter) uses file I/O utilities to serialise them, Executer (JavaExecuter) writes the advice settings into the agent configuration and runs the test cases in-process (no build tool, no compilation), EssentialDataReader (EssentialDataYAMLReader) uses Jackson YAML for parsing, and ProjectScanner (JavaProjectScanner) scans the project's source files with regular expressions to discover package name, main class, and test classes. The specific toolchain libraries (ArchUnit, WALA, AspectJ, Instrumentation agent) are loaded based on the ProgrammingLanguageConfiguration in the policy.
ConstructionProvides a static javaBuilder() method that returns a fluent Builder with seven mandatory parameters: the five collaborators (Creator, Writer, Executer, EssentialDataReader, ProjectScanner) plus the two file paths to the essential packages and classes YAML files. This forces callers to provide all dependencies upfront, reducing the risk of misconfiguration.

What it does step by step:

  1. Receives the parsed SecurityPolicy object (containing all resource access permissions and restrictions) and the project folder Path (where the student code lives).
  2. Extracts the ProgrammingLanguageConfiguration enum from the policy. This enum encodes eight combinations: Java × {Maven, Gradle} × {ArchUnit, WALA} × {AspectJ, Instrumentation}.
  3. Maps the configuration to three mode enums that control test generation: BuildMode (MAVEN or GRADLE, determines the build tool), ArchitectureMode (ARCHUNIT or WALA, determines the static analysis framework), and AOPMode (ASPECTJ or INSTRUMENTATION, determines the runtime enforcement mechanism).
  4. Calls the internal method generateFactoryAndBuilder(…), which constructs a JavaTestCaseFactoryAndBuilder via its builder (JavaTestCaseFactoryAndBuilder.builder()), passing the five collaborators (Creator, Writer, Executer, EssentialDataReader, ProjectScanner), the two essential-data file paths, the three mode selections, the security policy, and the project path. This factory-builder is specialised for the exact Java toolchain combination specified in the configuration.
  5. Returns the TestCaseAbstractFactoryAndBuilder instance, which is now ready for the caller to invoke create (generate test cases according to the selected toolchain), write (save them to disk in the appropriate test folder), and execute (run the architecture checks and configure the runtime agent).

What is a TestCaseAbstractFactoryAndBuilder? It is a combined Abstract Factory and Builder that produces two kinds of test artefacts: (1) ArchitectureTestCase objects for static analysis and (2) AOPTestCase objects that configure the runtime interception agent. The "execute" step runs the architecture tests immediately and writes the allowed-resource lists into the agent's configuration so that the agent can enforce them when the student code runs.


7. Orchestration — SecurityPolicyReaderAndDirector

This is the entry-point class that ties together reading, directing, and security test-case management. It is the main public API that instructors and automated grading systems interact with. The class implements two important roles:

  • A Facade, exposing a small public interface (createTestCases, writeTestCases, writeTestCasesAndContinue, executeTestCases) that hides the complexity of reader selection, policy parsing, director selection, and factory configuration.
  • A Client of the Abstract Factory Pattern, consuming the TestCaseAbstractFactoryAndBuilder that the director creates, and delegating each workflow step to the factory.

The class uses the Builder pattern for construction and the Fluent API for the workflow steps, allowing the entire security-enforcement process to be expressed as a single, method-chained expression.

7.1. Construction

Instructors instantiate this class via its builder:

SecurityPolicyReaderAndDirector.builder()
.securityPolicyFilePath(Path.of("SecurityConfiguration.yaml"))
.projectFolderPath(Path.of("/path/to/student/project"))
.build();

The builder accepts two optional paths (both are @Nullable, and build() performs no null checks):

  • securityPolicyFilePath: The file system path to the YAML security policy file (e.g., SecurityConfiguration.yaml) that the instructor has written. If this is null, no policy file is read and Ares falls back to the default most-restricted enforcement (see Section 7.3).
  • projectFolderPath: The file system path to the student's project folder, where the code to be verified lives. If this is null, the project-dependent steps operate without a project base path.

The resulting instance is immutable and ready to orchestrate the three-step workflow.

7.2. Three-Step Workflow

The public API consists of three methods that orchestrate the security-enforcement pipeline:

StepMethodWhat happens internally
1. CreatecreateTestCases()Detects the policy file format (currently YAML) and selects the appropriate SecurityPolicyReader → reads and parses the policy file into a SecurityPolicy object → reads the ProgrammingLanguageConfiguration enum and selects the appropriate SecurityPolicyDirector (currently SecurityPolicyJavaDirector) → the director constructs and returns a TestCaseAbstractFactoryAndBuilder configured for the exact toolchain combination specified. This step validates the policy and prepares the factory for test generation.
2. WritewriteTestCases(testFolderPath) or writeTestCasesAndContinue(testFolderPath)Delegates to the factory/builder's write method, which invokes the injected Writer collaborator to serialise the generated test-case source code artefacts to disk in the specified test folder. writeTestCases returns a List<Path> of written files and ends the chain. writeTestCasesAndContinue returns this to allow further chaining.
3. ExecuteexecuteTestCases()Delegates to the factory/builder's execute method, which invokes the injected Executer collaborator. No compilation and no build tool invocation takes place: the executer writes the advice settings (modes, allowed packages/classes, restricted package, main class) into the agent configuration, then runs the architecture test cases (ArchUnit or WALA) in-process and finally the AOP test cases (AspectJ or Instrumentation) to enforce the declared permissions. Returns this for chaining.

The chainable methods (createTestCases, writeTestCasesAndContinue, executeTestCases) return this (the SecurityPolicyReaderAndDirector instance), enabling method chaining via the Fluent API. Note that writeTestCases returns a List<Path> instead and ends the chain:

SecurityPolicyReaderAndDirector.builder()
.securityPolicyFilePath(policyPath)
.projectFolderPath(projectPath)
.build()
.createTestCases()
.writeTestCasesAndContinue(testFolder)
.executeTestCases();

This chained call reads as a sequence of actions: create → write → execute, making the workflow both self-documenting and resistant to accidental operation reordering.

7.3. Null-Safety Strategy

The class uses a plain null-and-empty check (if (securityPolicyFilePath != null && !securityPolicyFilePath.toString().isEmpty())) to handle the case where no policy file path is provided. If securityPolicyFilePath is null (or empty), only the reader step is skipped:

  • No reader is selected and no policy file is read; the parsed policy remains null.
  • SecurityPolicyDirector.selectSecurityPolicyDirector(null) is still called and returns the default SecurityPolicyJavaDirector.
  • The director's createTestCases(null, …) first calls ProjectSourcesFinder.discover(root, null), so the build mode is discovered from the project rather than defaulted, and it then builds a factory with ArchitectureMode.ARCHUNIT and AOPMode.ASPECTJ.
  • Discovery can fail, and it runs before any enforcement is configured. With no policy there is no explicitly selected build mode, so a project root containing both a pom.xml and a Gradle descriptor is rejected as ambiguous, and one containing neither is rejected as unsupported. Both throw an IllegalStateException out of discover, which pre-empts everything below: no factory is built and the restrictive fallback is never reached. The remaining bullets therefore describe what happens after discovery succeeds.
  • The factory then falls back to ResourceAccesses.createRestrictive(): file system, network, command execution and thread creation are denied outright, and package imports are restricted to an implicit allowlist (the essential packages, which include the java prefix, plus the supervised package and the test-class packages). A 10-second limit is constructed as well, but it becomes a Phobos test case, and executeTestCases() does not dispatch the Phobos family yet, so no execution timeout applies today; use @StrictTimeout where a deadline is required.
  • The supervised package, main class, and test classes are derived by scanning the project instead of the policy.

In other words, a missing policy path does not disable enforcement. Provided the project is discoverable, it results in the most restrictive default enforcement, so that a forgotten or misconfigured policy path fails closed rather than open. An undiscoverable project fails earlier and louder, which is closed too.


8. Processing Pipeline (Overview)

The following diagram shows the end-to-end flow from a YAML policy file to enforced security tests:

┌─────────────────────┐
│ SecurityPolicy.yaml│ ← instructor writes this
└─────────┬───────────┘
│ selectSecurityPolicyReader(path)

┌─────────────────────┐
│ SecurityPolicyYAML │ ← reads & deserialises YAML
│ Reader │
└─────────┬───────────┘
│ returns SecurityPolicy record

┌─────────────────────┐
│ SecurityPolicyJava │ ← selects build/arch/AOP modes
│ Director │
└─────────┬───────────┘
│ returns TestCaseAbstractFactoryAndBuilder

┌─────────────────────┐
│ TestCaseAbstract │ ← creates ArchitectureTestCase
│ FactoryAndBuilder │ + AOPTestCase artefacts
└─────────┬───────────┘
│ .create() → .write(folder) → .execute()

┌─────────────────────┐
│ Architecture checks │ ← static: ArchUnit / WALA
│ + Agent config │ ← dynamic: AspectJ / Instrumentation
└─────────────────────┘

9. End-to-End Example

1. Instructor writes SecurityConfiguration.yaml:

thisPolicyFileCompliesToThePolicyVersion: 1
regardingTheSupervisedCode:
theFollowingProgrammingLanguageConfigurationIsUsed: JAVA_USING_GRADLE_ARCHUNIT_AND_INSTRUMENTATION
theSupervisedCodeUsesTheFollowingPackage: com.student
theMainClassInsideThisPackageIs: "Main"
theFollowingClassesAreTestClasses: []
theFollowingResourceAccessesArePermitted:
regardingFileSystemInteractions: []
regardingNetworkConnections: []
regardingCommandExecutions: []
regardingThreadCreations: []
regardingPackageImports: []
regardingTimeouts:
- timeout: 5000

2. Instructor writes a JUnit test:

import de.tum.cit.ase.ares.api.Policy;
import de.tum.cit.ase.ares.api.jupiter.PublicTest;

class SecurityTest {
// @PublicTest, not a plain @Test: the Ares test annotation is what registers
// JupiterSecurityExtension. @Policy carries no @ExtendWith and activates nothing
// on its own, so a plain @Test would run entirely unsupervised.
@PublicTest
@Policy(value = "SecurityConfiguration.yaml",
withinPath = "classes/java/main/com/student")
void studentCodeMustNotAccessFileSystem() {
// Call the student's code, if it tries to read/write files,
// Ares will throw a SecurityException.
com.student.Main.main(new String[]{});
}
}

3. What happens at runtime:

  1. JUnit runs the test → Ares creates a SecurityPolicyReaderAndDirector (enforcement is on by default; the @Policy annotation supplies the policy path and bytecode scope).
  2. Reader parses the YAML → produces SecurityPolicy record.
  3. Director selects Gradle + ArchUnit + Instrumentation modes.
  4. Factory creates architecture test cases → executed immediately → if com.student.Main imports java.io.File, the test fails.
  5. Factory configures the ByteBuddy agent → when Main.main() runs, any File.read() call is intercepted and throws SecurityException.

10. Integration with JUnit Extensions

In practice, instructors do not call SecurityPolicyReaderAndDirector directly. Instead, they annotate test methods with @Policy (see Security Policy Manual), and Ares’s JUnit extension handles the rest:

  1. Before each test method, JupiterSecurityExtension (or JqwikSecurityExtension) checks for a @Policy annotation on the test method or test class.
  2. It resets all previous security settings so that tests are isolated from each other.
  3. Enforcement is on by default: both extensions evaluate the activated field and skip enforcement only for @Policy(activated = false). JupiterSecurityExtension enforces whenever Ares is activated and a @Policy annotation or a test method is present; JqwikSecurityExtension enforces for every activated property. With @Policy present, the annotation's value (policy path) and withinPath (bytecode scope) are used; without @Policy, a SecurityPolicyReaderAndDirector is still created with a null policy path, which triggers the default most-restricted enforcement (see Section 7.3).
  4. It calls createTestCases().executeTestCases(), this runs the architecture checks and configures the runtime agent.
  5. The test method executes. Any forbidden I/O call is intercepted by the agent and throws a SecurityException.
  6. After each test method, the settings are reset again in a finally block.

11. Troubleshooting

ProblemPossible CauseSolution
IllegalArgumentException on test startup mentioning unsupported file formatPolicy file does not have .yaml or .yml extensionRename the file to use .yaml or .yml
SecurityException, YAML parse error (caused by StreamReadException)Malformed YAML syntax (wrong indentation, tabs, missing colons)Validate the YAML file with a linter; use spaces only
SecurityException, cannot deserialise policy (caused by DatabindException)YAML field names or types do not match the SecurityPolicy record schemaCheck field names against the Security Policy Manual
SecurityException, file not found or unreadableThe path in @Policy(value = "...") does not point to an existing file. JupiterSecurityExtension rejects a non-existent path already when reading the annotation; a read failure inside the YAML reader (caused by IOException) is likewise wrapped in a SecurityExceptionVerify the path is correct and relative to the project root
Tests fail with denied resource accesses although no policy was setsecurityPolicyFilePath is null, so the default most-restricted enforcement applies (Section 7.3): file, network, command and thread accesses are denied, and package imports are restricted to the implicit allowlistSet the policy path to a policy that permits the required accesses, or use @Policy(activated = false) to deactivate Ares for the test
IllegalStateException, Ambiguous project: both Maven and Gradle descriptors are activeNo policy was set, so no build mode is explicitly selected, and the project root carries both a pom.xml and a Gradle descriptor. Discovery fails before the restrictive fallback of Section 7.3 is reachedRemove the descriptor you do not use, or supply a policy whose configuration names the build tool
IllegalStateException, Unsupported project: no pom.xml, build.gradle or build.gradle.ktsThe project root carries no supported build descriptor, so discovery cannot determine a build modeRun against the project root that holds the build descriptor, or pass the correct projectFolderPath
IllegalStateException, Maven was selected but pom.xml is absent (or the Gradle equivalent)A policy names a JAVA_USING_MAVEN_… configuration while the project root has no pom.xml, or the reverseAlign theFollowingProgrammingLanguageConfigurationIsUsed with the build tool the project uses
Architecture tests pass but runtime enforcement is missingAgent JAR not loaded via -javaagentSee the Maven or Gradle walkthrough

12. Glossary

TermMeaning
AOP (Aspect-Oriented Programming)A technique for intercepting method calls at defined points ("pointcuts") without modifying the original code. Ares uses AOP to block forbidden I/O operations at runtime.
ArchUnitA Java library for checking architecture rules on compiled bytecode (e.g., "no class in package X may call class Y").
WALAA static analysis framework that builds inter-procedural call graphs to detect forbidden API usage even through chains of method calls.
AspectJA compile-time AOP framework that weaves interception code directly into bytecode.
Instrumentation (Java Agent)A runtime AOP approach using the java.lang.instrument API and ByteBuddy. A Java agent modifies class bytecode at load time.
ByteBuddyA library for creating and modifying Java classes at runtime, used by Ares to implement the instrumentation agent.
FacadeA design pattern that provides a simplified interface to a complex subsystem.
Strategy PatternA design pattern that defines a family of interchangeable algorithms (here: different readers and directors).

Further reading

This page describes the internals. For the policy file as an instructor writes it, including every supported option, see the Security Policy Manual in the user guide.