@linzumi/cli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Linzumi CLI \u2014 point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,128 @@
1
+ # Codex MCP keychain re-prompt - fix + QA
2
+
3
+ Spec: `kandan/server_v2/plans/2026-06-24-codex-mcp-keychain-reprompt-report.md`
4
+
5
+ ## The problem (one paragraph)
6
+
7
+ Codex stores its MCP OAuth secret in a macOS login-keychain generic-password
8
+ item labeled `Codex MCP Credentials`. The item's access-control list is bound to
9
+ one exact codex binary (its CDHash). When Codex.app auto-updates (new CDHash),
10
+ or you move between codex identities (official OpenAI team `2DC432GLL2`, the
11
+ unsigned npm `codex` under `node`, or a Linzumi-re-signed codex), the CDHash no
12
+ longer matches the ACL and macOS re-prompts: "codex wants to use the Codex MCP
13
+ Credentials in your keychain". The proper upstream fixes (a Designated-
14
+ Requirement-bound ACL, or moving to the data-protection keychain) are OpenAI's.
15
+
16
+ ## The no-upstream fix (lever 2)
17
+
18
+ Set the keychain item's PARTITION LIST to the code-signing team id(s) so any
19
+ same-team-signed codex build is granted silent access across updates, instead of
20
+ binding access to a single CDHash. This is what the
21
+ `linzumi fix-codex-keychain` subcommand does. The exact command it runs per
22
+ matching item is:
23
+
24
+ ```
25
+ security set-generic-password-partition-list \
26
+ -S "teamid:2DC432GLL2,teamid:2CWCP5BR45,apple:" \
27
+ -s "Codex MCP Credentials" \
28
+ -a <account> \
29
+ -k <login-keychain-password>
30
+ ```
31
+
32
+ - `2DC432GLL2` - OpenAI Codex code-signing team (the official Codex.app).
33
+ - `2CWCP5BR45` - Linzumi's macOS team
34
+ (`kandan/kandan-electron-shell/scripts/common_macos_beta.sh` `DEFAULT_TEAM_ID`,
35
+ overridable via `KANDAN_DEVELOPMENT_TEAM`; add more with `--team-id`).
36
+ - `apple:` - keeps Apple-provided tooling working.
37
+
38
+ The helper is macOS-only, scoped exclusively to the `Codex MCP Credentials`
39
+ service, idempotent, and consent-gated: it defaults to a dry run and only
40
+ mutates with `--apply`. The login-keychain password is never logged or
41
+ persisted (pass it via `LINZUMI_KEYCHAIN_PASSWORD` for automation, or let macOS
42
+ prompt).
43
+
44
+ ### Using the helper
45
+
46
+ ```
47
+ # Dry run - shows the item(s) and the partition list it would apply.
48
+ linzumi fix-codex-keychain
49
+
50
+ # Apply. macOS authorizes the ACL change with your login-keychain password.
51
+ linzumi fix-codex-keychain --apply
52
+
53
+ # Add an extra signing team (e.g. a custom re-signed codex).
54
+ linzumi fix-codex-keychain --apply --team-id ABCDE12345
55
+ ```
56
+
57
+ ## Automated synthetic repro (no GUI, no real item)
58
+
59
+ `scripts/qa/codex-keychain-partition-repro.sh` proves the mechanism the helper
60
+ relies on, without touching your real login keychain or the real
61
+ `Codex MCP Credentials` item, and without needing a real Codex.app update.
62
+
63
+ It creates a dedicated, unlocked test keychain with a known password, adds a
64
+ test item whose partition list does NOT include the target team (the would-
65
+ re-prompt precondition), runs the exact `set-generic-password-partition-list`
66
+ command the helper runs, then reads the partition list back with
67
+ `security dump-keychain -a` to prove the target team flipped from absent to
68
+ present. The test keychain is deleted on exit via a `trap` (even on failure).
69
+
70
+ Run it:
71
+
72
+ ```
73
+ bash packages/linzumi-cli/scripts/qa/codex-keychain-partition-repro.sh
74
+ ```
75
+
76
+ Expected output ends with:
77
+
78
+ ```
79
+ == BEFORE: target team must be ABSENT from the partition list ==
80
+ partition list (before): apple:
81
+ ok: target team is absent before the fix (a same-team codex would re-prompt).
82
+ ...
83
+ == AFTER: target team must now be PRESENT in the partition list ==
84
+ partition list (after): teamid:2DC432GLL2, apple:
85
+ ok: target team is present after the fix (same-team codex builds read silently).
86
+ ...
87
+ RESULT: PASS (partition list flipped from team-absent to team-present)
88
+ ```
89
+
90
+ ### What the synthetic repro does and does NOT prove
91
+
92
+ - PROVES (headless, deterministic): the fix command writes the intended
93
+ partition list onto the item; the team id transitions from absent to present,
94
+ observed verbatim via `security dump-keychain -a`. This is the load-bearing
95
+ state change.
96
+ - Does NOT prove headlessly: that a same-team-signed codex binary then reads the
97
+ secret with zero GUI prompt. Demonstrating the silent read requires a binary
98
+ actually signed by the team in the partition; `/usr/bin/security` is not, and
99
+ the authorization for a non-member reader is a GUI event that cannot be
100
+ satisfied non-interactively (it returns errSec interaction-not-allowed in an
101
+ automated shell). That payoff is confirmed by the on-device manual procedure
102
+ below, which is itself inherently a GUI observation (the re-prompt is a dialog).
103
+
104
+ ## On-device manual verification (real Codex.app item)
105
+
106
+ This is the human, on-device confirmation of the end-user payoff. Do it on a Mac
107
+ that actually uses Codex.app MCP credentials.
108
+
109
+ 1. Observe the BEFORE re-prompt. Trigger codex MCP usage (or force a re-prompt by
110
+ updating Codex.app, or temporarily switching codex identities). macOS shows:
111
+ "codex wants to use the Codex MCP Credentials in your keychain". This dialog
112
+ is the bug; note that it appears.
113
+ 2. Apply the fix:
114
+ ```
115
+ linzumi fix-codex-keychain # dry run: confirm the item + plan
116
+ linzumi fix-codex-keychain --apply # enter your login-keychain password once
117
+ ```
118
+ 3. Confirm AFTER. Relaunch codex / Codex.app (or re-trigger the same MCP usage,
119
+ ideally after an app update so the CDHash differs). The credential is read
120
+ with NO re-prompt. To double-check the state without a relaunch:
121
+ ```
122
+ security dump-keychain -a login.keychain-db \
123
+ | grep -A2 'authorizations (1): partition_id'
124
+ ```
125
+ The `description:` line for the `Codex MCP Credentials` item now lists
126
+ `teamid:2DC432GLL2` (and `teamid:2CWCP5BR45`, `apple:`).
127
+
128
+ Re-running `linzumi fix-codex-keychain --apply` is harmless (idempotent).
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Spec: plans/2026-06-24-codex-mcp-keychain-reprompt-report.md
4
+ #
5
+ # Self-contained, NON-INTERACTIVE proof of the Codex MCP keychain partition-list
6
+ # fix (lever 2). It NEVER touches your real login keychain or the real
7
+ # "Codex MCP Credentials" item, and it does NOT require a real Codex.app update.
8
+ #
9
+ # WHAT IT PROVES (headless, deterministic):
10
+ # The fix runs `security set-generic-password-partition-list` to add the
11
+ # code-signing team(s) to a keychain item's PARTITION LIST. A keychain item's
12
+ # silent-read access is gated by that partition list. We create a dedicated
13
+ # test item whose partition list does NOT include the target team
14
+ # ("teamid:2DC432GLL2"), confirm the team is ABSENT (the "broken" / would-
15
+ # re-prompt precondition), run the SAME command the helper runs, and confirm
16
+ # the team is now PRESENT (the "fixed" state). The partition list is read back
17
+ # verbatim via `security dump-keychain -a`, so the before/after transition is
18
+ # fully observable without any GUI.
19
+ #
20
+ # WHY NOT "silent read" headlessly:
21
+ # The post-fix payoff is that a same-team-signed codex binary reads the secret
22
+ # with NO GUI prompt. Demonstrating that requires a binary that is actually in
23
+ # the team partition; /usr/bin/security is not, and the authorization is a GUI
24
+ # event. That on-device confirmation is the manual procedure in the QA doc.
25
+ # This script proves the load-bearing state change the helper performs.
26
+ #
27
+ # Everything happens inside a DEDICATED test keychain with a KNOWN password,
28
+ # created unlocked and deleted on exit (even on failure, via trap), so macOS
29
+ # never pops a GUI dialog and your real keychains are untouched.
30
+ #
31
+ # Usage: bash codex-keychain-partition-repro.sh
32
+ # Exit: 0 = PASS, 1 = FAIL, 2 = not macOS.
33
+
34
+ set -u
35
+
36
+ if [[ "$(uname -s)" != "Darwin" ]]; then
37
+ echo "SKIP: this repro only runs on macOS (darwin). Detected: $(uname -s)" >&2
38
+ exit 2
39
+ fi
40
+
41
+ KEYCHAIN_NAME="linzumi-qa.keychain"
42
+ KEYCHAIN_PW="linzumi-qa-$$"
43
+ SERVICE="Codex MCP Credentials TEST"
44
+ ACCOUNT="testacct"
45
+ SECRET="testsecret-$$"
46
+ # The official OpenAI Codex team id is the partition we add (the same default
47
+ # the helper applies). The synthetic item proves the partition-list mechanism;
48
+ # the team id value is not verified against a real signature in this test.
49
+ TARGET_TEAM="2DC432GLL2"
50
+ # BEFORE: a partition list WITHOUT the target team. AFTER: target team + apple.
51
+ BEFORE_PARTITION="apple:"
52
+ AFTER_PARTITION="teamid:${TARGET_TEAM},apple:"
53
+
54
+ cleanup() {
55
+ security delete-keychain "$KEYCHAIN_NAME" >/dev/null 2>&1 || true
56
+ }
57
+ trap cleanup EXIT
58
+
59
+ # Read the partition-list "description:" line (under the partition_id
60
+ # authorization) for the test item from a keychain dump.
61
+ read_partition_description() {
62
+ security dump-keychain -a "$KEYCHAIN_NAME" 2>/dev/null \
63
+ | grep -A2 'authorizations (1): partition_id' \
64
+ | grep 'description:' \
65
+ | head -n1 \
66
+ | sed 's/^[[:space:]]*description:[[:space:]]*//'
67
+ }
68
+
69
+ echo "== Codex MCP keychain partition-list repro (synthetic, non-interactive) =="
70
+ echo "test keychain: $KEYCHAIN_NAME"
71
+ echo "service: $SERVICE"
72
+ echo "target team: teamid:$TARGET_TEAM"
73
+ echo
74
+
75
+ # Clean slate in case a prior aborted run left the keychain behind.
76
+ cleanup
77
+
78
+ echo "[setup] create + unlock dedicated test keychain"
79
+ if ! security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN_NAME"; then
80
+ echo "FAIL: could not create test keychain" >&2
81
+ exit 1
82
+ fi
83
+ # Disable auto-relock so no GUI unlock prompt can appear during the run.
84
+ security set-keychain-settings "$KEYCHAIN_NAME" || true
85
+ security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN_NAME" || true
86
+
87
+ echo "[setup] add a test item (permissive ACL) then set a partition list that"
88
+ echo " EXCLUDES the target team (the would-re-prompt precondition)"
89
+ if ! security add-generic-password \
90
+ -a "$ACCOUNT" -s "$SERVICE" -w "$SECRET" -A "$KEYCHAIN_NAME"; then
91
+ echo "FAIL: could not add test item" >&2
92
+ exit 1
93
+ fi
94
+ security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN_NAME" || true
95
+ if ! security set-generic-password-partition-list \
96
+ -S "$BEFORE_PARTITION" \
97
+ -s "$SERVICE" -a "$ACCOUNT" -k "$KEYCHAIN_PW" "$KEYCHAIN_NAME" \
98
+ >/dev/null 2>&1; then
99
+ echo "FAIL: could not set the BEFORE partition list" >&2
100
+ exit 1
101
+ fi
102
+
103
+ echo
104
+ echo "== BEFORE: target team must be ABSENT from the partition list =="
105
+ BEFORE_DESC="$(read_partition_description)"
106
+ echo "partition list (before): ${BEFORE_DESC:-<none>}"
107
+ if echo "$BEFORE_DESC" | grep -q "teamid:${TARGET_TEAM}"; then
108
+ echo "BEFORE precondition NOT met: target team already present." >&2
109
+ BEFORE_OK=0
110
+ else
111
+ echo "ok: target team is absent before the fix (a same-team codex would re-prompt)."
112
+ BEFORE_OK=1
113
+ fi
114
+
115
+ echo
116
+ echo "== APPLY FIX: the exact command the helper runs =="
117
+ echo "running: security set-generic-password-partition-list -S \"$AFTER_PARTITION\" -s \"$SERVICE\" -a \"$ACCOUNT\" -k <pw> $KEYCHAIN_NAME"
118
+ security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN_NAME" || true
119
+ if ! security set-generic-password-partition-list \
120
+ -S "$AFTER_PARTITION" \
121
+ -s "$SERVICE" -a "$ACCOUNT" -k "$KEYCHAIN_PW" "$KEYCHAIN_NAME" \
122
+ >/dev/null 2>&1; then
123
+ echo "FAIL: set-generic-password-partition-list failed" >&2
124
+ exit 1
125
+ fi
126
+ echo "partition list applied."
127
+
128
+ echo
129
+ echo "== AFTER: target team must now be PRESENT in the partition list =="
130
+ AFTER_DESC="$(read_partition_description)"
131
+ echo "partition list (after): ${AFTER_DESC:-<none>}"
132
+ if echo "$AFTER_DESC" | grep -q "teamid:${TARGET_TEAM}"; then
133
+ echo "ok: target team is present after the fix (same-team codex builds read silently)."
134
+ AFTER_OK=1
135
+ else
136
+ echo "AFTER FAILED: target team is still absent." >&2
137
+ AFTER_OK=0
138
+ fi
139
+
140
+ echo
141
+ echo "============================== VERDICT =============================="
142
+ echo "BEFORE (team absent): $BEFORE_OK"
143
+ echo "AFTER (team present): $AFTER_OK"
144
+
145
+ if [[ $BEFORE_OK -eq 1 && $AFTER_OK -eq 1 ]]; then
146
+ echo "RESULT: PASS (partition list flipped from team-absent to team-present)"
147
+ exit 0
148
+ fi
149
+
150
+ echo "RESULT: FAIL"
151
+ exit 1