Skip to content

Commit 2f3a255

Browse files
Update Safe Outputs conformance checker for recent spec changes (#31431)
1 parent cebfa67 commit 2f3a255

1 file changed

Lines changed: 155 additions & 0 deletions

File tree

scripts/check-safe-outputs-conformance.sh

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,161 @@ check_create_issue_auto_injection() {
10201020
}
10211021
check_create_issue_auto_injection
10221022

1023+
# INT-001: JSON Schema Draft 7 Validation (Section 9.1)
1024+
echo "Running INT-001: JSON Schema Draft 7 Validation..."
1025+
check_json_schema_draft7() {
1026+
local tools_json="pkg/workflow/js/safe_outputs_tools.json"
1027+
local gateway_handler="actions/setup/js/safe_outputs_handlers.cjs"
1028+
local failed=0
1029+
1030+
# Per spec Section 9.1: All tool invocations MUST validate against JSON Schema Draft 7.
1031+
# Check that tool schemas declare draft-07 or that the gateway validates using Ajv/equivalent.
1032+
1033+
if [ ! -f "$tools_json" ]; then
1034+
log_high "INT-001: Tool definitions file missing: $tools_json"
1035+
return
1036+
fi
1037+
1038+
# Per spec Section 9.1: Schema validation is provided by the MCP framework via inputSchema.
1039+
# Check that the tool definitions include inputSchema on all tools, which enables
1040+
# JSON Schema Draft 7 validation at the MCP server level.
1041+
# Note: Explicit Ajv usage is one approach; relying on MCP framework schema enforcement
1042+
# via inputSchema is the primary conformant pattern in this implementation.
1043+
1044+
# Verify inputSchema is present on all tools (required by JSON Schema Draft 7 pattern)
1045+
local tools_without_schema
1046+
tools_without_schema=$(python3 -c "
1047+
import json, sys
1048+
try:
1049+
data = json.load(open('$tools_json'))
1050+
tools = data if isinstance(data, list) else data.get('tools', [])
1051+
missing = [t.get('name','?') for t in tools if isinstance(t, dict) and 'inputSchema' not in t]
1052+
if missing: print(','.join(missing))
1053+
except Exception as e:
1054+
sys.exit(0)
1055+
" 2>/dev/null)
1056+
if [ -n "$tools_without_schema" ]; then
1057+
log_medium "INT-001: Tools missing inputSchema in $tools_json: $tools_without_schema"
1058+
failed=1
1059+
fi
1060+
1061+
if [ $failed -eq 0 ]; then
1062+
log_pass "INT-001: Tool schemas include inputSchema for JSON Schema Draft 7 validation"
1063+
fi
1064+
}
1065+
check_json_schema_draft7
1066+
1067+
# INT-002: Sanitization Pipeline Completeness (Section 9.4 S1, S4)
1068+
echo "Running INT-002: Sanitization Pipeline Completeness..."
1069+
check_sanitization_pipeline() {
1070+
local core_sanitizer="actions/setup/js/sanitize_content_core.cjs"
1071+
local fallback_sanitizer="actions/setup/js/sanitize_content.cjs"
1072+
local failed=0
1073+
1074+
# Per spec Section 9.4: Implementations MUST apply these stages in order:
1075+
# S1: Null byte removal (remove \x00 and control chars)
1076+
# S4: HTML tag filtering (remove <script>, <iframe>, on* event handlers)
1077+
1078+
local sanitizer_file=""
1079+
if [ -f "$core_sanitizer" ]; then
1080+
sanitizer_file="$core_sanitizer"
1081+
elif [ -f "$fallback_sanitizer" ]; then
1082+
sanitizer_file="$fallback_sanitizer"
1083+
else
1084+
log_high "INT-002: Sanitization implementation file missing (expected $core_sanitizer)"
1085+
return
1086+
fi
1087+
1088+
# Check S1: Null byte / control character removal (Section 9.4 S1)
1089+
# Spec requires removal of all null bytes (\0, \x00).
1090+
# Implementation may use a control-char range starting at \x00 (e.g., /[\x00-\x08...]/)
1091+
if ! grep -qE 'x00|removeNull|null.*byte|byte.*null' "$sanitizer_file"; then
1092+
log_high "INT-002: Sanitization pipeline missing null byte removal (Section 9.4 S1)"
1093+
failed=1
1094+
fi
1095+
1096+
# Check S4: HTML tag filtering — <script>, <iframe>, on* event handlers (Section 9.4 S4)
1097+
if ! grep -qE 'script|iframe|on\*|onerror|onclick|event.*handler|dangerous.*attr|strip.*attr' "$sanitizer_file"; then
1098+
log_high "INT-002: Sanitization pipeline missing HTML tag/event handler filtering (Section 9.4 S4)"
1099+
failed=1
1100+
fi
1101+
1102+
if [ $failed -eq 0 ]; then
1103+
log_pass "INT-002: Sanitization pipeline implements null byte removal (S1) and HTML filtering (S4)"
1104+
fi
1105+
}
1106+
check_sanitization_pipeline
1107+
1108+
# EXEC-001: System Types Processed Last (Section 10.2)
1109+
echo "Running EXEC-001: System Types Processed Last..."
1110+
check_system_types_ordering() {
1111+
local manager_file="actions/setup/js/safe_output_handler_manager.cjs"
1112+
local failed=0
1113+
1114+
# Per spec Section 10.2: Operations execute in NDJSON order, with system types
1115+
# (noop, missing_tool, missing_data, report_incomplete) processed LAST.
1116+
1117+
if [ ! -f "$manager_file" ]; then
1118+
log_high "EXEC-001: Safe output handler manager missing: $manager_file"
1119+
return
1120+
fi
1121+
1122+
# Check that system types are collected separately (prerequisite for last processing)
1123+
if ! grep -qE "missing_tool.*missing_data.*noop|collect.*missing|system.*type" "$manager_file"; then
1124+
log_medium "EXEC-001: Handler manager does not appear to separate system types for ordering (Section 10.2)"
1125+
failed=1
1126+
fi
1127+
1128+
# Verify that noop, missing_tool, missing_data, report_incomplete are recognized as a group
1129+
if ! grep -q "report_incomplete" "$manager_file"; then
1130+
log_medium "EXEC-001: report_incomplete system type not handled in $manager_file (Section 10.2)"
1131+
failed=1
1132+
fi
1133+
1134+
if [ $failed -eq 0 ]; then
1135+
log_pass "EXEC-001: System types (noop, missing_tool, missing_data, report_incomplete) are grouped for last processing"
1136+
fi
1137+
}
1138+
check_system_types_ordering
1139+
1140+
# EXEC-002: Zero Max Limit Disables Type (Section 10.5)
1141+
echo "Running EXEC-002: Zero Max Limit Disables Type..."
1142+
check_zero_max_disables_type() {
1143+
local failed=0
1144+
1145+
# Per spec Section 10.5: When max: 0 is configured for a safe output type,
1146+
# the type MUST be disabled (MCP tool not registered, no config generated).
1147+
1148+
# Check Go compiler: types with max: 0 should not appear in generated config
1149+
if grep -rqE "max.*==.*0|\.Max.*==.*0|maxIsZero|disabledType|skipZeroMax" pkg/workflow/safe_outputs*.go 2>/dev/null; then
1150+
log_pass "EXEC-002: Compiler handles max: 0 type disabling"
1151+
return
1152+
fi
1153+
1154+
# Alternative: check if there are tests validating zero-max disabling
1155+
if grep -rqE "max.*0.*disabled|max.*:.*0|\"max\".*0" pkg/workflow/safe_outputs*test*.go 2>/dev/null; then
1156+
log_pass "EXEC-002: Tests validate max: 0 type disabling behavior"
1157+
return
1158+
fi
1159+
1160+
# Check if the gateway handler skips tools not present in config (indirectly validates zero-max)
1161+
local gateway="actions/setup/js/safe_outputs_handlers.cjs"
1162+
if [ -f "$gateway" ]; then
1163+
if grep -qE "toolsConfig|registeredTools|register.*tool|tool.*register" "$gateway"; then
1164+
log_pass "EXEC-002: Gateway registers tools from config (zero-max types absent from config will not be registered)"
1165+
return
1166+
fi
1167+
fi
1168+
1169+
log_medium "EXEC-002: No explicit evidence that max: 0 disables/unregisters the safe output type (Section 10.5)"
1170+
failed=1
1171+
1172+
if [ $failed -eq 0 ]; then
1173+
log_pass "EXEC-002: max: 0 properly disables safe output type registration"
1174+
fi
1175+
}
1176+
check_zero_max_disables_type
1177+
10231178
# Summary
10241179
echo ""
10251180
echo "=================================================="

0 commit comments

Comments
 (0)