Skip to main content
Version: Current

Write a Scenario

This guide walks through the process of creating a CRADLE scenario from an empty .cradle file.

A scenario typically defines:

  • scenario metadata
  • instances
  • networks
  • external objects
  • lifecycle phases
  • events

The exact components required depend on the environment you want to describe.

Create the source file​

Create a new file with the .cradle extension.

For example:

ExampleEnvironment.cradle

You can organize scenarios under a dedicated directory:

scenarios/
└── ExampleEnvironment.cradle

The .cradle file is the authoritative source for the scenario.

Generated YAML and backend-specific files should be treated as outputs rather than as replacements for the source.

Plan the scenario​

Before writing the source, identify the main components.

For example:

ComponentExample
ScenarioExampleEnvironment
ClientClient
RouterRouter
Networklan_0
ObjectInitializationScript
Main eventinitialize_client

A simple topology might be:

The scenario will then connect these components through named references.

Define metadata​

Start with the metadata() block.

For example:

metadata() >
name("ExampleEnvironment"),
eventType("sequence"),
repositoryRemote("https://172.18.178.10:4443"),
object("InitializationScript").

This:

  • names the scenario
  • selects the event model
  • defines the remote artifact repository
  • declares the object used by the scenario

Scenario name​

Use:

name("ExampleEnvironment")

Choose a stable name that identifies the scenario.

Event model​

Use:

eventType("sequence")

for sequential event handling.

The supported event type is:

sequence

Artifact repository​

Use:

repositoryRemote("https://172.18.178.10:4443")

to define the remote artifact base location.

important

Do not embed usernames, passwords, access tokens or other secrets directly in repository URLs.

Object declarations​

Declare objects used by the scenario:

object("InitializationScript")

Each declared object should later have a matching object("name") definition.

For detailed metadata behavior, see Metadata.

Declare instances​

Use instances() to declare the systems participating in the scenario.

For example:

instances() >
instance("Client"),
instance("Router").

This introduces:

  • Client
  • Router
important

Each declared instance should have a corresponding definition.

Define the instances​

Define each instance separately.

For example:

instance("Client") >
os("ubuntu", "20.04"),
object("InitializationScript"),
description("Example client system").

Then define the router:

instance("Router") >
os("ubuntu", "20.04"),
config("linux-router").

The relationship is:

Operating system​

Use:

os("ubuntu", "20.04")

to describe the instance operating system.

Configurations​

Configurations can be associated with instances using:

config("linux-router")

For example:

instance("Router") >
os("ubuntu", "20.04"),
config("linux-router").

Configuration availability depends on the environment and backend being used.

Objects​

Associate an object with an instance using:

object("InitializationScript")

For example:

instance("Client") >
os("ubuntu", "20.04"),
object("InitializationScript").

The object name should match the declaration in metadata().

For detailed instance behavior, see Instances and Roles.

Declare networks​

Declare the scenario networks through:

networks() >
network("lan_0").

Each declared network should have a corresponding definition.

Define the network​

For example:

network("lan_0") >
subnet("192.168.10.0/24"),
endpoint("Client", "DHCP"),
endpoint("Router", "192.168.10.1").

This defines:

  • the 192.168.10.0/24 subnet
  • a DHCP endpoint for Client
  • a static endpoint for Router

The endpoint names reference the instances declared earlier.

Conceptually:

important

Endpoint names must match declared instance names exactly.

Let the backend resolve network details​

The subnet can be omitted where supported by the compiler and selected backend.

The Hello World example uses:

network("lan_0") >
endpoint("HelloWorld", "DHCP"),
endpoint("router", "DHCP").

This leaves additional network details to the target-specific generation path.

For more information, see Networks and Routing.

Plan the object definition​

Objects describe external artifacts used by the scenario.

For example:

object("InitializationScript") >
location("${uriRemote}/scripts/initialize.sh").

In the complete source, place this final object definition after the event declarations and definitions. The object name is introduced earlier through metadata() and its instance association.

${uriRemote} references the remote artifact repository defined by:

repositoryRemote("https://172.18.178.10:4443")

Conceptually:

The object name should remain consistent across:

  • metadata()
  • instance associations
  • event references
  • the object definition

For detailed object behavior, see Objects and Interpolation.

Define the event lifecycle​

Declare the three event lifecycle phases:

events() >
preEvent(),
mainEvent(),
postEvent().

The lifecycle is:

1
preEventPreparation before the primary scenario.
↓
2
mainEventPrimary scenario behavior.
↓
3
postEventFollow-up or evidence-related activity.

Add an event to a phase​

For this example, add one event to the main phase:

mainEvent() >
event("initialize_client").

The event name is initialize_client. It should have a matching event definition.

Define the event​

Define event initialize_client:

event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", ""),
pauseBeforeRun(0),
pauseAfterRun(0),
scheduleExecution("2026-01-15T10:00:00+00:00"),
description("Initialize the example client").

This event:

  • references the Client instance
  • does not request root privileges
  • uses bash
  • runs the InitializationScript object
  • has no delay before execution
  • has no delay after execution
  • does not wait for another event
  • has a scheduled execution time
  • includes a description

Add pre-event and post-event behavior​

If the scenario requires preparation or follow-up behavior, declare additional events.

For example:

preEvent() >
event("initialize_client").

mainEvent() >
event("collect_evidence").

postEvent() >
event("cleanup").

Each event then receives its own definition:

event("initialize_client") >
...

event("collect_evidence") >
...

event("cleanup") >
...

Semantic event names should be unique within the scenario.

For more information, see Events and Dependencies.

Add event dependencies​

CRADLE expresses dependencies through dependsOn().

For example:

mainEvent() >
event("initialize_client"),
event("collect_evidence").

The initialization event has no dependency, so it omits dependsOn():

event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", "").

The evidence event can depend on the initialization event:

event("collect_evidence") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("CollectEvidence", ""),
dependsOn("initialize_client").

Conceptually:

1
initialize_client
↓
2
collect_evidence
note

Every dependsOn() value must match a defined semantic event name.

Add heuristic annotations​

Heuristic annotations can provide additional classification metadata.

For example:

event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", ""),
heuristic("ttp", "T1190").

Objects and instances can also use heuristic().

For example:

object("InitializationScript") >
location("${uriRemote}/scripts/initialize.sh"),
heuristic("mbc", "F0002").

Heuristic annotations do not change the underlying behavior of the element.

For more information, see Heuristic Annotations.

Complete example​

The resulting scenario is:

metadata() >
name("ExampleEnvironment"),
eventType("sequence"),
repositoryRemote("https://172.18.178.10:4443"),
object("InitializationScript").

instances() >
instance("Client"),
instance("Router").

instance("Client") >
os("ubuntu", "20.04"),
object("InitializationScript"),
description("Example client system").

instance("Router") >
os("ubuntu", "20.04"),
config("linux-router").

networks() >
network("lan_0").

network("lan_0") >
subnet("192.168.10.0/24"),
endpoint("Client", "DHCP"),
endpoint("Router", "192.168.10.1").

events() >
preEvent(),
mainEvent(),
postEvent().

mainEvent() >
event("initialize_client").

event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", ""),
pauseBeforeRun(0),
pauseAfterRun(0),
scheduleExecution("2026-01-15T10:00:00+00:00"),
description("Initialize the example client").

object("InitializationScript") >
location("${uriRemote}/scripts/initialize.sh").

Follow the references​

The scenario contains several relationships. Read each chain from left to right to trace a name from its declaration to the places where it is used.

Instance references​

Object references​

Network references​

Names should remain consistent throughout these relationships.

important

A spelling or capitalization mismatch can leave a named reference unresolved.

Validate the scenario​

Save the source as:

scenarios/ExampleEnvironment.cradle

Then validate it:

cxc validate -i scenarios/ExampleEnvironment.cradle

If CradleXC reports syntax errors, correct the source and run validation again.

For more information, see Validate and Compile.

Inspect the scenario​

If you want to inspect how CradleXC interpreted the source:

cxc dump-ir -i scenarios/ExampleEnvironment.cradle

This step is optional.

It is mainly useful for inspection and diagnostics.

Compile the scenario​

After validation succeeds:

cxc compile \
-i scenarios/ExampleEnvironment.cradle \
-o ExampleEnvironment.yml

The resulting YAML is compiler-generated output.

Continue editing the .cradle file rather than modifying the YAML as the authoritative scenario definition.

Generate target-specific files​

If you need output for a particular backend, first confirm that the backend is available:

cxc plugin list

Then generate the target-specific files:

cxc build \
-i scenarios/ExampleEnvironment.cradle \
--target <plugin_name> \
-o ./output

The files produced depend on the selected backend.

important

CradleXC does not automatically execute backend-generated files. Use them with the corresponding tools in your own testbed environment.

For backend setup, see Use a Backend.

A practical scenario-authoring workflow is:

  1. Plan the instances, networks, objects and events.
  2. Create the .cradle file.
  3. Define scenario metadata.
  4. Declare and define instances.
  5. Declare and define networks.
  6. Confirm that objects are declared in metadata and associated with the intended instances.
  7. Define the event lifecycle.
  8. Add events and dependencies.
  9. Finish with the object definitions.
  10. Check every named reference.
  11. Validate the scenario.
  12. Inspect the intermediate representation if required.
  13. Compile the scenario.
  14. Generate target-specific files only when required.

At a high level:

AuthorPlan and write the CRADLE scenario.
↓
VerifyCheck references and validate the source.
↓
Inspect and compileInspect the IR if needed, then compile the scenario.
↓
Generate target filesUse a backend plugin only when target-specific output is required.

Authoring checklist​

Before considering a scenario complete, verify the following.

Scenario and objects​

  • metadata() identifies the scenario.
  • All objects used by the scenario are declared.
  • Every object has a matching definition.
  • Object references use consistent names.
  • Repository values do not expose credentials.

Instances and networks​

  • Every instance is declared in instances().
  • Every declared instance has a matching definition.
  • Every network is declared in networks().
  • Every declared network has a matching definition.
  • Network endpoints reference valid instances.

Events and references​

  • Lifecycle phases are declared.
  • Every event belongs to the intended phase.
  • Every event has a matching definition.
  • Event instance references resolve.
  • runObject() references resolve.
  • Dependency references resolve.

Validation​

  • The scenario passes cxc validate.

Keep source and generated files separate​

A useful project layout is:

.
├── scenarios/
│ └── ExampleEnvironment.cradle
├── build/
│ └── ExampleEnvironment.yml
└── output/
└── ...

Here:

DirectoryPurpose
scenarios/Human-authored CRADLE source.
build/CradleXC compiler output.
output/Backend-generated target-specific files.

These directory names are recommendations rather than required CRADLE paths.

Next steps​

After writing your scenario, continue with Validate and Compile to verify the source and generate the compiled representation.