- embedCsp is connectSrc + wasmUnsafeEval + blob (all default off), not per-directive lists - cross-origin script/worker loading is unsupported; fetch + Blob is the supported path - check-app.sh/selftest validate flags and platform capability boundaries
138 lines
5.5 KiB
Bash
Executable File
138 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# check-app.sh 自测:用临时 fixture 覆盖大产物 / embedCsp 规则,防止技能与平台校验漂移。
|
||
# 用法: bash scripts/selftest-check-app.sh 退出码 0=全部断言通过。
|
||
set -u
|
||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||
CHECK="$HERE/check-app.sh"
|
||
[ -f "$CHECK" ] || { echo "找不到 $CHECK" >&2; exit 2; }
|
||
|
||
ROOT=$(mktemp -d)
|
||
trap 'rm -rf "$ROOT"' EXIT
|
||
N=0; BAD=0
|
||
|
||
# fixture <name>:建一个最小 micro 仓库骨架(manifest/Dockerfile/入口/前端)。
|
||
fixture() {
|
||
local dir="$ROOT/$1"
|
||
mkdir -p "$dir/web/src/skin" "$dir/dist"
|
||
cat >"$dir/app.manifest.json" <<'JSON'
|
||
{ "listingKey": "selftest-app", "name": "自测应用", "version": "1.0.0", "type": "micro" }
|
||
JSON
|
||
cat >"$dir/Dockerfile" <<'DOCKER'
|
||
FROM tickshield-node-runtime:base
|
||
RUN ln -s /opt/xgent/runtime/node_modules /app/node_modules
|
||
DOCKER
|
||
cat >"$dir/server.js" <<'JS'
|
||
import { createGuard } from '@aigrego/guard';
|
||
const guard = createGuard({ portalUrl: 'http://x', appKey: 'selftest-app', appSecret: 'x' });
|
||
export function handler(req, res) {}
|
||
export const routes = guard.routes([]);
|
||
JS
|
||
cat >"$dir/web/index.html" <<'HTML'
|
||
<!doctype html><html><body>
|
||
<script src="/apps/selftest-app/vendor/portal-sdk.js"></script>
|
||
<script src="/apps/selftest-app/app.js"></script>
|
||
</body></html>
|
||
HTML
|
||
cat >"$dir/web/src/main.tsx" <<'TS'
|
||
import { initSkin } from './skin/apply';
|
||
initSkin();
|
||
TS
|
||
printf "export const SKINS = [{ key: 'light' }, { key: 'dark' }, { key: 'jade' }];\n// SKIN_SNAPSHOT_DATE = '2026-10-22'\n" >"$dir/web/src/skin/tokens.ts"
|
||
printf ":root { font-family: system-ui, -apple-system, 'PingFang SC'; font-size: 13px; }\n" >"$dir/web/app.css"
|
||
echo '<!doctype html><html><body>app</body></html>' >"$dir/dist/index.html"
|
||
echo "$dir"
|
||
}
|
||
|
||
# set_embed <manifest> <json-array>:写入 embedCsp.connectSrc(node 优先,退化 python3)。
|
||
set_embed() {
|
||
set_embed_json "$1" "{\"connectSrc\":$2}"
|
||
}
|
||
|
||
# set_embed_json <manifest> <embedCsp-json>:写入任意 embedCsp 对象。
|
||
set_embed_json() {
|
||
local f="$1" body="$2"
|
||
if command -v node >/dev/null 2>&1; then
|
||
node -e 'const fs=require("fs");const [p,src]=process.argv.slice(1);const o=JSON.parse(fs.readFileSync(p));o.embedCsp=JSON.parse(src);fs.writeFileSync(p,JSON.stringify(o,null,2));' "$f" "$body"
|
||
else
|
||
python3 - "$f" "$body" <<'PY'
|
||
import json, sys
|
||
p, body = sys.argv[1], json.loads(sys.argv[2])
|
||
o = json.load(open(p))
|
||
o['embedCsp'] = body
|
||
json.dump(o, open(p, 'w'), ensure_ascii=False, indent=2)
|
||
PY
|
||
fi
|
||
}
|
||
|
||
# expect <name> <dir> <contains-regex> <want:1|0>
|
||
expect() {
|
||
local name="$1" dir="$2" pat="$3" want="$4" out got
|
||
out=$(bash "$CHECK" "$dir" 2>&1)
|
||
N=$((N + 1))
|
||
if printf '%s\n' "$out" | grep -qE "$pat"; then got=1; else got=0; fi
|
||
if [ "$got" = "$want" ]; then
|
||
echo "ok $name"
|
||
else
|
||
BAD=$((BAD + 1))
|
||
echo "FAIL ${name}(期望命中=$want 模式=${pat})"
|
||
printf '%s\n' "$out" | sed 's/^/ /'
|
||
fi
|
||
}
|
||
|
||
# 1) dist tgz 超限且未声明 embedCsp → 两条 die + RESULT FAIL。
|
||
D=$(fixture over-limit)
|
||
dd if=/dev/urandom of="$D/dist/core.bin" bs=1048576 count=70 2>/dev/null
|
||
expect "tgz 超限诊断" "$D" 'dist 打 tgz 后 [0-9]+MiB > 平台 64MiB' 1
|
||
expect "超限未声明外置" "$D" 'dist 超限但 manifest 未声明 embedCsp' 1
|
||
expect "超限 RESULT FAIL" "$D" '^RESULT: FAIL$' 1
|
||
|
||
# 2) embedCsp 带路径 → 结构非法。
|
||
D=$(fixture embed-bad)
|
||
set_embed "$D/app.manifest.json" '["https://cdn.example.com/assets"]'
|
||
expect "embedCsp 路径非法" "$D" 'embedCsp 非法' 1
|
||
|
||
# 3) 平台能力边界:跨域直载不支持;blob / wasm 需显式开关。
|
||
D=$(fixture worker-ext)
|
||
echo "new Worker('https://cdn.example.com/w.js');" >>"$D/web/src/main.tsx"
|
||
expect "跨域 Worker 直载拦截" "$D" '不支持跨域直载 Worker' 1
|
||
|
||
D=$(fixture ext-script)
|
||
echo '<script src="https://cdn.example.com/lib.js"></script>' >>"$D/web/index.html"
|
||
expect "跨域脚本直载拦截" "$D" '不支持跨域直载脚本' 1
|
||
|
||
D=$(fixture blob-worker)
|
||
echo "const u = URL.createObjectURL(new Blob([])); new Worker(u);" >>"$D/web/src/main.tsx"
|
||
expect "blob 未开启拦截" "$D" 'embedCsp.blob 未开启' 1
|
||
set_embed_json "$D/app.manifest.json" '{"blob":true}'
|
||
expect "blob 开启放行" "$D" 'embedCsp.blob 未开启' 0
|
||
|
||
D=$(fixture wasm-use)
|
||
echo "WebAssembly.instantiate(bytes);" >>"$D/web/src/main.tsx"
|
||
expect "wasm 未开启拦截" "$D" 'wasmUnsafeEval 未开启' 1
|
||
set_embed_json "$D/app.manifest.json" '{"wasmUnsafeEval":true}'
|
||
expect "wasm 开启放行" "$D" 'wasmUnsafeEval 未开启' 0
|
||
|
||
# 2b) 开关必须是布尔。
|
||
D=$(fixture embed-bad-flag)
|
||
set_embed_json "$D/app.manifest.json" '{"blob":"yes"}'
|
||
expect "开关非布尔拦截" "$D" 'embedCsp 非法' 1
|
||
|
||
# 4) 合规声明 + 使用点 + 小 dist → 不误报。
|
||
D=$(fixture embed-ok)
|
||
set_embed "$D/app.manifest.json" '["https://cdn.example.com"]'
|
||
echo "fetch('https://cdn.example.com/apps/selftest-app/1.0.0/core.data');" >>"$D/web/src/main.tsx"
|
||
expect "合规声明不报非法" "$D" 'embedCsp 非法' 0
|
||
expect "合规声明不报超限" "$D" 'dist 打 tgz 后' 0
|
||
expect "合规声明不报死配置" "$D" '找不到使用点' 0
|
||
expect "输出始终带结论行" "$D" '^RESULT: (PASS|FAIL)$' 1
|
||
|
||
# 5) 免提示自检入口:无 AGENTS.md → warn;有且含 check-app.sh → 不再 warn。
|
||
expect "缺 AGENTS.md 提示" "$D" '缺 AGENTS.md 自检硬规则' 1
|
||
printf '# AGENTS.md\n开工前先跑 `bash .agents/skills/app-dev/scripts/check-app.sh .`。\n' >"$D/AGENTS.md"
|
||
expect "有 AGENTS.md 不提示" "$D" '缺 AGENTS.md 自检硬规则' 0
|
||
|
||
echo "---"
|
||
echo "selftest: $N 项,失败 $BAD"
|
||
[ "$BAD" -gt 0 ] && exit 1
|
||
exit 0
|