In this post

Three years ago I wrote an introduction to the STIX2 Python library.

Since then, not a lot has changed.

What has changed, at least for us, is the kind of metadata we want to carry through our intelligence pipelines.

Recently, we have been exploring Admiralty Codes.

This post explains:

  • what Admiralty Codes are and why they matter in CTI workflows
  • where existing STIX handling falls short
  • how to represent Admiralty source reliability and information credibility in a reusable, standards-aligned way
  • how to adopt the resulting objects in your own tooling

The short version is simple:

if you want analysts and downstream systems to filter, query, and operationalise intelligence by Admiralty score, you need something more explicit than a confidence value alone.

That is the problem we set out to solve.


Why Admiralty Codes matter

The Admiralty System, NATO AJP-2.1, is a structured framework used to evaluate the quality of your sources and the information they provide.

Freddy Murstad has a great write-up of the Admiralty System here.

In short, Admiralty Codes are a shorthand way to rate the reliability of intelligence using two dimensions:

  • source reliability
  • information credibility

Admiralty Code CTI

The letter rates the source: for example, A means completely reliable, B usually reliable, C fairly reliable, and lower letters mean less reliable or unknown.

The number rates the information itself: 1 means confirmed by other sources, 2 probably true, 3 possibly true, and lower numbers mean doubtful or impossible to judge.

So an A1 indicator or report is highly trusted because both the source and the information are strong, while F6 means the source cannot be judged and the information cannot be verified.

These codes help analysts express confidence quickly without rewriting the same rationale every time.

They are also operationally useful. A code is not just something to display in a report. It is something teams may want to search, sort, filter, and automate against.


The problem in STIX today

The problem we faced was straightforward: there is no simple, standard way to represent Admiralty Codes in STIX, which is how we generate and move intelligence.

The STIX2 Python library does include functions to convert information credibility, the number part of the Admiralty Code, into STIX confidence values.

Admiralty Credibility STIX Confidence Value
6 - Truth cannot be judged Not present
5 - Improbable 10
4 - Doubtful 30
3 - Possibly True 50
2 - Probably True 70
1 - Confirmed by other sources 90

Here is an example turning an information credibility of 1 into a confidence score, 90 in this example, assigned to an Indicator:

from stix2 import Indicator
from stix2.confidence.scales import admiralty_credibility_to_value

confidence = admiralty_credibility_to_value("1 - Confirmed by other sources")

indicator = Indicator(
    name="Example malicious domain",
    pattern="[domain-name:value = 'evil.example']",
    pattern_type="stix",
    confidence=confidence
)

print(indicator.serialize(pretty=True))
{
    "type": "indicator",
    "spec_version": "2.1",
    "id": "indicator--1ff058d6-1864-45af-bcbb-8426a3a6adfa",
    "created": "2026-05-14T09:42:27.138Z",
    "modified": "2026-05-14T09:42:27.138Z",
    "name": "Example malicious domain",
    "pattern": "[domain-name:value = 'evil.example']",
    "pattern_type": "stix",
    "pattern_version": "2.1",
    "valid_from": "2026-05-14T09:42:27.138Z",
    "confidence": 90
}

That is useful, but it only captures half of the model.

It ignores the source reliability, the letter part of the Admiralty Code.

For teams using Admiralty Codes operationally, that is a real limitation. confidence alone does not let you preserve the full judgement, and it does not give consumers an interoperable way to filter on the source side of the score.


What existing tooling does

In OpenCTI, you can assign source reliability to Organizations, Individuals, Systems, and Reports, alongside information credibility expressed as a confidence score.

Report OpenCTI source reliability

The export looks like this:

    {
        "id": "report--fd7bd254-354f-5e8c-af24-163c1a878d1d",
        "spec_version": "2.1",
        "revoked": false,
        "x_opencti_reliability": "A - Completely reliable",
        "confidence": 100,
        "created": "2026-05-14T11:19:13.000Z",
        "modified": "2026-05-14T11:19:40.910Z",
        "name": "Test Report for blog",
        "description": "test",
        "content": "<p>test</p>",
        "report_types": [
            "internal-report"
        ],
        "published": "2026-05-14T11:19:13.000Z",
        "x_opencti_workflow_id": "209bd740-0df9-4074-a4f3-6f1803455007",
        "x_opencti_id": "429f407a-f43a-4007-a734-0e0d727b38ae",
        "x_opencti_type": "Report",
        "type": "report"
}

You can see source reliability is captured in a custom property, x_opencti_reliability, while information credibility is mapped to a confidence score.

That works inside one platform.

The problem is portability.

If you want to move this data across STIX-native tooling without tying yourself to a vendor-specific property, you need a representation that is explicit, reusable, and discoverable by others.


What we needed to support

The main requirement here is practical:

we want to filter objects by Admiralty Code.

For example:

  • only show reports where source reliability is greater than C
  • find all intelligence marked as A or B
  • preserve both halves of the judgement when exchanging data between tools

That requirement matters because it pushes us away from a loose descriptive field and towards a model that is structured enough to query.

In STIX terms, Admiralty Codes are best represented as a set of hardcoded Marking Definition objects.

The design question then becomes:

should source reliability and information credibility be represented as one object, for example A1, or should they be split into two objects?

In our view, splitting them is the better option.

It avoids maintaining 36 possible combinations as separate objects, and it makes filtering much easier because each side of the code remains independently searchable.

That gives us a more maintainable implementation and a more useful data model.


Designing the STIX representation

According to the STIX specification:

Any new marking definitions SHOULD be specified using the extension facility described in section 7.3.

Source: STIX specification

So the design needs two things:

  1. Marking Definition objects for the Admiralty values themselves
  2. Extension Definitions that describe the custom properties used inside those markings

A good reference point is the STIX TLP v2.0 Marking Definitions:

{
    "type": "marking-definition",
    "spec_version": "2.1",
    "id": "marking-definition--55d920b0-5e8b-4f79-9ee9-91f868d9b421",
    "created": "2022-10-01T00:00:00.000Z",
    "name": "TLP:AMBER",
    "extensions": {
        "extension-definition--60a3c5c5-0d10-413e-aab3-9e08dde9e88d": {
            "extension_type": "property-extension",
            "tlp_2_0": "amber"
        }
    }
}

My initial rough design for the Admiralty Marking Definitions looked like this.

Source Reliability

{
    "type": "marking-definition",
    "spec_version": "2.1",
    "id": "marking-definition--<ID>",
    "created": "<DATE>",
    "name": "Admiralty Source Reliability: B - Usually reliable",
    "extensions": {
        "extension-definition--<ID>": {
            "extension_type": "property-extension",
            "admiralty_source_reliability": "B",
            "admiralty_source_reliability_description": "Usually reliable"
        }
    }
}

Information Credibility

{
    "type": "marking-definition",
    "spec_version": "2.1",
    "id": "marking-definition--<ID>",
    "created": "<DATE>",
    "name": "Admiralty Information Credibility: 1 - Confirmed by other sources",
    "extensions": {
        "extension-definition--<ID>": {
            "extension_type": "property-extension",
            "admiralty_information_credibility": 1,
            "admiralty_information_credibility_description": "Confirmed by other sources"
        }
    }
}

This gives us a model that is:

  • explicit
  • vendor-neutral
  • easy to distribute
  • easy to attach to any STIX object via object_marking_refs

Most importantly, it gives other teams something they can adopt without depending on our internal implementation choices.


Turning the design into reusable objects

With that design in mind, I created two STIX Extension Definitions to support the properties used in those Marking Definitions.

You can see them here:

We then put together a small library, stix2admiralty, to generate the Marking Definitions with fixed IDs so they can be reused consistently across datasets and tooling.

You can find and reference all of the objects in this directory. Here is an example:

{
    "type": "marking-definition",
    "spec_version": "2.1",
    "id": "marking-definition--0a48adab-e7d5-5354-8a41-abf199fe2628",
    "created": "2020-01-01T00:00:00.000Z",
    "created_by_ref": "identity--9779a2db-f98c-5f4b-8d08-8ee04e02dbb5",
    "name": "Admiralty Information Credibility: 5 - Improbable",
    "extensions": {
        "extension-definition--88c675ee-098f-4502-8108-5167d24a5e11": {
            "extension_type": "property-extension",
            "admiralty_information_credibility": "5",
            "admiralty_information_credibility_description": "Improbable"
        }
    },
    "object_marking_refs": [
        "marking-definition--94868c89-83c2-464b-929b-a1a8aa3c8487",
        "marking-definition--60c0f466-511a-5419-9f7e-4814e696da40"
    ]
}

For convenience, we also generate a bundle containing all of the Admiralty Marking Definitions together.

That makes adoption much easier for technical teams:

  • import the bundle once
  • reuse the fixed IDs
  • apply the markings to any relevant STIX object

The benefit is that we, and anyone else who wants to tag their objects with Admiralty scores, can now do so in a standardised way that is not tied to any one vendor.


What this looks like in practice

For example, the Report object from OpenCTI shown earlier in this post could instead be represented like this:

    {
        "id": "report--fd7bd254-354f-5e8c-af24-163c1a878d1d",
        "spec_version": "2.1",
        "revoked": false,
        "created": "2026-05-14T11:19:13.000Z",
        "modified": "2026-05-14T11:19:40.910Z",
        "name": "Test Report for blog",
        "description": "test",
        "content": "<p>test</p>",
        "report_types": [
            "internal-report"
        ],
        "published": "2026-05-14T11:19:13.000Z",
        "x_opencti_workflow_id": "209bd740-0df9-4074-a4f3-6f1803455007",
        "x_opencti_id": "429f407a-f43a-4007-a734-0e0d727b38ae",
        "x_opencti_type": "Report",
        "type": "report",
        "object_marking_refs": [
            "marking-definition--cf438540-077a-56c7-b68e-82fcc2bb0208",
            "marking-definition--2462b621-0825-5879-917c-082e0394bcf4"
        ]
}

Where:

  • marking-definition--cf438540-077a-56c7-b68e-82fcc2bb0208 is Admiralty Source Reliability: A - Completely reliable
  • marking-definition--2462b621-0825-5879-917c-082e0394bcf4 is Admiralty Information Credibility: 1 - Confirmed by other sources

This is the key implementation outcome.

Instead of carrying a vendor-specific reliability field, the judgement is represented using reusable STIX objects that other systems can understand and preserve.

That means teams can exchange, store, and filter on Admiralty metadata without inventing their own incompatible approach every time.


Why we think this is the right approach

This design is not especially complicated, and that is part of the appeal.

It works with the existing STIX model.

It preserves both parts of the Admiralty judgement.

It avoids exploding the object set into 36 combinations.

And it gives the community something implementation-ready rather than another informal convention hidden inside a blog post or product export.

If you already produce STIX and want Admiralty scoring to survive beyond one platform, this is a practical way to do it.

If you want to adopt it yourself, the pieces are already available:

  • the Extension Definitions
  • the generated Marking Definitions
  • the full bundle of reusable objects

That should be enough to integrate Admiralty-aware filtering and tagging into an existing CTI pipeline with minimal custom work.


Stixify

Your automated threat intelligence analyst.

Discuss this post

Head on over to the dogesec community to discuss this post.

dogesec community

Open-Source Projects

All dogesec commercial products are built in-part from code-bases we have made available under permissive licenses.

dogesec Github

Posted by:

David Greenwood

David Greenwood, Do Only Good Everyday



Never miss an update


Sign up to receive new articles in your inbox as they published.

Your subscription could not be saved. Please try again.
Your subscription has been successful.