-
Notifications
You must be signed in to change notification settings - Fork 17
JSON Style Guide Design Philosophy
All credit goes to EclecticIQ - thanks for sharing this!
The STIX language uses quite a few advanced XML modeling techniques (multiple
namespaces, xsi:type
substitutions in instance documents, QName
identifiers,
and so on), making it quite complex to work with.
The JSON format used by objectivistix tries to be much simpler to work with. Structurally it mirrors most of the original XML tree structure, but the resulting tree structures are not identical since the JSON representation favors flat objects over nested structures.
In general, each compound structure is converted into a JSON object (dict
in
Python). These objects always have a type
key to indicate the type of the
structure:
{
"type": "indicator",
"...": "..."
}
Each of the main STIX constructs (see the STIX architecture
) is represented
as a JSON object. The type
keys used are:
Defining Schema | XML Schema Type | Object type field |
---|---|---|
STIX Core | STIXType |
package |
STIX Campaign | CampaignType |
campaign |
STIX COA | CourseOfActionType |
course-of-action |
STIX Exploit Target | ExploitTargetType |
exploit-target |
STIX Incident | IncidentType |
incident |
STIX Indicator | IndicatorType |
indicator |
STIX Report | ReportType |
report |
STIX TTP | TTPType |
ttp |
STIX Threat Actor | ThreatActorType |
threat-actor |
CybOX | ObservableType |
observable |
Secondary constructs use these additional types:
Defining Schema | XML Schema Type | Object type field |
---|---|---|
STIX Common | ActivityType |
activity |
STIX Common | ConfidenceType |
confidence |
STIX Common | IdentityType |
identity |
STIX Common | InformationSourceType |
information-source |
STIX Common | KillChainType |
kill-chain |
STIX Common | KillChainPhaseType |
kill-chain-phase |
STIX Common | KillChainPhaseReferenceType |
kill-chain-phase-reference |
STIX Common | StatementType |
statement |
STIX Common | ToolInformationType |
tool-information |
STIX COA | ObjectiveType |
objective |
STIX Exploit Target | ConfigurationType |
configuration |
STIX Exploit Target | CVSSVectorType |
cvss-vector |
STIX Exploit Target | ConfigurationType |
configuration |
STIX Exploit Target | VulnerabilityType |
vulnerability |
STIX Exploit Target | WeaknessType |
weakness |
STIX Exploit Target | CVSSVectorType |
cvss-vector |
STIX Incident | AffectedAssetType |
affected-asset |
STIX Incident | HistoryItemType |
history-item |
STIX Incident | ImpactAssessmentType |
impact-assessment |
STIX Incident | PropertyAffectedType |
property-affected |
STIX Indicator | TestMechanismType |
test-mechanism |
STIX Indicator | ValidTimeType |
valid-time |
STIX Markings | MarkingSpecificationType |
marking-specification |
STIX Markings | MarkingStructureType |
marking-structure |
STIX TTP | AttackPatternType |
attack-pattern |
STIX TTP | BehaviorType |
behavior |
STIX TTP | ExploitType |
exploit |
STIX TTP | InfrastructureType |
infrastructure |
STIX TTP | ResourceType |
resource |
STIX TTP | VictimTargetingType |
victim-targeting |
CybOX Core | ObservablesType |
observables |
CybOX Common | HashType |
hash |
CybOX Common | MeasureSourceType |
hash |
CybOX Common | ObjectType |
cybox-object |
CybOX Common | ToolInformationType |
tool-information |
Both the attributes and child elements defined for a compound structure usually map to additional key/value pairs of the JSON objects:
{
"type": "indicator",
"negate": false,
"title": "This is the title."
}
For one-to-one relations, the value is a nested object, and the key is a
singular noun (observable
in the example):
{
"type": "indicator",
"observable": {
"type": "observable",
"...": "..."
},
"...": "..."
}
For one-to-many relations, the value is a JSON array containing the child
objects, and the key is a plural noun (indicators
in the example):
{
"type": "package",
"indicators": [
{
"type": "indicator",
"...": "..."
},
{
"type": "indicator",
"...": "..."
}
],
"...": "..."
}
Additionally, the many RelatedXYZ
constructs (and the surrounding container
objects) in STIX are also flattened: the target of the relation is the child
object (or a list of those), and any additional relationship information is
embedded into the child object(s):
{
"type": "indicator",
"indicated_ttps": [
{
"type": "ttp",
"relationship": "...",
"relationship_information_source": "...",
"...": "..."
},
{
"type": "ttp",
"relationship": "...",
"relationship_information_source": "...",
"...": "..."
}
],
"...": "..."
}
See also the notes about nesting below.
The STIX XML representation is deeply nested, partly due to the way XML is typically used. The JSON representation tries to be a bit more pragmatic and adheres to the "flat is better than nested" adage.
In practice, this means that nested container structures are flattened as much
as possible. Unnecessary container structures are simply removed. For example,
the <stix:Indicators>
container structure used in the XML representation
does not exist as such in the JSON representation, since using an array is
sufficient.
To further reduce the number of nested objects, various XML constructs using container elements with (optional) attributes are flattened into the parent object by using multiple related keys. This is best explained using an example.
For example, the StructuredTextType
used in both STIX and CybOX is basically
a string that can optionally carry a structuring_format
attribute. A naive
conversion would require a nested object to represent this:
{
"type": "...",
"description": {
"structuring_format": "html",
"value": "Description goes here."
},
"...": "..."
}
Since the structuring_format
is optional, this approach would often result
in a small nested object with only a single key/value pair (the value
). To
avoid this, objectivistix takes an alternative approach using two related keys
in the containing object:
{
"type": "...",
"description": "Description goes here.",
"description_structuring_format": "html",
"...": "..."
}
In case the structuring_format
is not specified, the
description_structuring_format
key/value pair would simply not be present:
{
"type": "...",
"description": "Description goes here.",
"...": "..."
}
All id
and idref
attributes in STIX XML are not simply string values,
but qualified names (QName in XML), meaning that they contain a namespace prefix
which resolves to a namespace URI. To avoid any explicit mappings for these
prefixes and their associated namespace URI, the JSON representation always
expresses id
and idref
values in their canonical form using the
so-called Clark notation
, which looks like this:
{http://example.com/ns/uri}local-name
.
The top level object may optionally contain an id_namespaces
mapping that
maps prefixes to namespace URIs. This mapping will be used to determine the
prefixes used for id
and idref
attribute values when converting the
object to XML, as illustrated by the example below:
{
"type": "package",
"id": "{http://example.org/}Package-b3ba766b-d3e6-4d92-82b2-5940f0cb763c",
"id_namespaces": {
"example": "http://example.org/"
}
}
<stix:STIX_Package
xmlns:stix="http://stix.mitre.org/stix-1"
xmlns:example="http://example.com/"
id="example:Package-b3ba766b-d3e6-4d92-82b2-5940f0cb763c">
…
</stix:STIX_Package>
In case no id_namespaces
mapping is present, a unique namespace prefix will
be used instead. The id_namespaces
can safely be left out with no semantical
loss, since the prefix is arbitrary and only used for serialized XML data, and
not for the in-memory model.
The package header is not treated as a first-class structure. Since the
STIX_Header
construct only applies to STIX_Package
, it is merged
completely into the main package
object (this avoids having an additional
nested object for the header):
{
"type": "package",
"description": "Description goes here.",
"...": "..."
}
The StructuredTextType
construct is not transformed into a child object.
Instead, the keys foo
and (optionally) foo_structuring_format
are
added to the containing object.
An observable composition
structure does not result in a nested object for
the composition itself. Instead, the composition
key contains the child
objects, and the composition_operator
specifies the operator:
{
"type": "indicator",
"observable": {
"composition_operator": "or",
"composition": [
{
"type": "observable",
"...": "..."
},
{
"type": "observable",
"...": "..."
}
]
},
"...": "..."
}