-
Notifications
You must be signed in to change notification settings - Fork 4.7k
131 lines (110 loc) · 5.49 KB
/
Copy pathvalidate-canvas-extensions.yml
File metadata and controls
131 lines (110 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
name: Validate Canvas Extensions
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
paths:
- "extensions/**"
- ".schemas/canvas.schema.json"
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Validate changed canvas extensions
run: |
set -euo pipefail
# Validate the schema file itself is parseable JSON whenever it is present
if [ -f ".schemas/canvas.schema.json" ]; then
if ! node -e "JSON.parse(require('fs').readFileSync('.schemas/canvas.schema.json','utf8'))" 2>/dev/null; then
echo "❌ .schemas/canvas.schema.json is not valid JSON"
exit 1
fi
echo "✅ .schemas/canvas.schema.json is valid JSON"
fi
# Collect changed extension directories.
# Use null-terminated (-z) output from git diff so filenames containing newlines
# or other special characters are read atomically (matches the pattern in skill-check.yml).
# Each extracted name is then validated against a strict allowlist regex before use,
# rejecting anything containing shell metacharacters ($, (, ), spaces, etc.).
declare -A seen_dirs=()
changed_extensions=()
while IFS= read -r -d '' file; do
case "$file" in
extensions/*)
ext_name="${file#extensions/}"
ext_name="${ext_name%%/*}"
# Allowlist: extension directory names must be lowercase alphanumeric + hyphens only.
# Any name that does not match (e.g. $(id), spaces, slashes) is silently skipped —
# it cannot be a valid extension and cannot produce a valid canvas.json.
if [[ "$ext_name" =~ ^[a-z0-9][a-z0-9-]*$ ]] && \
[ "$ext_name" != "external-assets" ] && \
[ -z "${seen_dirs[$ext_name]+x}" ]; then
seen_dirs["$ext_name"]=1
changed_extensions+=("extensions/$ext_name")
fi
;;
esac
done < <(git diff --name-only -z "origin/${{ github.base_ref }}...HEAD")
if [ "${#changed_extensions[@]}" -eq 0 ]; then
echo "No canvas extension directories changed — skipping validation."
exit 0
fi
echo "Validating ${#changed_extensions[@]} extension(s): ${changed_extensions[*]}"
errors=()
for ext_dir in "${changed_extensions[@]}"; do
if [ ! -d "$ext_dir" ]; then
echo "$ext_dir no longer exists (deleted?), skipping."
continue
fi
if [ ! -f "$ext_dir/extension.mjs" ]; then
errors+=("\`$ext_dir\`: missing required \`extension.mjs\`.")
fi
if [ ! -f "$ext_dir/canvas.json" ]; then
errors+=("\`$ext_dir\`: missing required \`canvas.json\`.")
continue
fi
if [ ! -f "$ext_dir/assets/preview.png" ]; then
errors+=("\`$ext_dir\`: missing required \`assets/preview.png\`.")
fi
if ! schema_output="$(npx ajv-cli validate --spec=draft7 --strict=false -s .schemas/canvas.schema.json -d "$ext_dir/canvas.json" 2>&1)"; then
condensed_output="$(echo "$schema_output" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g')"
errors+=("\`$ext_dir/canvas.json\`: schema validation failed against \`.schemas/canvas.schema.json\` ($condensed_output).")
continue
fi
mapfile -t screenshot_paths < <(
node -e 'const fs = require("fs"); const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const paths = [manifest?.screenshots?.icon?.path, manifest?.screenshots?.gallery?.path].filter((value) => typeof value === "string" && value.trim().length > 0); for (const value of [...new Set(paths)]) { console.log(value); }' "$ext_dir/canvas.json"
)
for screenshot_path in "${screenshot_paths[@]}"; do
# Allowlist: screenshot paths must follow assets/<name>.<ext> — no .., no shell metacharacters.
# This mirrors the schema regex and guards against a crafted canvas.json producing an
# unexpected value from node stdout that could bypass the schema check.
if [[ ! "$screenshot_path" =~ ^assets/([A-Za-z0-9_-]+/)*[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*\.[A-Za-z0-9]+$ ]]; then
errors+=("\`$ext_dir/canvas.json\`: screenshot path \`$screenshot_path\` is not a valid assets path.")
continue
fi
if [ ! -f "$ext_dir/$screenshot_path" ]; then
errors+=("\`$ext_dir/canvas.json\`: screenshot path \`$screenshot_path\` does not exist in the extension directory.")
fi
done
done
if [ "${#errors[@]}" -ne 0 ]; then
echo "❌ Canvas extension validation failed:"
for error in "${errors[@]}"; do
echo "- $error"
done
exit 1
fi
echo "✅ All changed canvas extensions passed validation."