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

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.

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
AorB - 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:
- Marking Definition objects for the Admiralty values themselves
- 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-82fcc2bb0208isAdmiralty Source Reliability: A - Completely reliablemarking-definition--2462b621-0825-5879-917c-082e0394bcf4isAdmiralty 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.
Open-Source Projects
All dogesec commercial products are built in-part from code-bases we have made available under permissive licenses.
Never miss an update
Sign up to receive new articles in your inbox as they published.
