Skip to content

Commit d3da088

Browse files
nicolehaugenCopilot
andcommitted
fix: reject non-json artifact contributions
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent e5fb1a7 commit d3da088

2 files changed

Lines changed: 67 additions & 0 deletions

File tree

‎src/specify_cli/artifacts/catalog.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from __future__ import annotations
1313

14+
import json
1415
import re
1516
import shlex
1617
from collections.abc import Iterable
@@ -436,6 +437,10 @@ def get_contribution_info(self, lookup_id: str) -> dict[str, Any]:
436437
raise ContributionNotFoundError(lookup_id)
437438

438439
contribution, manifest_path, source_path = resolved
440+
try:
441+
json.dumps(contribution)
442+
except (TypeError, ValueError) as exc:
443+
raise ArtifactResolutionError() from exc
439444
return {
440445
"id": lookup_id,
441446
"layer": layer,

‎tests/test_artifact_command.py‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
import re
1313
import shutil
14+
from datetime import date
1415
from pathlib import Path
1516

1617
import pytest
@@ -1073,6 +1074,67 @@ def test_lookup_json_rejects_unknown_contribution(
10731074
"error": f"unknown contribution {lookup_id}"
10741075
}
10751076

1077+
def test_lookup_json_rejects_non_json_manifest_value(
1078+
self, spec_kit_project: Path, monkeypatch: pytest.MonkeyPatch
1079+
):
1080+
monkeypatch.chdir(spec_kit_project)
1081+
install_preset(
1082+
spec_kit_project,
1083+
"dated-contribution",
1084+
{
1085+
"templates": [
1086+
{
1087+
"type": "template",
1088+
"name": "dated-contribution",
1089+
"released": date(2026, 1, 1),
1090+
}
1091+
]
1092+
},
1093+
)
1094+
1095+
result = CliRunner().invoke(
1096+
app,
1097+
[
1098+
"artifact",
1099+
"lookup",
1100+
"preset:dated-contribution:template:dated-contribution",
1101+
"--json",
1102+
],
1103+
)
1104+
1105+
assert result.exit_code == 1
1106+
assert result.stdout == ""
1107+
assert json.loads(result.stderr) == {
1108+
"error": "artifact resolution failed"
1109+
}
1110+
1111+
@pytest.mark.parametrize(
1112+
"lookup_id",
1113+
[
1114+
"invalid:source:command:name",
1115+
"extension:source:invalid:name",
1116+
"extension:source:hook:%FF:command",
1117+
"extension:source:hook:event:%ZZ",
1118+
],
1119+
)
1120+
def test_lookup_json_rejects_malformed_lookup_id(
1121+
self,
1122+
spec_kit_project: Path,
1123+
monkeypatch: pytest.MonkeyPatch,
1124+
lookup_id: str,
1125+
):
1126+
monkeypatch.chdir(spec_kit_project)
1127+
1128+
result = CliRunner().invoke(
1129+
app, ["artifact", "lookup", lookup_id, "--json"]
1130+
)
1131+
1132+
assert result.exit_code == 1
1133+
assert result.stdout == ""
1134+
assert json.loads(result.stderr) == {
1135+
"error": f"unknown contribution {lookup_id}"
1136+
}
1137+
10761138
def test_lookup_requires_json_flag(
10771139
self, spec_kit_project: Path, monkeypatch: pytest.MonkeyPatch
10781140
):

0 commit comments

Comments
 (0)