Policy Reader and Director
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, thereaderanddirectorpackages. Ares Version: 2.1.5
Related documentation:
- Security Policy Manual, how to write a security policy YAML file
- Precompile or Postcompile, and from there the walkthrough for your build tool
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:
- Letting the instructor write a security policy file (currently YAML) that declares which operations are allowed and which are forbidden.
- Reading that file into an in-memory data model.
- 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
| Pattern | Where it is used | Why |
|---|---|---|
| Builder Pattern | SecurityPolicySupervisedCodeSecurityPolicyReaderAndDirectorSecurityPolicyYAMLReaderSecurityPolicyJavaDirectorAll *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 Pattern | SecurityPolicyReader → SecurityPolicyYAMLReaderSecurityPolicyDirector → SecurityPolicyJavaDirector | 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 + Builder | SecurityPolicyDirector.createTestCases() returns TestCaseAbstractFactoryAndBuilder | Ares 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. |
| Facade | SecurityPolicyReaderAndDirector | Internally, 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 Chaining | Methods in SecurityPolicyReaderAndDirector and builder classes return this | Ares 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 Method | selectSecurityPolicyReader(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-Safety | Constructor 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)
| Aspect | Detail |
|---|---|
| Role | Defines 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. |
| Pattern | Strategy Pattern. This abstract class with concrete subclasses allows runtime selection of the appropriate reader based on file extension. |
| Key dependencies | None. 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 attribute | ObjectMapper objectMapper, Jackson's polymorphic serialisation engine, used by concrete subclasses to deserialise file content into SecurityPolicy Java objects. |
| Key methods | readSecurityPolicyFrom(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 .yml → SecurityPolicyYAMLReader; 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)
| Aspect | Detail |
|---|---|
| Extends | Extends the abstract SecurityPolicyReader class, inheriting its strategy pattern interface and objectMapper attribute. |
| Library | Uses 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. |
| Construction | Uses 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:
- Receives the file system
Pathto the YAML policy file. - Reads the raw file content via
FileTools.readFile(path). - Deserialises the content into a
SecurityPolicyJava object viaFileTools.readYamlFile(…, SecurityPolicy.class). - Returns the fully populated, validated, immutable
SecurityPolicyinstance.
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)
| Aspect | Detail |
|---|---|
| Role | This 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. |
| Pattern | Strategy 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 dependencies | Five 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 attributes | Creator 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 methods | createTestCases(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):
| Interface | Responsibility |
|---|---|
Creator | Generates test-case source-code artefacts (architecture checks and AOP configuration) from the parsed policy. |
Writer | Serialises the generated artefacts to disk in the appropriate test folder. |
Executer | Executes 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. |
EssentialDataReader | Reads 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. |
ProjectScanner | Scans the student project directory to discover its package structure and locate compiled bytecode. |
Essential-data file paths (defaults):
| Constant | Resolves to | Content |
|---|---|---|
| DEFAULT_ESSENTIAL_PACKAGES_PATH | <Ares code-source location>/de/tum/cit/ase/ares/api/configuration/essentialFiles/java/EssentialPackages.yaml | Packages 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.yaml | Individual 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)
| Aspect | Detail |
|---|---|
| Extends | Extends the abstract SecurityPolicyDirector class, inheriting its abstract createTestCases method and static factory selectSecurityPolicyDirector, along with all five injected collaborators. |
| Library | Depends 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. |
| Construction | Provides 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:
- Receives the parsed
SecurityPolicyobject (containing all resource access permissions and restrictions) and the project folderPath(where the student code lives). - Extracts the
ProgrammingLanguageConfigurationenum from the policy. This enum encodes eight combinations: Java × {Maven, Gradle} × {ArchUnit, WALA} × {AspectJ, Instrumentation}. - Maps the configuration to three mode enums that control test generation:
BuildMode(MAVENorGRADLE, determines the build tool),ArchitectureMode(ARCHUNITorWALA, determines the static analysis framework), andAOPMode(ASPECTJorINSTRUMENTATION, determines the runtime enforcement mechanism). - Calls the internal method
generateFactoryAndBuilder(…), which constructs aJavaTestCaseFactoryAndBuildervia 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. - Returns the
TestCaseAbstractFactoryAndBuilderinstance, 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)ArchitectureTestCaseobjects for static analysis and (2)AOPTestCaseobjects 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
TestCaseAbstractFactoryAndBuilderthat 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 isnull, 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 isnull, 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:
| Step | Method | What happens internally |
|---|---|---|
| 1. Create | createTestCases() | 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. Write | writeTestCases(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. Execute | executeTestCases() | 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 defaultSecurityPolicyJavaDirector.- The director's
createTestCases(null, …)first callsProjectSourcesFinder.discover(root, null), so the build mode is discovered from the project rather than defaulted, and it then builds a factory withArchitectureMode.ARCHUNITandAOPMode.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.xmland a Gradle descriptor is rejected as ambiguous, and one containing neither is rejected as unsupported. Both throw anIllegalStateExceptionout ofdiscover, 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 thejavaprefix, plus the supervised package and the test-class packages). A 10-second limit is constructed as well, but it becomes a Phobos test case, andexecuteTestCases()does not dispatch the Phobos family yet, so no execution timeout applies today; use@StrictTimeoutwhere 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:
- JUnit runs the test → Ares creates a
SecurityPolicyReaderAndDirector(enforcement is on by default; the@Policyannotation supplies the policy path and bytecode scope). - Reader parses the YAML → produces
SecurityPolicyrecord. - Director selects Gradle + ArchUnit + Instrumentation modes.
- Factory creates architecture test cases → executed immediately → if
com.student.Mainimportsjava.io.File, the test fails. - Factory configures the ByteBuddy agent → when
Main.main()runs, anyFile.read()call is intercepted and throwsSecurityException.
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:
- Before each test method,
JupiterSecurityExtension(orJqwikSecurityExtension) checks for a@Policyannotation on the test method or test class. - It resets all previous security settings so that tests are isolated from each other.
- Enforcement is on by default: both extensions evaluate the
activatedfield and skip enforcement only for@Policy(activated = false).JupiterSecurityExtensionenforces whenever Ares is activated and a@Policyannotation or a test method is present;JqwikSecurityExtensionenforces for every activated property. With@Policypresent, the annotation'svalue(policy path) andwithinPath(bytecode scope) are used; without@Policy, aSecurityPolicyReaderAndDirectoris still created with anullpolicy path, which triggers the default most-restricted enforcement (see Section 7.3). - It calls
createTestCases().executeTestCases(), this runs the architecture checks and configures the runtime agent. - The test method executes. Any forbidden I/O call is intercepted by the agent and throws a
SecurityException. - After each test method, the settings are reset again in a
finallyblock.
11. Troubleshooting
| Problem | Possible Cause | Solution |
|---|---|---|
IllegalArgumentException on test startup mentioning unsupported file format | Policy file does not have .yaml or .yml extension | Rename 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 schema | Check field names against the Security Policy Manual |
SecurityException, file not found or unreadable | The 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 SecurityException | Verify the path is correct and relative to the project root |
| Tests fail with denied resource accesses although no policy was set | securityPolicyFilePath 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 allowlist | Set 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 active | No 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 reached | Remove 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.kts | The project root carries no supported build descriptor, so discovery cannot determine a build mode | Run 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 reverse | Align theFollowingProgrammingLanguageConfigurationIsUsed with the build tool the project uses |
| Architecture tests pass but runtime enforcement is missing | Agent JAR not loaded via -javaagent | See the Maven or Gradle walkthrough |
12. Glossary
| Term | Meaning |
|---|---|
| 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. |
| ArchUnit | A Java library for checking architecture rules on compiled bytecode (e.g., "no class in package X may call class Y"). |
| WALA | A static analysis framework that builds inter-procedural call graphs to detect forbidden API usage even through chains of method calls. |
| AspectJ | A 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. |
| ByteBuddy | A library for creating and modifying Java classes at runtime, used by Ares to implement the instrumentation agent. |
| Facade | A design pattern that provides a simplified interface to a complex subsystem. |
| Strategy Pattern | A 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.