Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

### 修复

- 修复 Codex Desktop 26.721.x 首页在 `home-icon` 延迟渲染时被误判为注入校验失败的问题(#306)。校验现在复用已由首页内容信号解析出的 `[role="main"]` 容器;严格的 `home-icon` 路径仍优先,旧版行为不变。
- 修复显式浅色、宽幅 artwork 的 `taskMode: "full"` 任务页 Markdown 继承原生深色壳白色前景的问题(#309)。完整模式现与 ambient/banner 一样强制使用主题文本色,并保留浅色壳对比阴影。
- 修复社区主题 ZIP 导入拒绝 `backdrop-filter: blur(var(--ds-theme-surface-blur))` 的问题(#312)。Safe CSS 仍只允许 `none`、0-20px blur 或注册的主题 blur 变量,不放宽到任意 filter 函数。

## 1.5.6 — 2026-07-26

### 安全
Expand Down
2 changes: 1 addition & 1 deletion macos/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.6
1.5.7
4 changes: 2 additions & 2 deletions macos/assets/dream-skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -444,15 +444,15 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-
backdrop-filter: none !important;
}

html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]
html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"], [data-dream-task-mode="full"], [data-dream-art-task-mode="full"])[data-dream-art-wide="true"]
main.main-surface:not(:has([role="main"])) [class*="_markdown"] {
color: var(--ds-text) !important;
text-shadow:
0 1px 2px rgb(var(--ds-bg-rgb) / .82),
0 0 10px rgb(var(--ds-bg-rgb) / .58);
}

html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]
html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"], [data-dream-task-mode="full"], [data-dream-art-task-mode="full"])[data-dream-art-wide="true"]
main.main-surface:not(:has([role="main"])) [class*="_markdown"] {
text-shadow:
0 1px 2px rgb(var(--ds-panel-rgb) / .92),
Expand Down
2 changes: 1 addition & 1 deletion macos/assets/safe-css-validator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ function validatePropertyValue(property, value) {
}
if (property === "backdrop-filter") {
if (value.toLowerCase() === "none") return true;
const match = value.match(/^blur\(\s*([^\s)]+)\s*\)$/i);
const match = value.match(/^blur\(\s*(.+?)\s*\)$/i);
return Boolean(match && (
registeredVar(match[1], new Set(["--ds-theme-surface-blur"]))
|| zeroOrPx(match[1], 0, 20)
Expand Down
2 changes: 1 addition & 1 deletion macos/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex-dream-skin-studio",
"version": "1.5.6",
"version": "1.5.7",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion macos/scripts/common-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ CODEX_APP_JOB_LABEL="com.openai.codex-dream-skin-studio.app"
INJECTOR_JOB_LABEL="com.openai.codex-dream-skin-studio.injector"
EXPECTED_CODEX_TEAM_ID="2DC432GLL2"
EXPECTED_CODEX_REQUIREMENT="anchor apple generic and certificate leaf[subject.OU] = \"$EXPECTED_CODEX_TEAM_ID\""
SKIN_VERSION="1.5.6"
SKIN_VERSION="1.5.7"
DREAM_SKIN_VALIDATED_RUNTIME_PID=""
DREAM_SKIN_VALIDATED_RUNTIME_BUNDLE=""
DREAM_SKIN_VALIDATED_RUNTIME_EXE=""
Expand Down
7 changes: 5 additions & 2 deletions macos/scripts/injector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const stableTestidLiteral = (testid) => {
}
return JSON.stringify(`[data-testid="${testid}"]`);
};
const SKIN_VERSION = "1.5.6";
const SKIN_VERSION = "1.5.7";
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
const CDP_ID_PATTERN = /^[A-Za-z0-9._-]{1,200}$/;
const MAX_ART_BYTES = 10 * 1024 * 1024;
Expand Down Expand Up @@ -1086,7 +1086,10 @@ async function verifySession(session, expectedThemeId = null, expectedRevision =
const homeSignal = homeIndicator ?? document.querySelector(${selectorLiteral("game-source")}) ??
document.querySelector(${selectorLiteral("home-suggestions")});
const homeRoute = homeSignal?.closest('[role="main"]') ?? null;
const home = document.querySelector(${selectorLiteral("home-route")});
// Codex 26.721.x can render the home content before home-icon. Reuse the
// already-resolved semantic home container so a healthy home session is
// not rejected solely because the stricter home-icon selector is late.
const home = document.querySelector(${selectorLiteral("home-route")}) ?? homeRoute;
const suggestions = home?.querySelector(${selectorLiteral("home-suggestions")}) ?? null;
const cardButtons = suggestions ? [...suggestions.querySelectorAll('button')] : [];
const cardBoxes = cardButtons.map(box);
Expand Down
4 changes: 2 additions & 2 deletions macos/tests/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ UPDATE_JSON="$({
})"
"$NODE" -e '
const value = JSON.parse(process.argv[1]);
if (value.currentVersion !== "v1.5.6" || value.latestVersion !== "v9.8.7") process.exit(1);
if (value.currentVersion !== "v1.5.7" || value.latestVersion !== "v9.8.7") process.exit(1);
if (!value.updateAvailable) process.exit(1);
if (value.releaseUrl !== "https://github.com/Fei-Away/Codex-Dream-Skin/releases/latest") process.exit(1);
' "$UPDATE_JSON"
Expand Down Expand Up @@ -1071,7 +1071,7 @@ CRLF_BACKUP="$TMP/config-crlf-backup.json"
"$NODE" "$ROOT/scripts/theme-config.mjs" restore "$CRLF_CONFIG" "$CRLF_BACKUP" >/dev/null
/usr/bin/cmp -s "$CRLF_CONFIG" "$TMP/original-crlf.toml"

/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.6" ]' _ "$ROOT"
/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.7" ]' _ "$ROOT"
if [ "${CODEX_DREAM_SKIN_SKIP_DOCTOR:-0}" = "1" ]; then
printf 'SKIP: Doctor requires an installed, signed Codex app.\n'
DOCTOR_RESULT="skipped"
Expand Down
14 changes: 14 additions & 0 deletions macos/tests/runtime-css-nested-has.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,18 @@ for (const file of files) {
const findings = findNestedHas(css);
assert.deepEqual(findings, [], `nested :has() found in ${file}`);
});

test(`wide full-mode Markdown retains themed light text in ${file}`, () => {
const css = readFileSync(join(root, file), "utf8");
const selectorToken = file.startsWith("runtime/")
? "__DREAM_SELECTOR_SHELL_MAIN__:not\\(:has\\(__DREAM_SELECTOR_HOME_ROUTE_CSS__\\)\\) __DREAM_SELECTOR_MARKDOWN__"
: "main\\.main-surface:not\\(:has\\(\\[role=\"main\"\\]\\)\\) \\[class\\*=\"_markdown\"\\]";
const fullMode = ':is\\([^)]*\\[data-dream-task-mode="full"\\][^)]*\\[data-dream-art-task-mode="full"\\][^)]*\\)\\[data-dream-art-wide="true"\\]';
const markdownRule = new RegExp(`${fullMode}\\s*\\n?\\s*${selectorToken}\\s*\\{\\s*\\n?\\s*color:\\s*var\\(--ds-text\\)\\s*!important;`);
const lightShadowRule = new RegExp(`\\[data-dream-shell="light"\\]${fullMode}\\s*\\n?\\s*${selectorToken}\\s*\\{\\s*\\n?\\s*text-shadow:`);
assert.match(css, markdownRule,
"Full task mode must override native dark-shell Markdown foreground (#309).");
assert.match(css, lightShadowRule,
"Full task mode must retain the light-shell Markdown contrast shadow (#309).");
});
}
2 changes: 1 addition & 1 deletion macos/tests/safe-css-validator.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test("accepts only the bounded public part/property/value contract", () => {
border-style: solid;
border-radius: 12px;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(18px);
backdrop-filter: blur(var(--ds-theme-surface-blur));
transition-property: background-color, border-color, box-shadow;
transition-duration: 180ms;
}
Expand Down
20 changes: 20 additions & 0 deletions macos/tests/window-readiness.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const [startSource, commonSource] = await Promise.all([
fs.readFile(startPath, "utf8"),
fs.readFile(commonPath, "utf8"),
]);
const injectorSource = await fs.readFile(path.join(macosRoot, "scripts", "injector.mjs"), "utf8");

const exactPayload = {
skinVersion: "test-version",
Expand Down Expand Up @@ -56,6 +57,25 @@ const baseRenderer = {
assert.equal(readyNativeWindow.status, "ready");
assert.equal(assessRendererVerification(baseRenderer, readyNativeWindow, exactPayload).pass, true);

// Codex 26.721.x sometimes renders game-source/home suggestions before
// home-icon. In that interval homeRoute is already a real [role=main], so
// verification must use it when the stricter home-route selector is late.
assert.match(
injectorSource,
/const home = document\.querySelector\(\$\{selectorLiteral\("home-route"\)\}\) \?\? homeRoute;/,
"Home verification must fall back to the already-resolved semantic home route (#306).",
);
assert.equal(
assessRendererVerification({
...baseRenderer,
homeRoute: true,
homePresent: true,
hero: { visible: true, width: 800, height: 520 },
}, readyNativeWindow, exactPayload).pass,
true,
"A visible fallback home container must satisfy the ordinary home verification gate.",
);

const windowCalls = [];
assert.equal((await inspectNativeWindow({
target: { id: "target-main" },
Expand Down
4 changes: 2 additions & 2 deletions runtime/dream-skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -444,15 +444,15 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-
backdrop-filter: none !important;
}

html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]
html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"], [data-dream-task-mode="full"], [data-dream-art-task-mode="full"])[data-dream-art-wide="true"]
__DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_MARKDOWN__ {
color: var(--ds-text) !important;
text-shadow:
0 1px 2px rgb(var(--ds-bg-rgb) / .82),
0 0 10px rgb(var(--ds-bg-rgb) / .58);
}

html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]
html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"], [data-dream-task-mode="full"], [data-dream-art-task-mode="full"])[data-dream-art-wide="true"]
__DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_MARKDOWN__ {
text-shadow:
0 1px 2px rgb(var(--ds-panel-rgb) / .92),
Expand Down
2 changes: 1 addition & 1 deletion runtime/safe-css-validator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ function validatePropertyValue(property, value) {
}
if (property === "backdrop-filter") {
if (value.toLowerCase() === "none") return true;
const match = value.match(/^blur\(\s*([^\s)]+)\s*\)$/i);
const match = value.match(/^blur\(\s*(.+?)\s*\)$/i);
return Boolean(match && (
registeredVar(match[1], new Set(["--ds-theme-surface-blur"]))
|| zeroOrPx(match[1], 0, 20)
Expand Down
8 changes: 8 additions & 0 deletions windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Windows Changelog

## Unreleased

### 修复

- 修复 v1.5.6 安装器在部分 Windows 10/11 环境中校验自带 Node.js 签名时,PowerShell 自动加载 `Microsoft.PowerShell.Security` / `Get-AuthenticodeSignature` 失败而中止安装的问题(#313、#314)。签名校验现在会在执行 `node.exe` 前显式加载安全模块,并在模块名加载失败时回退到 `$PSHOME` 下的系统模块清单路径;签名状态和发行者校验仍保持 fail-closed。
- 修复社区主题一键换肤和 ZIP 导入拒绝 `backdrop-filter: blur(var(--ds-theme-surface-blur))` 的问题(#307、#312)。Safe CSS 仍只允许 `none`、0-20px blur 或注册的主题 blur 变量,不放宽到任意 filter 函数。
- 修复 Codex Desktop 26.721.x 首页在 `home-icon` 延迟渲染时被误判为注入校验失败的问题(#307)。Windows 校验现在与 macOS 一样复用已由首页内容信号解析出的 `[role="main"]` 容器;严格的 `home-icon` 路径仍优先,旧版行为不变。

## 1.5.6 — 2026-07-26

### 安全
Expand Down
2 changes: 1 addition & 1 deletion windows/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.6
1.5.7
4 changes: 2 additions & 2 deletions windows/assets/dream-skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -444,15 +444,15 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-
backdrop-filter: none !important;
}

html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]
html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"], [data-dream-task-mode="full"], [data-dream-art-task-mode="full"])[data-dream-art-wide="true"]
main.main-surface:not(:has([role="main"])) [class*="_markdown"] {
color: var(--ds-text) !important;
text-shadow:
0 1px 2px rgb(var(--ds-bg-rgb) / .82),
0 0 10px rgb(var(--ds-bg-rgb) / .58);
}

html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]
html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"], [data-dream-task-mode="full"], [data-dream-art-task-mode="full"])[data-dream-art-wide="true"]
main.main-surface:not(:has([role="main"])) [class*="_markdown"] {
text-shadow:
0 1px 2px rgb(var(--ds-panel-rgb) / .92),
Expand Down
2 changes: 1 addition & 1 deletion windows/assets/safe-css-validator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ function validatePropertyValue(property, value) {
}
if (property === "backdrop-filter") {
if (value.toLowerCase() === "none") return true;
const match = value.match(/^blur\(\s*([^\s)]+)\s*\)$/i);
const match = value.match(/^blur\(\s*(.+?)\s*\)$/i);
return Boolean(match && (
registeredVar(match[1], new Set(["--ds-theme-surface-blur"]))
|| zeroOrPx(match[1], 0, 20)
Expand Down
19 changes: 19 additions & 0 deletions windows/scripts/common-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -419,12 +419,31 @@ function Invoke-DreamSkinNative {
}
}

function Import-DreamSkinPowerShellSecurityModule {
$command = Get-Command Get-AuthenticodeSignature -CommandType Cmdlet -ErrorAction SilentlyContinue
if ($command) { return }
try {
Import-Module Microsoft.PowerShell.Security -ErrorAction Stop
} catch {
$modulePath = Join-Path $PSHOME 'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1'
if (-not (Test-Path -LiteralPath $modulePath -PathType Leaf)) {
throw "PowerShell security module is unavailable: $($_.Exception.Message)"
}
Import-Module $modulePath -ErrorAction Stop
}
$command = Get-Command Get-AuthenticodeSignature -CommandType Cmdlet -ErrorAction SilentlyContinue
if (-not $command) {
throw 'PowerShell security module loaded, but Get-AuthenticodeSignature is unavailable.'
}
}

function Assert-DreamSkinTrustedNodeImage {
param([Parameter(Mandatory = $true)][string]$Path)

# Runs BEFORE the binary is ever executed. Get-DreamSkinValidatedNodeRuntime
# learns the version by running `node -p`, so any authenticity check placed
# after that point would already have executed attacker-controlled code.
Import-DreamSkinPowerShellSecurityModule
$signature = Get-AuthenticodeSignature -LiteralPath $Path -ErrorAction Stop
if ("$($signature.Status)" -ine 'Valid') {
throw "The Node.js runtime is not validly signed: $Path ($($signature.Status))."
Expand Down
11 changes: 9 additions & 2 deletions windows/scripts/injector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const stableTestidLiteral = (testid) => {
}
return JSON.stringify(`[data-testid="${testid}"]`);
};
const SKIN_VERSION = "1.5.6";
const SKIN_VERSION = "1.5.7";
const MAX_ART_BYTES = 10 * 1024 * 1024;
const MAX_SAFE_CSS_BYTES = 256 * 1024;
const STRONG_THEME_AUDIT_MS = 30000;
Expand Down Expand Up @@ -1145,7 +1145,14 @@ export async function verifySession(
visible: Boolean(node.isConnected !== false && cssVisible && intersectsViewport),
};
};
const home = document.querySelector(${selectorLiteral("home-route")});
const homeIndicator = document.querySelector(${selectorLiteral("home-icon")});
const homeSignal = homeIndicator ?? document.querySelector(${selectorLiteral("game-source")}) ??
document.querySelector(${selectorLiteral("home-suggestions")});
const homeRoute = homeSignal?.closest('[role="main"]') ?? null;
// Codex 26.721.x can render the home content before home-icon. Reuse the
// already-resolved semantic home container so a healthy home session is
// not rejected solely because the stricter home-icon selector is late.
const home = document.querySelector(${selectorLiteral("home-route")}) ?? homeRoute;
const settingsAnchor = document.querySelector(${selectorLiteral("appearance-radio")}) ||
document.querySelector(${stableTestidLiteral("theme-preview")});
const suggestions = home?.querySelector(${selectorLiteral("home-suggestions")}) ?? null;
Expand Down
24 changes: 23 additions & 1 deletion windows/tests/injector-window-readiness.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ const selectors = {
shell: "main.main-surface",
sidebar: "aside.app-shell-left-panel",
composer: ".composer-surface-chrome",
homeIcon: '[data-testid="home-icon"]',
home: '[role="main"]:has([data-testid="home-icon"])',
gameSource: '[data-feature="game-source"]',
suggestions: ".group\\/home-suggestions",
settings: 'input[name="appearance-theme"]',
themePreview: '[data-testid="theme-preview"]',
};
Expand Down Expand Up @@ -51,6 +54,7 @@ function makeDomFixture({
sidebar = makeElement(),
composer = makeElement(),
home = null,
homeSignal = null,
settings = null,
visibilityState = "visible",
hidden = false,
Expand All @@ -74,7 +78,9 @@ function makeDomFixture({
if (selector === selectors.shell) return shell;
if (selector === selectors.sidebar) return sidebar;
if (selector === selectors.composer) return composer;
if (selector === selectors.homeIcon) return null;
if (selector === selectors.home) return home;
if (selector === selectors.gameSource || selector === selectors.suggestions) return homeSignal;
if (selector === selectors.settings || selector === selectors.themePreview) return settings;
return null;
},
Expand All @@ -83,7 +89,7 @@ function makeDomFixture({
};
const window = {
__CODEX_DREAM_SKIN_STATE__: {
version: "1.5.6",
version: "1.5.7",
themeId: "fixture-theme",
revision: "fixture-revision",
styleMode: "style",
Expand Down Expand Up @@ -178,6 +184,22 @@ test("visible settings and home anchors are the only L0 structure exceptions", a
});
assert.equal(home.result.pass, true);

const fallbackHome = makeHome({ rect: makeRect(900, 650, 20, 20) });
const lateHomeIconSignal = {
closest: (selector) => selector === '[role="main"]' ? fallbackHome : null,
};
const lateHomeIcon = await verify({
dom: makeDomFixture({
scope: { level: "L0", baseState: "home" },
shell: null,
sidebar: null,
home: null,
homeSignal: lateHomeIconSignal,
}),
});
assert.equal(lateHomeIcon.result.homePresent, true);
assert.equal(lateHomeIcon.result.pass, true);

const noAnchor = await verify({
dom: makeDomFixture({
scope: { level: "L0", baseState: "settings" },
Expand Down
Loading
Loading