mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
fix(release): pass the keychain password to set-key-partition-list on macOS
`Build on macos arm64` started failing on master with
security set-key-partition-list -S apple-tool:,apple: -s -k *** <tmp>.keychain
SecKeychainUnlock: The user name or passphrase you entered is not correct.
Every failure ran the `macos-26-arm64` runner image 20260831.0337; the same
job on image 20260728.0273 kept passing, as did `macos-15-intel`. The image
exposed an electron-builder bug: `app-builder-lib` hands the certificate's
`.p12` import password to `set-key-partition-list -k`, which authenticates
against the temporary keychain and therefore needs the keychain's own
generated password. Older macOS builds accepted the wrong password once the
keychain was unlocked; the new one verifies it.
Upstream fixed this in electron-userland/electron-builder#10101 (master,
v27 alpha) and backported it in #10172 to release/v26 on 2026-09-03, but no
26.x containing it is published (26.16.0 predates the backport, #10167).
Apply the backport to the installed 26.15.7 as a pnpm patch, guarded by
`tools/dependencies/app-builder-lib-keychain-password.test.mjs`: it checks
that the patched version is the installed one, that the compiled source
passes `keychainPassword`, and — with `security` recorded — that
`createKeychain` unlocks the partition list with the password it gave
`create-keychain`, not the import password. The test fails 2/3 on the
unpatched code. CI runs it next to the Vite patch guard; CLAUDE.md and
AGENTS.md document when to retire the patch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
50b980af7a
commit
52b33fe5a3
7 files changed
+240
-5
No files matched your search
@@ -153,6 +153,9 @@ jobs:
|
||||
- name: Validate Vite dev-server transform filter patch
|
||||
run: pnpm run deps:vite:test
|
||||
|
||||
- name: Validate electron-builder keychain password patch
|
||||
run: pnpm run deps:electron-builder:test
|
||||
|
||||
- name: Validate stylesheet Nx inputs
|
||||
run: pnpm run styles:inputs:validate
|
||||
|
||||
|
||||
@@ -24,6 +24,17 @@ This file provides guidance to coding agents working in this repository.
|
||||
`patches/vite@7.3.6.patch`. Keep the patch until supported Angular tooling
|
||||
resolves a Vite version containing the fix, and run `pnpm run deps:vite:test`
|
||||
after related dependency updates.
|
||||
- `app-builder-lib` `26.15.7` (electron-builder's macOS signing) is patched in
|
||||
`patches/app-builder-lib@26.15.7.patch` with the upstream backport
|
||||
electron-userland/electron-builder#10172: `security set-key-partition-list -k`
|
||||
must receive the temporary keychain's own password, not the `.p12` import
|
||||
password. macOS runner images since `macos-26-arm64` 20260831 verify that
|
||||
password, and `Build on macos arm64` failed with `SecKeychainUnlock: The user
|
||||
name or passphrase you entered is not correct`. Keep the patch until
|
||||
electron-builder resolves an `app-builder-lib` containing the fix (26.16.1+),
|
||||
and run `pnpm run deps:electron-builder:test` after related dependency
|
||||
updates — the test fails when the patched version no longer matches the
|
||||
installed one.
|
||||
- A directory holding files consumed by other projects must be an Nx project.
|
||||
Nx builds its graph from TypeScript imports only, so a relative SCSS `@use`
|
||||
across project roots creates no edge and the imported file lands in no task
|
||||
|
||||
@@ -85,6 +85,17 @@ pnpm nx show projects
|
||||
`patches/vite@7.3.6.patch`. Keep the patch until supported Angular tooling
|
||||
resolves a Vite version containing the fix, and run `pnpm run deps:vite:test`
|
||||
after related dependency updates.
|
||||
- `app-builder-lib` `26.15.7` (electron-builder's macOS signing) is patched in
|
||||
`patches/app-builder-lib@26.15.7.patch` with the upstream backport
|
||||
electron-userland/electron-builder#10172: `security set-key-partition-list -k`
|
||||
must receive the temporary keychain's own password, not the `.p12` import
|
||||
password. macOS runner images since `macos-26-arm64` 20260831 verify that
|
||||
password, and `Build on macos arm64` failed with `SecKeychainUnlock: The user
|
||||
name or passphrase you entered is not correct`. Keep the patch until
|
||||
electron-builder resolves an `app-builder-lib` containing the fix (26.16.1+),
|
||||
and run `pnpm run deps:electron-builder:test` after related dependency
|
||||
updates — the test fails when the patched version no longer matches the
|
||||
installed one.
|
||||
- A directory holding files consumed by other projects must be an Nx project.
|
||||
Nx builds its graph from TypeScript imports only, so a relative SCSS `@use`
|
||||
across project roots creates no edge and the imported file lands in no task
|
||||
|
||||
+3
-1
@@ -40,6 +40,7 @@
|
||||
"deps:nx:check": "node tools/dependencies/check-nx-version-sync.mjs",
|
||||
"deps:nx:validate": "pnpm run deps:nx:test && pnpm run deps:nx:check",
|
||||
"deps:vite:test": "node --test tools/dependencies/vite-transform-filter.test.mjs",
|
||||
"deps:electron-builder:test": "node --test tools/dependencies/app-builder-lib-keychain-password.test.mjs",
|
||||
"styles:inputs:test": "node --test tools/nx/check-stylesheet-inputs.test.mjs",
|
||||
"styles:inputs:check": "node tools/nx/check-stylesheet-inputs.mjs",
|
||||
"styles:inputs:validate": "pnpm run styles:inputs:test && pnpm run styles:inputs:check",
|
||||
@@ -277,7 +278,8 @@
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"nx-electron@22.0.0": "patches/nx-electron@22.0.0.patch",
|
||||
"vite@7.3.6": "patches/vite@7.3.6.patch"
|
||||
"vite@7.3.6": "patches/vite@7.3.6.patch",
|
||||
"app-builder-lib@26.15.7": "patches/app-builder-lib@26.15.7.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
diff --git a/out/codeSign/macCodeSign.js b/out/codeSign/macCodeSign.js
|
||||
index 9a69042fd48f4759a1c697bf23fa5b44f2da2366..193f42a8c4cb95ded694ba8ec0c27a5dcc94ed4c 100644
|
||||
--- a/out/codeSign/macCodeSign.js
|
||||
+++ b/out/codeSign/macCodeSign.js
|
||||
@@ -156,16 +156,18 @@ async function createKeychain({ tmpDir, cscLink, cscKeyPassword, cscILink, cscIK
|
||||
if (cscIKeyPassword != null) {
|
||||
cscPasswords.push(cscIKeyPassword);
|
||||
}
|
||||
- return await importCerts(keychainFile, certPaths, cscPasswords);
|
||||
+ return await importCerts(keychainFile, certPaths, cscPasswords, keychainPassword);
|
||||
}
|
||||
-async function importCerts(keychainFile, paths, keyPasswords) {
|
||||
+async function importCerts(keychainFile, paths, keyPasswords, keychainPassword) {
|
||||
var _a;
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const password = (_a = keyPasswords[i]) !== null && _a !== void 0 ? _a : "";
|
||||
await (0, builder_util_1.exec)("/usr/bin/security", ["import", paths[i], "-k", keychainFile, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", password]);
|
||||
// https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
|
||||
// https://github.com/electron-userland/electron-packager/issues/701#issuecomment-322315996
|
||||
- await (0, builder_util_1.exec)("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", password, keychainFile]);
|
||||
+ // `-k` expects the keychain's own unlock password (as used by create-keychain/unlock-keychain above),
|
||||
+ // not the imported item's password used by `security import -P`.
|
||||
+ await (0, builder_util_1.exec)("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", keychainPassword, keychainFile]);
|
||||
}
|
||||
return {
|
||||
keychainFile,
|
||||
Generated
+7
-4
@@ -43,6 +43,9 @@ overrides:
|
||||
yaml@1.10.2: 1.10.3
|
||||
|
||||
patchedDependencies:
|
||||
app-builder-lib@26.15.7:
|
||||
hash: 6114f0c543079e090166568e1ff0c28e406abb22bded6030663b7a3470dce83b
|
||||
path: patches/app-builder-lib@26.15.7.patch
|
||||
nx-electron@22.0.0:
|
||||
hash: f4bbde1778360c4c462b255bec47a337c0465d59cdcab14ecb601161f3467002
|
||||
path: patches/nx-electron@22.0.0.patch
|
||||
@@ -16709,7 +16712,7 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.2
|
||||
|
||||
app-builder-lib@26.15.7(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7):
|
||||
app-builder-lib@26.15.7(patch_hash=6114f0c543079e090166568e1ff0c28e406abb22bded6030663b7a3470dce83b)(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7):
|
||||
dependencies:
|
||||
'@electron/asar': 3.4.1
|
||||
'@electron/fuses': 1.8.0
|
||||
@@ -17859,7 +17862,7 @@ snapshots:
|
||||
|
||||
dmg-builder@26.15.7(electron-builder-squirrel-windows@26.15.7):
|
||||
dependencies:
|
||||
app-builder-lib: 26.15.7(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7)
|
||||
app-builder-lib: 26.15.7(patch_hash=6114f0c543079e090166568e1ff0c28e406abb22bded6030663b7a3470dce83b)(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7)
|
||||
builder-util: 26.15.3
|
||||
fs-extra: 10.1.0
|
||||
js-yaml: 4.3.1
|
||||
@@ -17943,7 +17946,7 @@ snapshots:
|
||||
|
||||
electron-builder-squirrel-windows@26.15.7(dmg-builder@26.15.7):
|
||||
dependencies:
|
||||
app-builder-lib: 26.15.7(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7)
|
||||
app-builder-lib: 26.15.7(patch_hash=6114f0c543079e090166568e1ff0c28e406abb22bded6030663b7a3470dce83b)(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7)
|
||||
builder-util: 26.15.3
|
||||
electron-winstaller: 5.4.0
|
||||
transitivePeerDependencies:
|
||||
@@ -17952,7 +17955,7 @@ snapshots:
|
||||
|
||||
electron-builder@26.15.7(electron-builder-squirrel-windows@26.15.7):
|
||||
dependencies:
|
||||
app-builder-lib: 26.15.7(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7)
|
||||
app-builder-lib: 26.15.7(patch_hash=6114f0c543079e090166568e1ff0c28e406abb22bded6030663b7a3470dce83b)(dmg-builder@26.15.7)(electron-builder-squirrel-windows@26.15.7)
|
||||
builder-util: 26.15.3
|
||||
builder-util-runtime: 9.7.0
|
||||
chalk: 4.1.2
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Guards `patches/app-builder-lib@<version>.patch`.
|
||||
*
|
||||
* electron-builder 26.15.x hands the certificate's `.p12` import password to
|
||||
* `security set-key-partition-list -k`, which authenticates against the
|
||||
* temporary keychain — so it needs the keychain's own generated password
|
||||
* (upstream #10066, fixed on master in #10101 and backported to release/v26 in
|
||||
* #10172, not yet in a published 26.x). macOS runner images since
|
||||
* `macos-26-arm64` 20260831 verify that password, and `Build on macos arm64`
|
||||
* failed with `SecKeychainUnlock: The user name or passphrase you entered is
|
||||
* not correct`. The patch applies the backport to the compiled package.
|
||||
*
|
||||
* Two checks: the installed source carries the fix (a dependency bump that
|
||||
* drops the patch must not silently reintroduce the bug), and the behavior
|
||||
* holds when `createKeychain` runs against a recorded `security` — the
|
||||
* partition-list call must use the password `create-keychain` was given, not
|
||||
* the import password.
|
||||
*
|
||||
* Retire this test together with the patch once electron-builder resolves an
|
||||
* `app-builder-lib` that contains #10172.
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
// app-builder-lib is a transitive dependency of electron-builder; pnpm's
|
||||
// strict layout keeps it out of the root node_modules, so resolve it the way
|
||||
// electron-builder itself does.
|
||||
const electronBuilderDir = path.dirname(
|
||||
require.resolve('electron-builder/package.json')
|
||||
);
|
||||
const appBuilderLibPackage = require.resolve('app-builder-lib/package.json', {
|
||||
paths: [electronBuilderDir],
|
||||
});
|
||||
const appBuilderLibDir = path.dirname(appBuilderLibPackage);
|
||||
const macCodeSignPath = path.join(
|
||||
appBuilderLibDir,
|
||||
'out/codeSign/macCodeSign.js'
|
||||
);
|
||||
|
||||
const IMPORT_PASSWORD = 'certificate-import-password';
|
||||
|
||||
describe('app-builder-lib keychain password patch', () => {
|
||||
it('targets the version the patch was written for', () => {
|
||||
const { version } = JSON.parse(
|
||||
readFileSync(appBuilderLibPackage, 'utf8')
|
||||
);
|
||||
const rootPackage = JSON.parse(
|
||||
readFileSync(new URL('../../package.json', import.meta.url), 'utf8')
|
||||
);
|
||||
const patched = Object.keys(
|
||||
rootPackage.pnpm?.patchedDependencies ?? {}
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
patched.includes(`app-builder-lib@${version}`),
|
||||
`installed app-builder-lib ${version} has no entry in pnpm.patchedDependencies (${patched.join(', ')}) — bump or retire the patch`
|
||||
);
|
||||
});
|
||||
|
||||
it('passes the keychain password, not the import password, to set-key-partition-list', () => {
|
||||
const source = readFileSync(macCodeSignPath, 'utf8');
|
||||
|
||||
assert.match(
|
||||
source,
|
||||
/"set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", keychainPassword, keychainFile/
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/importCerts\(keychainFile, certPaths, cscPasswords, keychainPassword\)/
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/"set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", password, keychainFile/
|
||||
);
|
||||
});
|
||||
|
||||
describe('createKeychain against a recorded security binary', () => {
|
||||
const calls = [];
|
||||
let cacheDir;
|
||||
let tmpRoot;
|
||||
let builderUtil;
|
||||
let originalExec;
|
||||
let macCodeSign;
|
||||
|
||||
before(() => {
|
||||
cacheDir = mkdtempSync(
|
||||
path.join(tmpdir(), 'app-builder-lib-cache-')
|
||||
);
|
||||
tmpRoot = mkdtempSync(path.join(tmpdir(), 'app-builder-lib-tmp-'));
|
||||
// Keep createKeychain's only real filesystem side effects (the bundled
|
||||
// root-certs keychain copy and the temp keychain path) out of the
|
||||
// user's cache and temp directories.
|
||||
process.env.ELECTRON_BUILDER_CACHE = cacheDir;
|
||||
process.env.APP_BUILDER_TMP_DIR = tmpRoot;
|
||||
|
||||
// The compiled code reads `exec` off builder-util's util module at
|
||||
// call time (`(0, builder_util_1.exec)(...)`), so replacing the
|
||||
// export records every `/usr/bin/security` invocation without
|
||||
// touching a real keychain.
|
||||
builderUtil = require(
|
||||
require.resolve('builder-util/out/util', {
|
||||
paths: [appBuilderLibDir],
|
||||
})
|
||||
);
|
||||
originalExec = builderUtil.exec;
|
||||
builderUtil.exec = async (file, args) => {
|
||||
calls.push({ file, args: [...(args ?? [])] });
|
||||
return '';
|
||||
};
|
||||
macCodeSign = require(macCodeSignPath);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
builderUtil.exec = originalExec;
|
||||
delete process.env.ELECTRON_BUILDER_CACHE;
|
||||
delete process.env.APP_BUILDER_TMP_DIR;
|
||||
rmSync(cacheDir, { recursive: true, force: true });
|
||||
rmSync(tmpRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('unlocks the partition list with the generated keychain password', async () => {
|
||||
const { TmpDir } = require(
|
||||
require.resolve('builder-util', { paths: [appBuilderLibDir] })
|
||||
);
|
||||
const tmpDir = new TmpDir('keychain-password-test');
|
||||
|
||||
try {
|
||||
await macCodeSign.createKeychain({
|
||||
tmpDir,
|
||||
// Base64 links are written to a temp file without inspection;
|
||||
// only the path reaches the recorded `security import`.
|
||||
cscLink: Buffer.from('not a real p12').toString('base64'),
|
||||
cscKeyPassword: IMPORT_PASSWORD,
|
||||
currentDir: tmpRoot,
|
||||
});
|
||||
} finally {
|
||||
await tmpDir.cleanup();
|
||||
}
|
||||
|
||||
const security = calls.filter(
|
||||
(call) => call.file === '/usr/bin/security'
|
||||
);
|
||||
const argAfter = (args, flag) => args[args.indexOf(flag) + 1];
|
||||
const created = security.find(
|
||||
(call) => call.args[0] === 'create-keychain'
|
||||
);
|
||||
const imported = security.find((call) => call.args[0] === 'import');
|
||||
const partition = security.find(
|
||||
(call) => call.args[0] === 'set-key-partition-list'
|
||||
);
|
||||
|
||||
assert.ok(created, 'create-keychain was not invoked');
|
||||
assert.ok(imported, 'security import was not invoked');
|
||||
assert.ok(partition, 'set-key-partition-list was not invoked');
|
||||
|
||||
const keychainPassword = argAfter(created.args, '-p');
|
||||
assert.ok(keychainPassword, 'create-keychain carried no password');
|
||||
assert.notEqual(keychainPassword, IMPORT_PASSWORD);
|
||||
assert.equal(argAfter(imported.args, '-P'), IMPORT_PASSWORD);
|
||||
assert.equal(
|
||||
argAfter(partition.args, '-k'),
|
||||
keychainPassword,
|
||||
'set-key-partition-list must authenticate with the keychain password'
|
||||
);
|
||||
assert.equal(
|
||||
partition.args.at(-1),
|
||||
created.args.at(-1),
|
||||
'same keychain file'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user