mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: 避免 macOS unsigned 包携带残缺签名 (#2101)
* fix: mitigate broken signatures in unsigned macOS packages * fix: normalize app signatures before DMG packaging
This commit is contained in:
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -31,6 +31,7 @@ jobs:
|
||||
- 'src/brokers/futu/**'
|
||||
- 'scripts/build-backend.ps1'
|
||||
- 'scripts/build-backend-macos.sh'
|
||||
- 'scripts/macos-signature-audit.sh'
|
||||
- 'scripts/build-all.ps1'
|
||||
- 'scripts/build-all-macos.sh'
|
||||
- 'apps/dsa-desktop/**'
|
||||
@@ -176,6 +177,8 @@ jobs:
|
||||
run: npm ci --prefix apps/dsa-web
|
||||
- name: 🧊 Build and verify frozen backend
|
||||
run: bash scripts/build-backend-macos.sh
|
||||
- name: 📦 Build and verify unsigned desktop package
|
||||
run: bash scripts/build-desktop-macos.sh
|
||||
|
||||
web-gate:
|
||||
name: web-gate
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"afterPack": "scripts/afterPackMacos.js",
|
||||
"files": [
|
||||
"main.js",
|
||||
"preload.js",
|
||||
@@ -54,6 +55,8 @@
|
||||
"include": "installer.nsh"
|
||||
},
|
||||
"mac": {
|
||||
"identity": null,
|
||||
"hardenedRuntime": false,
|
||||
"target": "dmg"
|
||||
}
|
||||
}
|
||||
|
||||
25
apps/dsa-desktop/scripts/afterPackMacos.js
Normal file
25
apps/dsa-desktop/scripts/afterPackMacos.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const path = require('node:path')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
|
||||
exports.default = async function afterPackMacos(context) {
|
||||
if (context.electronPlatformName !== 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
const appPath = path.join(
|
||||
context.appOutDir,
|
||||
`${context.packager.appInfo.productFilename}.app`,
|
||||
)
|
||||
const auditScript = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'scripts',
|
||||
'macos-signature-audit.sh',
|
||||
)
|
||||
|
||||
execFileSync('bash', [auditScript, 'normalize', appPath], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
}
|
||||
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
> For user-friendly release highlights, see the [GitHub Releases](https://github.com/ZhuLinsen/daily_stock_analysis/releases) page.
|
||||
|
||||
## [Unreleased]
|
||||
- [修复] macOS unsigned 打包显式禁用 Electron 签名与 Hardened Runtime,在冻结后端首次执行前及 electron-builder `afterPack` 阶段清理残缺签名,并对原始应用和 DMG 挂载产物执行签名审计,避免再次发布带损坏签名的桌面包;该缓解不替代 Apple Developer 签名与公证(refs #2075)。
|
||||
- [修复] WebUI 分开展示发布版本、代码版本与构建时间,并通过构建输入摘要识别 `rsync -a` 保留时间戳造成的旧静态资源复用(fixes #2093)。
|
||||
- [chore] 暂停 PR Review 的自动触发,仅保留 `workflow_dispatch` 手动入口,避免辅助评审重复运行及评论权限失败产生误导性红灯;正式 CI 检查保持不变。
|
||||
- [新功能] Multi-Agent specialist 运行在分析历史保存成功后,按独立 skill 持久化版本化、低敏且幂等的有效 opinion 样本,为后续后验评估提供真实数据;本阶段不计算 outcome、不统计表现、不调整权重。
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
- Windows 安装包:双击 `.exe`,按提示安装,安装目录用默认位置即可。
|
||||
- Windows 免安装包:解压 `.zip`,双击 `Daily Stock Analysis.exe`。
|
||||
- macOS:双击 `.dmg`,把应用拖到 `Applications`。如果提示来自未验证开发者,在系统设置的隐私与安全性里允许打开。
|
||||
- macOS:双击 `.dmg`,把应用拖到 `Applications`。当前 DMG 未经 Apple Developer 签名和公证,Gatekeeper 仍可能阻止启动;仅对 GitHub Releases 官方附件尝试在“隐私与安全性”中允许打开,完整限制与排查方式见 `docs/desktop-package.md`。
|
||||
|
||||
macOS 用户升级前建议先在客户端设置里导出一次配置备份。
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ powershell -ExecutionPolicy Bypass -File scripts\build-all.ps1
|
||||
|
||||
### macOS 提示“应用已损坏,无法打开”
|
||||
|
||||
当前 macOS DMG 尚未使用 Apple Developer 证书签名和公证。通过浏览器下载后,macOS Gatekeeper 可能因此提示“Daily Stock Analysis 已损坏,无法打开”或“无法验证开发者”;这通常是系统对未签名、未公证应用的拦截,不代表 DMG 文件必然损坏。
|
||||
当前 macOS DMG 尚未使用 Apple Developer 证书签名和公证。构建配置会显式生成 unsigned 应用,在 PyInstaller 产物首次执行前清理残缺签名,并通过 electron-builder `afterPack` hook 在 DMG 创建前再次清理完整 `.app`;CI 还会检查 Electron 原始 `.app` 和 DMG 挂载后的 `.app`,阻止再次发布带有 `code has no resources but signature indicates they must be present` 等损坏签名的产物。该处理只能缓解 v3.27.0 的残缺签名缺陷,**不会让应用获得 Apple 信任**。通过浏览器下载后,macOS Gatekeeper 仍可能提示“无法验证开发者”、阻止启动,或要求用户人工确认。
|
||||
|
||||
请按以下顺序排查:
|
||||
|
||||
@@ -90,7 +90,16 @@ powershell -ExecutionPolicy Bypass -File scripts\build-all.ps1
|
||||
xattr -dr com.apple.quarantine "/Applications/Daily Stock Analysis.app"
|
||||
```
|
||||
|
||||
如果应用不在 `/Applications`,请将命令中的路径替换为实际 `.app` 路径。不要对整个“应用程序”目录执行 `xattr`,也不要对来源不明的应用执行此命令。长期彻底消除该提示需要在发布流程中接入 Apple Developer 签名与 notarization(公证),不属于上述临时放行步骤。
|
||||
如果应用不在 `/Applications`,请将命令中的路径替换为实际 `.app` 路径。不要对整个“应用程序”目录执行 `xattr`,也不要对来源不明的应用执行此命令。不同 macOS 版本可能仍拒绝 unsigned 应用,清除 quarantine 不保证能够放行。长期彻底消除该提示需要在发布流程中接入 Apple Developer 签名与 notarization(公证),不属于上述临时放行步骤。
|
||||
|
||||
维护者可用以下命令区分“预期的 unsigned 拒绝”和“不可发布的残缺签名”:
|
||||
|
||||
```bash
|
||||
codesign -d "/Applications/Daily Stock Analysis.app"
|
||||
spctl --assess --type execute --verbose=4 "/Applications/Daily Stock Analysis.app"
|
||||
```
|
||||
|
||||
当前 unsigned 产物的 `codesign -d` 预期包含 `code object is not signed at all`,`spctl` 预期拒绝;如果输出 `code has no resources but signature indicates they must be present` 或其它签名损坏信息,应视为发布阻断。
|
||||
|
||||
建议发布流程:
|
||||
|
||||
|
||||
@@ -128,9 +128,12 @@ popd >/dev/null
|
||||
|
||||
cp -R "${ROOT_DIR}/dist/stock_analysis" "${ROOT_DIR}/dist/backend/stock_analysis"
|
||||
|
||||
log "Verifying packaged runtime imports..."
|
||||
packaged_root="${ROOT_DIR}/dist/backend/stock_analysis"
|
||||
|
||||
log "Removing invalid signatures before the packaged backend is executed..."
|
||||
bash "${SCRIPT_DIR}/macos-signature-audit.sh" normalize "${packaged_root}"
|
||||
|
||||
log "Verifying packaged runtime imports..."
|
||||
packaged_entry="${packaged_root}/stock_analysis"
|
||||
if [[ ! -x "${packaged_entry}" ]]; then
|
||||
echo "ERROR: packaged backend entrypoint not found or not executable: ${packaged_entry}."
|
||||
|
||||
@@ -7,6 +7,69 @@ ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
export CSC_IDENTITY_AUTO_DISCOVERY="false"
|
||||
export ELECTRON_BUILDER_CACHE="${ROOT_DIR}/.electron-builder-cache"
|
||||
|
||||
verify_expected_unsigned_app() {
|
||||
local app_path="$1"
|
||||
local signature_details=""
|
||||
local assessment_output=""
|
||||
|
||||
bash "${SCRIPT_DIR}/macos-signature-audit.sh" check "${app_path}"
|
||||
|
||||
if signature_details="$(codesign -d "${app_path}" 2>&1)"; then
|
||||
echo "ERROR: expected an unsigned application bundle, but a signature was found: ${app_path}"
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${signature_details}" != *"code object is not signed at all"* ]]; then
|
||||
echo "ERROR: application bundle has an unreadable or invalid signature: ${app_path}"
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if assessment_output="$(spctl --assess --type execute --verbose=4 "${app_path}" 2>&1)"; then
|
||||
echo "WARNING: Gatekeeper accepted an explicitly unsigned application: ${app_path}"
|
||||
echo "${assessment_output}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "${assessment_output}"
|
||||
if [[ "${assessment_output}" == *"code has no resources but signature indicates they must be present"* ]]; then
|
||||
echo "ERROR: Gatekeeper detected the broken-signature defect reported in issue #2075." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "WARNING: Gatekeeper rejection is expected because this build has no Apple Developer signature."
|
||||
}
|
||||
|
||||
verify_unsigned_dmg() {
|
||||
local dmg_path="$1"
|
||||
local mount_dir=""
|
||||
local mounted_app=""
|
||||
local mounted=false
|
||||
|
||||
mount_dir="$(mktemp -d "${TMPDIR:-/tmp}/dsa-unsigned-dmg.XXXXXX")"
|
||||
cleanup_mount() {
|
||||
if [[ "${mounted}" == "true" ]]; then
|
||||
hdiutil detach "${mount_dir}" >/dev/null || true
|
||||
fi
|
||||
rmdir "${mount_dir}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup_mount EXIT
|
||||
|
||||
hdiutil attach "${dmg_path}" -nobrowse -readonly -mountpoint "${mount_dir}" >/dev/null
|
||||
mounted=true
|
||||
mounted_app="${mount_dir}/Daily Stock Analysis.app"
|
||||
if [[ ! -d "${mounted_app}" ]]; then
|
||||
echo "ERROR: application bundle not found in mounted DMG: ${mounted_app}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_expected_unsigned_app "${mounted_app}"
|
||||
|
||||
hdiutil detach "${mount_dir}" >/dev/null
|
||||
mounted=false
|
||||
rmdir "${mount_dir}"
|
||||
trap - EXIT
|
||||
}
|
||||
|
||||
echo "Building Electron desktop app (macOS)..."
|
||||
|
||||
if [[ ! -d "${ROOT_DIR}/dist/backend/stock_analysis" ]]; then
|
||||
@@ -64,6 +127,10 @@ if compgen -G "dist/mac*" >/dev/null; then
|
||||
echo "Cleaning dist/mac*..."
|
||||
rm -rf dist/mac*
|
||||
fi
|
||||
if compgen -G "dist/*.dmg" >/dev/null; then
|
||||
echo "Cleaning stale dist/*.dmg..."
|
||||
rm -f dist/*.dmg
|
||||
fi
|
||||
|
||||
MAC_ARCH="${DSA_MAC_ARCH:-}"
|
||||
ARCH_ARGS=()
|
||||
@@ -85,6 +152,24 @@ if [[ ${#ARCH_ARGS[@]} -gt 0 ]]; then
|
||||
else
|
||||
npx electron-builder --mac dmg --publish never
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
app_candidates=(dist/mac*/"Daily Stock Analysis.app")
|
||||
dmg_candidates=(dist/*.dmg)
|
||||
shopt -u nullglob
|
||||
|
||||
if [[ "${#app_candidates[@]}" -ne 1 ]]; then
|
||||
echo "ERROR: expected one unpacked macOS app, found ${#app_candidates[@]}."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${#dmg_candidates[@]}" -ne 1 ]]; then
|
||||
echo "ERROR: expected one macOS DMG, found ${#dmg_candidates[@]}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_expected_unsigned_app "${app_candidates[0]}"
|
||||
verify_unsigned_dmg "${dmg_candidates[0]}"
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
echo "Desktop build completed."
|
||||
|
||||
93
scripts/macos-signature-audit.sh
Executable file
93
scripts/macos-signature-audit.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <normalize|check> <artifact-path>" >&2
|
||||
}
|
||||
|
||||
mode="${1:-}"
|
||||
artifact_root="${2:-}"
|
||||
|
||||
if [[ "${mode}" != "normalize" ]] && [[ "${mode}" != "check" ]]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "${artifact_root}" ]] || [[ ! -e "${artifact_root}" ]]; then
|
||||
echo "ERROR: macOS signature audit target does not exist: ${artifact_root:-<empty>}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v codesign >/dev/null 2>&1; then
|
||||
echo "ERROR: codesign is required for macOS signature auditing." >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v file >/dev/null 2>&1; then
|
||||
echo "ERROR: file is required for macOS signature auditing." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
checked_count=0
|
||||
signed_count=0
|
||||
removed_count=0
|
||||
|
||||
remove_broken_signature() {
|
||||
local candidate="$1"
|
||||
local signature_details=""
|
||||
|
||||
echo "WARNING: removing invalid signature from unsigned macOS artifact: ${candidate}"
|
||||
codesign --remove-signature "${candidate}"
|
||||
removed_count=$((removed_count + 1))
|
||||
|
||||
signature_details="$(codesign -d "${candidate}" 2>&1 || true)"
|
||||
if [[ "${signature_details}" != *"code object is not signed at all"* ]]; then
|
||||
echo "ERROR: failed to remove invalid signature: ${candidate}" >&2
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
audit_candidate() {
|
||||
local candidate="$1"
|
||||
local signature_details=""
|
||||
|
||||
checked_count=$((checked_count + 1))
|
||||
if ! signature_details="$(codesign -d "${candidate}" 2>&1)"; then
|
||||
if [[ "${signature_details}" == *"code object is not signed at all"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "${mode}" == "normalize" ]]; then
|
||||
remove_broken_signature "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
echo "ERROR: unreadable or invalid signature in macOS artifact: ${candidate}" >&2
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
signed_count=$((signed_count + 1))
|
||||
if codesign --verify --strict --verbose=4 "${candidate}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${mode}" == "normalize" ]]; then
|
||||
remove_broken_signature "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "ERROR: invalid signature in macOS artifact: ${candidate}" >&2
|
||||
codesign --verify --strict --verbose=4 "${candidate}" || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
while IFS= read -r -d '' candidate; do
|
||||
if [[ -d "${candidate}" ]]; then
|
||||
audit_candidate "${candidate}"
|
||||
elif file -b "${candidate}" | grep -q "Mach-O"; then
|
||||
audit_candidate "${candidate}"
|
||||
fi
|
||||
done < <(
|
||||
find "${artifact_root}" -depth \
|
||||
\( -type f -o -type d \( -name "*.app" -o -name "*.framework" -o -name "*.xpc" \) \) \
|
||||
-print0
|
||||
)
|
||||
|
||||
echo "macOS signature audit complete: mode=${mode}, checked=${checked_count}, signed=${signed_count}, removed=${removed_count}"
|
||||
@@ -1,6 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Validation tests for backend packaging scripts."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -11,6 +15,15 @@ def _read_text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _bash_path(path: Path) -> str:
|
||||
resolved = path.resolve()
|
||||
if os.name != "nt":
|
||||
return str(resolved)
|
||||
drive = resolved.drive.rstrip(":").lower()
|
||||
relative = resolved.relative_to(resolved.anchor).as_posix()
|
||||
return f"/mnt/{drive}/{relative}"
|
||||
|
||||
|
||||
def test_windows_backend_build_script_collects_alphasift_adapter() -> None:
|
||||
script = _read_text(REPO_ROOT / "scripts" / "build-backend.ps1")
|
||||
main_py = _read_text(REPO_ROOT / "main.py")
|
||||
@@ -47,3 +60,143 @@ def test_macos_backend_build_script_collects_alphasift_adapter() -> None:
|
||||
assert 'normalized.startswith("alphasift/dsa_adapter.")' not in script
|
||||
assert "DSA_PACKAGED_IMPORT_PROBE" in main_py
|
||||
assert "importlib.import_module(_packaged_import_probe)" in main_py
|
||||
|
||||
|
||||
def test_macos_unsigned_packaging_contract_is_explicit() -> None:
|
||||
package = json.loads(
|
||||
_read_text(REPO_ROOT / "apps" / "dsa-desktop" / "package.json")
|
||||
)
|
||||
after_pack_hook = _read_text(
|
||||
REPO_ROOT / "apps" / "dsa-desktop" / "scripts" / "afterPackMacos.js"
|
||||
)
|
||||
backend_script = _read_text(REPO_ROOT / "scripts" / "build-backend-macos.sh")
|
||||
desktop_script = _read_text(REPO_ROOT / "scripts" / "build-desktop-macos.sh")
|
||||
workflow = _read_text(REPO_ROOT / ".github" / "workflows" / "ci.yml")
|
||||
|
||||
assert package["build"]["mac"]["identity"] is None
|
||||
assert package["build"]["mac"]["hardenedRuntime"] is False
|
||||
assert package["build"]["afterPack"] == "scripts/afterPackMacos.js"
|
||||
assert "context.electronPlatformName !== 'darwin'" in after_pack_hook
|
||||
assert "'macos-signature-audit.sh'" in after_pack_hook
|
||||
assert "execFileSync('bash', [auditScript, 'normalize', appPath]" in after_pack_hook
|
||||
normalize_call = (
|
||||
'bash "${SCRIPT_DIR}/macos-signature-audit.sh" normalize "${packaged_root}"'
|
||||
)
|
||||
assert normalize_call in backend_script
|
||||
assert backend_script.index(normalize_call) < backend_script.index(
|
||||
'"${packaged_entry}" --help'
|
||||
)
|
||||
assert 'bash "${SCRIPT_DIR}/macos-signature-audit.sh" check "${app_path}"' in (
|
||||
desktop_script
|
||||
)
|
||||
assert "verify_unsigned_dmg" in desktop_script
|
||||
assert "code has no resources but signature indicates they must be present" in (
|
||||
desktop_script
|
||||
)
|
||||
assert "- 'scripts/macos-signature-audit.sh'" in workflow
|
||||
assert "run: bash scripts/build-backend-macos.sh" in workflow
|
||||
assert "run: bash scripts/build-desktop-macos.sh" in workflow
|
||||
|
||||
|
||||
def _write_fake_macos_signature_tools(fake_bin: Path) -> None:
|
||||
fake_bin.mkdir()
|
||||
file_tool = fake_bin / "file"
|
||||
file_tool.write_text(
|
||||
"#!/usr/bin/env bash\nprintf 'Mach-O 64-bit executable\\n'\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
codesign_tool = fake_bin / "codesign"
|
||||
codesign_tool.write_text(
|
||||
"""#!/usr/bin/env bash
|
||||
candidate="${@: -1}"
|
||||
marker="${candidate}.removed"
|
||||
case "$1" in
|
||||
-d)
|
||||
if [[ -f "${marker}" ]] || [[ "${candidate}" == *"unsigned.bin" ]]; then
|
||||
printf 'code object is not signed at all\\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'Authority=adhoc\\n' >&2
|
||||
;;
|
||||
--verify)
|
||||
if [[ "${candidate}" == *"broken.bin" ]] && [[ ! -f "${marker}" ]]; then
|
||||
printf 'broken signature\\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--remove-signature)
|
||||
: > "${marker}"
|
||||
;;
|
||||
esac
|
||||
""",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
file_tool.chmod(0o755)
|
||||
codesign_tool.chmod(0o755)
|
||||
|
||||
|
||||
def test_macos_signature_audit_normalizes_invalid_signatures(tmp_path: Path) -> None:
|
||||
fake_bin = tmp_path / "bin"
|
||||
_write_fake_macos_signature_tools(fake_bin)
|
||||
artifact = tmp_path / "artifact"
|
||||
artifact.mkdir()
|
||||
broken = artifact / "broken.bin"
|
||||
broken.write_text("broken", encoding="utf-8")
|
||||
(artifact / "unsigned.bin").write_text("unsigned", encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
'PATH={fake_bin}:"$PATH"; export PATH; bash {script} normalize {artifact}'.format(
|
||||
fake_bin=shlex.quote(_bash_path(fake_bin)),
|
||||
script=shlex.quote(
|
||||
_bash_path(REPO_ROOT / "scripts" / "macos-signature-audit.sh")
|
||||
),
|
||||
artifact=shlex.quote(_bash_path(artifact)),
|
||||
),
|
||||
],
|
||||
cwd=REPO_ROOT,
|
||||
env=os.environ.copy(),
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert (artifact / "broken.bin.removed").is_file()
|
||||
assert "removed=1" in result.stdout
|
||||
|
||||
|
||||
def test_macos_signature_audit_rejects_invalid_signatures(tmp_path: Path) -> None:
|
||||
fake_bin = tmp_path / "bin"
|
||||
_write_fake_macos_signature_tools(fake_bin)
|
||||
artifact = tmp_path / "artifact"
|
||||
artifact.mkdir()
|
||||
broken = artifact / "broken.bin"
|
||||
broken.write_text("broken", encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
'PATH={fake_bin}:"$PATH"; export PATH; bash {script} check {artifact}'.format(
|
||||
fake_bin=shlex.quote(_bash_path(fake_bin)),
|
||||
script=shlex.quote(
|
||||
_bash_path(REPO_ROOT / "scripts" / "macos-signature-audit.sh")
|
||||
),
|
||||
artifact=shlex.quote(_bash_path(artifact)),
|
||||
),
|
||||
],
|
||||
cwd=REPO_ROOT,
|
||||
env=os.environ.copy(),
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert not (artifact / "broken.bin.removed").exists()
|
||||
assert "invalid signature" in result.stderr
|
||||
|
||||
Reference in New Issue
Block a user