Repository: oasis-open/cti-python-stix2

Issue:
Spec version detection should use presence, not value of spec_version for STIX 2.0 bundles
Current spec version detection (via `stix2.utils.detect_spec_version()`) uses the value of the `spec_version` property, if present, to determine the spec version of bundles.  It should use only the presence.  The STIX 2.0 spec says in section 5.1 for the spec_version property:  "The version of the STIX specification used to represent the content in this Bundle."  I.e. its value represents the spec version of the content of the bundle, not the bundle itself.


Discussion:

Patch:
diff --git a/stix2/utils.py b/stix2/utils.py
index 647a89f..8f679d4 100644
--- a/stix2/utils.py
+++ b/stix2/utils.py
@@ -327,9 +327,15 @@ def detect_spec_version(stix_dict):
     obj_type = stix_dict["type"]
 
     if 'spec_version' in stix_dict:
-        # For STIX 2.0, applies to bundles only.
-        # For STIX 2.1+, applies to SCOs, SDOs, SROs, and markings only.
-        v = stix_dict['spec_version']
+        # For STIX 2.0, applies to bundles only.  Presence in a bundle implies
+        # STIX 2.0; the value applies to the content of the bundle, not the
+        # bundle itself, so we don't care here about the value.
+        #
+        # For STIX 2.1+, applies to non-bundles only.
+        if obj_type == "bundle":
+            v = "2.0"
+        else:
+            v = stix_dict['spec_version']
     elif "id" not in stix_dict:
         # Only 2.0 SCOs don't have ID properties
         v = "2.0"


Test patch:
diff --git a/stix2/test/test_spec_version_detect.py b/stix2/test/test_spec_version_detect.py
index 570cc8e..0d03e03 100644
--- a/stix2/test/test_spec_version_detect.py
+++ b/stix2/test/test_spec_version_detect.py
@@ -77,6 +77,35 @@ from stix2.utils import detect_spec_version
             },
             "2.0",
         ),
+        (
+            {
+                "type": "bundle",
+                "id": "bundle--8379cb02-8131-47c8-8a7c-9a1f0e0986b1",
+                "spec_version": "2.1",
+                "objects": [
+                    {
+                        "type": "identity",
+                        "spec_version": "2.1",
+                        "id": "identity--d7f72e8d-657a-43ec-9324-b3ec67a97486",
+                        "created": "1972-05-21T05:33:09.000Z",
+                        "modified": "1973-05-28T02:10:54.000Z",
+                        "name": "alice",
+                        "identity_class": "individual",
+                    },
+                    {
+                        "type": "marking-definition",
+                        "spec_version": "2.1",
+                        "id": "marking-definition--2a13090f-a493-4b70-85fe-fa021d91dcd2",
+                        "created": "1998-03-27T19:44:53.000Z",
+                        "definition_type": "statement",
+                        "definition": {
+                            "statement": "Copyright (c) ACME Corp.",
+                        },
+                    },
+                ],
+            },
+            "2.0",
+        ),
         # STIX 2.1 examples
         (
             {
diff --git a/stix2/test/v20/test_utils.py b/stix2/test/v20/test_utils.py
index 0443933..f61369b 100644
--- a/stix2/test/v20/test_utils.py
+++ b/stix2/test/v20/test_utils.py
@@ -349,6 +349,10 @@ def test_is_not_sro_dict(dict_):
         {"type": "identity"},
         {"type": "software"},
         {"type": "marking-definition"},
+        # Presence of spec_version property implies a STIX 2.0 bundle,
+        # regardless of the property's value.  STIX 2.1 bundles don't have a
+        # "spec_version" property defined.
+        {"type": "bundle", "spec_version": "2.1"},
         {
             "type": "bundle",
             "id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
@@ -370,12 +374,20 @@ def test_is_object_dict(dict_):
         {"type": "identity", "spec_version": "2.1"},
         {"type": "software", "spec_version": "2.1"},
         {"type": "marking-definition", "spec_version": "2.1"},
-        {"type": "bundle", "spec_version": "2.1"},
         {"type": "language-content", "spec_version": "2.1"},
         {"type": "relationship", "spec_version": "2.1"},
         {"type": "sighting", "spec_version": "2.1"},
         {"type": "foo", "spec_version": "2.1"},
         {"type": "foo"},
+        {
+            "type": "bundle",
+            "id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
+            "objects": [
+                {"type": "identity"},
+                {"type": "software"},
+                {"type": "marking-definition"},
+            ],
+        },
     ],
 )
 def test_is_not_object_dict(dict_):
diff --git a/stix2/test/v21/test_utils.py b/stix2/test/v21/test_utils.py
index 6d108d4..33e7ea4 100644
--- a/stix2/test/v21/test_utils.py
+++ b/stix2/test/v21/test_utils.py
@@ -382,7 +382,7 @@ def test_is_object_dict(dict_):
         {"type": "identity"},
         {"type": "software"},
         {"type": "marking-definition"},
-        {"type": "bundle"},
+        {"type": "bundle", "spec_version": "2.1"},
         {"type": "language-content"},
         {"type": "relationship"},
         {"type": "sighting"},