sneakoscope 6.4.0 → 6.7.0
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/README.md +26 -34
- package/config/codex-releases/{rust-v0.144.1.json → rust-v0.144.5.json} +5 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/install.js +1 -1
- package/dist/cli/command-manifest-lite.js +2 -3
- package/dist/cli/command-registry.js +6 -4
- package/dist/cli/help-fast.js +0 -1
- package/dist/cli/router.js +24 -3
- package/dist/commands/zellij.js +18 -29
- package/dist/config/skills-manifest.json +62 -62
- package/dist/core/agent-bridge/agent-manifest.js +22 -1
- package/dist/core/agents/agent-codex-cockpit.js +1 -81
- package/dist/core/agents/agent-effort-policy.js +13 -7
- package/dist/core/agents/agent-orchestrator.js +0 -6
- package/dist/core/agents/agent-output-validator.js +2 -1
- package/dist/core/agents/agent-patch-queue-store.js +0 -8
- package/dist/core/agents/agent-proof-evidence.js +0 -6
- package/dist/core/agents/agent-worker-pipeline.js +12 -6
- package/dist/core/agents/native-cli-worker-runtime.js +1 -16
- package/dist/core/agents/native-worker-backend-router.js +1 -1
- package/dist/core/bench.js +1 -1
- package/dist/core/codex/official-goal-mode.js +4 -2
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +11 -17
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +34 -6
- package/dist/core/codex-app/menubar/constants.js +0 -1
- package/dist/core/codex-app.js +1 -1
- package/dist/core/codex-compat/codex-release-manifest.js +5 -5
- package/dist/core/codex-control/codex-0139-image-path-real-probe.js +1 -1
- package/dist/core/codex-control/codex-0139-web-search-probe.js +1 -1
- package/dist/core/codex-control/codex-control-proof.js +1 -0
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +1 -1
- package/dist/core/codex-control/codex-lb-launch-recovery.js +7 -3
- package/dist/core/codex-control/codex-sdk-adapter.js +8 -7
- package/dist/core/codex-control/codex-sdk-config-policy.js +4 -24
- package/dist/core/codex-control/codex-sdk-env-policy.js +396 -11
- package/dist/core/codex-control/codex-task-runner.js +112 -57
- package/dist/core/codex-control/gpt-final-arbiter.js +1 -2
- package/dist/core/codex-control/python-codex-sdk-adapter.js +38 -15
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +3 -3
- package/dist/core/codex-native/core-skill-manifest.js +4 -7
- package/dist/core/codex-runtime/resolve-codex-runtime.js +199 -0
- package/dist/core/commands/agent-bridge-command.js +46 -15
- package/dist/core/commands/basic-cli.js +1 -1
- package/dist/core/commands/command-utils.js +2 -2
- package/dist/core/commands/eval-command.js +19 -2
- package/dist/core/commands/goal-command.js +37 -86
- package/dist/core/commands/mad-sks-command.js +0 -2
- package/dist/core/commands/naruto-command.js +236 -176
- package/dist/core/doctor/current-project-guidance.js +1 -1
- package/dist/core/doctor/doctor-dirty-planner.js +1 -1
- package/dist/core/doctor/retired-managed-residue-missions.js +26 -11
- package/dist/core/doctor/retired-managed-residue-private.js +1 -1
- package/dist/core/doctor/retired-managed-residue-runtime.js +12 -2
- package/dist/core/doctor/retired-managed-residue-state.js +7 -7
- package/dist/core/evaluation.js +83 -21
- package/dist/core/feature-fixtures.js +4 -7
- package/dist/core/feature-registry.js +0 -1
- package/dist/core/fsx.js +169 -14
- package/dist/core/goal-workflow.js +71 -140
- package/dist/core/hooks-runtime/hook-io.js +2 -2
- package/dist/core/hooks-runtime.js +228 -50
- package/dist/core/init/skills.js +7 -6
- package/dist/core/init.js +52 -33
- package/dist/core/install/installed-package-smoke.js +1 -1
- package/dist/core/lean-engineering-policy.js +25 -23
- package/dist/core/locks/file-lock.js +74 -21
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/mcp-config/health-check.js +7 -2
- package/dist/core/mission.js +99 -3
- package/dist/core/ops/upgrade-migration-fixtures.js +30 -2
- package/dist/core/pipeline-internals/runtime-core.js +67 -95
- package/dist/core/pipeline-internals/runtime-gates.js +25 -6
- package/dist/core/proof/fake-real-proof-policy.js +32 -9
- package/dist/core/proof/route-proof-gate.js +49 -1
- package/dist/core/provider/model-router.js +2 -0
- package/dist/core/questions.js +31 -5
- package/dist/core/recallpulse.js +2 -2
- package/dist/core/release/canonical-test-proof.js +135 -0
- package/dist/core/release/release-closure-contract.js +1 -0
- package/dist/core/release/release-closure-validation.js +25 -12
- package/dist/core/release/release-closure.js +34 -5
- package/dist/core/release/release-gate-contract.js +4 -3
- package/dist/core/release/release-gate-dag.js +4 -2
- package/dist/core/release/release-pack-content-scanner.js +7 -6
- package/dist/core/release/release-pack-receipt.js +33 -12
- package/dist/core/release/release-real-contract.js +41 -2
- package/dist/core/retention.js +95 -16
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +18 -20
- package/dist/core/safety/command-contract/contracts.js +33 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/safety/ssot-guard.js +1 -54
- package/dist/core/stop-gate/stop-gate-check.js +0 -14
- package/dist/core/stop-gate/stop-gate-writer.js +0 -4
- package/dist/core/strategy/adhd-orchestrating-gate.js +2 -2
- package/dist/core/subagents/agent-catalog.js +28 -5
- package/dist/core/subagents/model-policy.js +52 -11
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-proof-projection.js +449 -0
- package/dist/core/subagents/official-subagent-preparation.js +641 -51
- package/dist/core/subagents/official-subagent-prompt.js +16 -6
- package/dist/core/subagents/official-subagent-runner.js +132 -12
- package/dist/core/subagents/subagent-evidence.js +44 -8
- package/dist/core/subagents/wave-lifecycle.js +238 -0
- package/dist/core/update-check.js +13 -4
- package/dist/core/verification/machine-feedback.js +39 -19
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-lane-renderer.js +19 -19
- package/dist/core/zellij/zellij-official-subagent-activity.js +2 -0
- package/dist/core/zellij/zellij-right-column-manager.js +6 -55
- package/dist/core/zellij/zellij-slot-column-anchor.js +4 -5
- package/dist/core/zellij/zellij-slot-pane-renderer.js +1 -1
- package/dist/core/zellij/zellij-ui-mode.js +0 -7
- package/dist/core/zellij/zellij-worker-pane-manager.js +3 -11
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +0 -1
- package/dist/native/sks-menubar/Sources/NotificationCoordinator.swift +1 -3
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +194 -24
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +49 -4
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +38 -10
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +73 -11
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +27 -2
- package/dist/scripts/architecture-guard-check.js +1 -9
- package/dist/scripts/canonical-test-runner.js +51 -8
- package/dist/scripts/check-architecture.js +11 -1
- package/dist/scripts/cli-output-consistency-check.js +0 -2
- package/dist/scripts/codex-0144-app-server-v2-check.js +4 -4
- package/dist/scripts/codex-0144-binary-identity-check.js +10 -4
- package/dist/scripts/codex-0144-capability-check.js +5 -5
- package/dist/scripts/codex-0144-policy-check.js +4 -0
- package/dist/scripts/codex-control-all-pipelines-check.js +2 -1
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +3 -3
- package/dist/scripts/codex-sdk-all-pipelines-check.js +2 -1
- package/dist/scripts/codex-sdk-real-smoke-check.js +20 -2
- package/dist/scripts/current-command-surface-check.js +5 -3
- package/dist/scripts/current-surface-update-e2e-check.js +2 -2
- package/dist/scripts/docs-truthfulness-check.js +8 -6
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +3 -2
- package/dist/scripts/gate-policy-audit-check.js +7 -1
- package/dist/scripts/harness-benchmark-check.js +4 -4
- package/dist/scripts/hook-latency-quantum-check.js +2 -2
- package/dist/scripts/legacy-gate-purge-check.js +4 -2
- package/dist/scripts/loop-directive-check-lib.js +14 -13
- package/dist/scripts/naruto-codex-e2e-check.js +2 -2
- package/dist/scripts/naruto-ssot-gate-aliases-check.js +9 -0
- package/dist/scripts/naruto-ssot-pipeline-default-check.js +10 -0
- package/dist/scripts/naruto-ssot-route-normalization-check.js +8 -0
- package/dist/scripts/naruto-ssot-routing-check.js +12 -0
- package/dist/scripts/product-design-plugin-routing-check.js +4 -9
- package/dist/scripts/release-affected-selector-check.js +7 -1
- package/dist/scripts/release-check-stamp.js +151 -18
- package/dist/scripts/release-dynamic-presets-check.js +5 -2
- package/dist/scripts/release-metadata-1-19-check.js +4 -0
- package/dist/scripts/release-real-check.js +21 -3
- package/dist/scripts/sks-menubar-install-check.js +5 -5
- package/dist/scripts/upgrade-migration-matrix-check.js +278 -5
- package/dist/scripts/zellij-developer-controls-check.js +3 -5
- package/dist/scripts/zellij-initial-main-only-blackbox.js +3 -8
- package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +1 -2
- package/dist/scripts/zellij-right-column-headless-overflow-check.js +1 -1
- package/dist/scripts/zellij-right-column-manager-check.js +3 -4
- package/dist/scripts/zellij-slot-only-ui-check.js +2 -2
- package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +1 -2
- package/docs/demo.tape +1 -1
- package/infra-harness-gates.json +0 -27
- package/package.json +11 -6
- package/release-gates.v2.json +150 -22
- package/schemas/codex/agent-result.schema.json +4 -0
- package/schemas/codex/app-server-0.144/codex_app_server_protocol.v2.schemas.json +1 -18812
- package/schemas/codex/codex-0139-real-probe-result.schema.json +2 -2
- package/schemas/zellij/zellij-right-column-state.schema.json +0 -1
- package/dist/core/commands/ui-command.js +0 -198
- package/dist/core/loops/goal-to-loop-compat.js +0 -22
- package/dist/core/naruto/naruto-allocation-policy.js +0 -98
- package/dist/core/naruto/naruto-finalizer.js +0 -22
- package/dist/core/naruto/naruto-gpt-final-pack.js +0 -50
- package/dist/core/naruto/naruto-real-worker-child.js +0 -209
- package/dist/core/naruto/naruto-real-worker-runtime.js +0 -178
- package/dist/core/naruto/naruto-rebalance-policy.js +0 -47
- package/dist/core/naruto/naruto-verification-pool.js +0 -17
- package/dist/core/ui/dashboard-html.js +0 -110
- package/dist/core/zellij/zellij-dashboard-pane.js +0 -70
- package/dist/core/zellij/zellij-dashboard-renderer.js +0 -59
- package/dist/core/zellij/zellij-naruto-dashboard.js +0 -44
- package/dist/scripts/zellij-dashboard-pane-check.js +0 -69
- package/dist/scripts/zellij-dashboard-watch.js +0 -40
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { constants as fsConstants } from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
4
|
+
import os from 'node:os';
|
|
1
5
|
import path from 'node:path';
|
|
2
6
|
const SECRET_RE = /(?:key|token|secret|password|credential|auth|cookie)/i;
|
|
3
7
|
const BASE_ALLOWED_ENV = new Set([
|
|
@@ -40,14 +44,7 @@ export function buildCodexSdkEnv(input) {
|
|
|
40
44
|
if (BASE_ALLOWED_ENV.has(key) || key.startsWith('LC_'))
|
|
41
45
|
env[key] = String(value);
|
|
42
46
|
}
|
|
43
|
-
|
|
44
|
-
const codexLbBaseUrl = String(process.env.CODEX_LB_BASE_URL || '').trim();
|
|
45
|
-
const codexLbEnvInjected = process.env.SKS_CODEX_LB_AUTOBYPASS !== '1' && Boolean(codexLbApiKey && codexLbBaseUrl);
|
|
46
|
-
if (codexLbEnvInjected) {
|
|
47
|
-
env.CODEX_LB_API_KEY = codexLbApiKey;
|
|
48
|
-
env.CODEX_LB_BASE_URL = codexLbBaseUrl;
|
|
49
|
-
}
|
|
50
|
-
env.SKS_CODEX_CONTROL_PLANE_CONFIG_ISOLATED = '1';
|
|
47
|
+
env.SKS_CODEX_CONTROL_PLANE_NATIVE = '1';
|
|
51
48
|
const inheritedKeys = Object.keys(env).filter((key) => !key.startsWith('SKS_') && key !== 'HOME' && key !== 'CODEX_HOME').sort();
|
|
52
49
|
const blockedHostKeys = Object.keys(process.env).filter((key) => !(key in env)).sort();
|
|
53
50
|
return {
|
|
@@ -59,10 +56,15 @@ export function buildCodexSdkEnv(input) {
|
|
|
59
56
|
blocked_host_env_key_count: blockedHostKeys.length,
|
|
60
57
|
blocked_sensitive_host_env_key_count: blockedHostKeys.filter((key) => SECRET_RE.test(key)).length,
|
|
61
58
|
redacted_sensitive_keys: Object.keys(env).filter((key) => SECRET_RE.test(key)).sort(),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
native_codex_only: true,
|
|
60
|
+
model_provider: 'openai',
|
|
61
|
+
codex_lb_env_injected: false,
|
|
62
|
+
codex_lb_env_source: null,
|
|
63
|
+
codex_lb_api_key_redacted: false,
|
|
65
64
|
codex_home_isolated: true,
|
|
65
|
+
home_isolated: true,
|
|
66
|
+
native_codex_auth_bridge_required: true,
|
|
67
|
+
native_codex_auth_source: 'host_codex_home/auth.json',
|
|
66
68
|
codex_home: env.CODEX_HOME,
|
|
67
69
|
home: env.HOME
|
|
68
70
|
}
|
|
@@ -71,3 +73,386 @@ export function buildCodexSdkEnv(input) {
|
|
|
71
73
|
export function redactCodexSdkEnv(env) {
|
|
72
74
|
return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, SECRET_RE.test(key) ? '<redacted>' : value]));
|
|
73
75
|
}
|
|
76
|
+
export async function prepareNativeCodexAuthBridge(env, opts = {}) {
|
|
77
|
+
const required = opts.required !== false;
|
|
78
|
+
if (!required) {
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
blockers: [],
|
|
82
|
+
proof: {
|
|
83
|
+
schema: 'sks.codex-native-auth-bridge.v1',
|
|
84
|
+
ok: true,
|
|
85
|
+
status: 'skipped_fixture_adapter',
|
|
86
|
+
method: null,
|
|
87
|
+
cleanup_required: false
|
|
88
|
+
},
|
|
89
|
+
cleanup: async () => ({ ok: true, status: 'cleaned', outcome: 'not_required', cleanup_required: false, blockers: [] })
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const nativeHome = String(process.env.HOME || os.homedir());
|
|
93
|
+
const sourceHome = path.resolve(String(process.env.CODEX_HOME || path.join(nativeHome, '.codex')));
|
|
94
|
+
const sourceAuthCandidate = path.join(sourceHome, 'auth.json');
|
|
95
|
+
const originalHome = String(env.HOME || '');
|
|
96
|
+
const originalCodexHome = String(env.CODEX_HOME || '');
|
|
97
|
+
let tempRoot = null;
|
|
98
|
+
let tempRootIdentity = null;
|
|
99
|
+
let sourceAuthIdentity = null;
|
|
100
|
+
let sourceAuthFingerprint = '';
|
|
101
|
+
let originalTokenFingerprint = '';
|
|
102
|
+
let originalApiKeyFields = {};
|
|
103
|
+
let destinationAuth = null;
|
|
104
|
+
let destinationAuthIdentity = null;
|
|
105
|
+
let bridgeProof = null;
|
|
106
|
+
let cleanupComplete = false;
|
|
107
|
+
const removeOwnedTempRoot = async () => {
|
|
108
|
+
if (!tempRoot || cleanupComplete)
|
|
109
|
+
return;
|
|
110
|
+
let current;
|
|
111
|
+
try {
|
|
112
|
+
current = await fsp.lstat(tempRoot);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error?.code === 'ENOENT') {
|
|
116
|
+
cleanupComplete = true;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
if (!tempRootIdentity || !current.isDirectory() || current.isSymbolicLink()
|
|
122
|
+
|| current.dev !== tempRootIdentity.dev || current.ino !== tempRootIdentity.ino) {
|
|
123
|
+
throw new Error('native_codex_auth_temp_root_identity_changed');
|
|
124
|
+
}
|
|
125
|
+
await fsp.rm(tempRoot, { recursive: true, force: false });
|
|
126
|
+
try {
|
|
127
|
+
await fsp.lstat(tempRoot);
|
|
128
|
+
throw new Error('native_codex_auth_temp_root_cleanup_unverified');
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error?.code !== 'ENOENT')
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
cleanupComplete = true;
|
|
135
|
+
};
|
|
136
|
+
const cleanupOwnedTempRoot = async () => {
|
|
137
|
+
if (!tempRoot || cleanupComplete) {
|
|
138
|
+
return { ok: true, status: 'cleaned', outcome: 'already_cleaned', cleanup_required: false, blockers: [] };
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
if (!destinationAuth || !destinationAuthIdentity || !sourceAuthIdentity || !sourceAuthFingerprint) {
|
|
142
|
+
throw new Error('native_codex_auth_cleanup_state_incomplete');
|
|
143
|
+
}
|
|
144
|
+
const tempAuthText = await readValidatedAuthFile(destinationAuth, {
|
|
145
|
+
expectedIdentity: destinationAuthIdentity,
|
|
146
|
+
requirePrivateMode: true,
|
|
147
|
+
requireSingleLink: true,
|
|
148
|
+
errorPrefix: 'native_codex_auth_destination'
|
|
149
|
+
});
|
|
150
|
+
const tempAuth = parseStrictChatGptAuth(tempAuthText, { forbidTopLevelApiKeys: true });
|
|
151
|
+
const refreshedTokenFingerprint = fingerprintJson(tempAuth.tokens);
|
|
152
|
+
if (refreshedTokenFingerprint === originalTokenFingerprint) {
|
|
153
|
+
await removeOwnedTempRoot();
|
|
154
|
+
const result = { ok: true, status: 'cleaned', outcome: 'unchanged', cleanup_required: false, blockers: [] };
|
|
155
|
+
applyCleanupProof(bridgeProof, result);
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
await assertSourceAuthUnchanged(sourceAuthCandidate, sourceAuthIdentity, sourceAuthFingerprint);
|
|
159
|
+
const hostAuth = { ...tempAuth, ...originalApiKeyFields };
|
|
160
|
+
const hostAuthText = `${JSON.stringify(hostAuth, null, 2)}\n`;
|
|
161
|
+
await writeAuthFileAtomic(sourceAuthCandidate, hostAuthText, async () => {
|
|
162
|
+
await assertSourceAuthUnchanged(sourceAuthCandidate, sourceAuthIdentity, sourceAuthFingerprint);
|
|
163
|
+
});
|
|
164
|
+
await removeOwnedTempRoot();
|
|
165
|
+
const result = { ok: true, status: 'cleaned', outcome: 'refreshed_persisted', cleanup_required: false, blockers: [] };
|
|
166
|
+
applyCleanupProof(bridgeProof, result);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const reason = String(error?.message || error || 'native_codex_auth_cleanup_failed');
|
|
171
|
+
const outcome = reason === 'native_codex_auth_source_conflict' ? 'source_conflict' : 'failed';
|
|
172
|
+
const blocker = `native_codex_auth_cleanup_failed:${reason}`;
|
|
173
|
+
const result = { ok: false, status: 'blocked', outcome, cleanup_required: true, blockers: [blocker] };
|
|
174
|
+
applyCleanupProof(bridgeProof, result, tempRoot);
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
try {
|
|
179
|
+
const sourcePathStat = await fsp.lstat(sourceAuthCandidate);
|
|
180
|
+
if (sourcePathStat.isSymbolicLink())
|
|
181
|
+
throw new Error('native_codex_auth_source_symlink_forbidden');
|
|
182
|
+
if (!sourcePathStat.isFile())
|
|
183
|
+
throw new Error('native_codex_auth_source_not_file');
|
|
184
|
+
const noFollow = process.platform === 'win32' ? 0 : fsConstants.O_NOFOLLOW;
|
|
185
|
+
let sanitizedAuthText = '';
|
|
186
|
+
const sourceText = await readValidatedAuthFile(sourceAuthCandidate, {
|
|
187
|
+
expectedIdentity: { dev: sourcePathStat.dev, ino: sourcePathStat.ino },
|
|
188
|
+
requirePrivateMode: true,
|
|
189
|
+
errorPrefix: 'native_codex_auth_source'
|
|
190
|
+
});
|
|
191
|
+
const sourceAuth = parseStrictChatGptAuth(sourceText);
|
|
192
|
+
sourceAuthIdentity = { dev: sourcePathStat.dev, ino: sourcePathStat.ino };
|
|
193
|
+
sourceAuthFingerprint = fingerprintText(sourceText);
|
|
194
|
+
originalTokenFingerprint = fingerprintJson(sourceAuth.tokens);
|
|
195
|
+
originalApiKeyFields = Object.fromEntries(Object.entries(sourceAuth).filter(([key]) => isTopLevelApiKeyField(key)));
|
|
196
|
+
sanitizedAuthText = `${JSON.stringify(removeTopLevelApiKeyFields(sourceAuth), null, 2)}\n`;
|
|
197
|
+
const tempBase = await fsp.realpath(os.tmpdir());
|
|
198
|
+
tempRoot = await fsp.mkdtemp(path.join(tempBase, 'sks-native-codex-'));
|
|
199
|
+
const rootStat = await fsp.lstat(tempRoot);
|
|
200
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
|
|
201
|
+
throw new Error('native_codex_auth_temp_root_invalid');
|
|
202
|
+
}
|
|
203
|
+
tempRootIdentity = { dev: rootStat.dev, ino: rootStat.ino };
|
|
204
|
+
await fsp.chmod(tempRoot, 0o700);
|
|
205
|
+
const securedRootStat = await fsp.lstat(tempRoot);
|
|
206
|
+
if (securedRootStat.dev !== tempRootIdentity.dev || securedRootStat.ino !== tempRootIdentity.ino
|
|
207
|
+
|| (process.platform !== 'win32' && (securedRootStat.mode & 0o777) !== 0o700)) {
|
|
208
|
+
throw new Error('native_codex_auth_temp_root_permissions_invalid');
|
|
209
|
+
}
|
|
210
|
+
if (isPathInside(process.cwd(), tempRoot) || isPathInside(path.dirname(originalCodexHome), tempRoot)) {
|
|
211
|
+
throw new Error('native_codex_auth_temp_root_inside_workspace');
|
|
212
|
+
}
|
|
213
|
+
const destinationHome = path.join(tempRoot, 'home');
|
|
214
|
+
const destinationCodexHome = path.join(tempRoot, 'codex');
|
|
215
|
+
destinationAuth = path.join(destinationCodexHome, 'auth.json');
|
|
216
|
+
await fsp.mkdir(destinationHome, { mode: 0o700 });
|
|
217
|
+
await fsp.mkdir(destinationCodexHome, { mode: 0o700 });
|
|
218
|
+
await fsp.chmod(destinationHome, 0o700);
|
|
219
|
+
await fsp.chmod(destinationCodexHome, 0o700);
|
|
220
|
+
const destinationHandle = await fsp.open(destinationAuth, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollow, 0o600);
|
|
221
|
+
let destinationIdentity = null;
|
|
222
|
+
try {
|
|
223
|
+
await destinationHandle.writeFile(sanitizedAuthText, 'utf8');
|
|
224
|
+
await destinationHandle.chmod(0o600);
|
|
225
|
+
const destinationStat = await destinationHandle.stat();
|
|
226
|
+
if (!destinationStat.isFile() || destinationStat.nlink !== 1
|
|
227
|
+
|| (process.platform !== 'win32' && (destinationStat.mode & 0o777) !== 0o600)) {
|
|
228
|
+
throw new Error('native_codex_auth_destination_validation_failed');
|
|
229
|
+
}
|
|
230
|
+
destinationIdentity = { dev: destinationStat.dev, ino: destinationStat.ino };
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
await destinationHandle.close();
|
|
234
|
+
}
|
|
235
|
+
const destinationPathStat = await fsp.lstat(destinationAuth);
|
|
236
|
+
if (!destinationIdentity || !destinationPathStat.isFile() || destinationPathStat.isSymbolicLink()
|
|
237
|
+
|| destinationPathStat.dev !== destinationIdentity.dev || destinationPathStat.ino !== destinationIdentity.ino) {
|
|
238
|
+
throw new Error('native_codex_auth_destination_not_regular_file');
|
|
239
|
+
}
|
|
240
|
+
destinationAuthIdentity = destinationIdentity;
|
|
241
|
+
env.HOME = destinationHome;
|
|
242
|
+
env.CODEX_HOME = destinationCodexHome;
|
|
243
|
+
bridgeProof = {
|
|
244
|
+
schema: 'sks.codex-native-auth-bridge.v1',
|
|
245
|
+
ok: true,
|
|
246
|
+
status: 'ready',
|
|
247
|
+
method: 'exclusive_copy',
|
|
248
|
+
auth_mode: 'chatgpt_oauth',
|
|
249
|
+
source: 'host_codex_home/auth.json',
|
|
250
|
+
destination: 'private_temp_codex_home/auth.json',
|
|
251
|
+
temp_root_location: 'os_tmpdir',
|
|
252
|
+
temp_root_mode: '0700',
|
|
253
|
+
auth_file_mode: '0600',
|
|
254
|
+
source_symlinks_allowed: false,
|
|
255
|
+
source_identity_captured: true,
|
|
256
|
+
source_content_fingerprint_captured: true,
|
|
257
|
+
api_key_fields_removed: true,
|
|
258
|
+
refresh_writeback: 'strict_chatgpt_tokens_compare_and_swap',
|
|
259
|
+
cleanup_required: true
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
ok: true,
|
|
263
|
+
blockers: [],
|
|
264
|
+
proof: bridgeProof,
|
|
265
|
+
cleanup: cleanupOwnedTempRoot
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
let cleanupError = null;
|
|
270
|
+
try {
|
|
271
|
+
await removeOwnedTempRoot();
|
|
272
|
+
}
|
|
273
|
+
catch (cleanupFailure) {
|
|
274
|
+
cleanupError = cleanupFailure;
|
|
275
|
+
}
|
|
276
|
+
env.HOME = originalHome;
|
|
277
|
+
env.CODEX_HOME = originalCodexHome;
|
|
278
|
+
const reason = String(error?.message || error || 'native_codex_auth_bridge_failed');
|
|
279
|
+
const cleanupReason = cleanupError ? `:cleanup_failed:${String(cleanupError?.message || cleanupError)}` : '';
|
|
280
|
+
const blocker = `native_codex_auth_bridge_failed:${reason}${cleanupReason}`;
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
blockers: [blocker],
|
|
284
|
+
proof: {
|
|
285
|
+
schema: 'sks.codex-native-auth-bridge.v1',
|
|
286
|
+
ok: false,
|
|
287
|
+
status: 'blocked',
|
|
288
|
+
method: null,
|
|
289
|
+
source: 'host_codex_home/auth.json',
|
|
290
|
+
destination: 'private_temp_codex_home/auth.json',
|
|
291
|
+
cleanup_required: Boolean(tempRoot && !cleanupComplete),
|
|
292
|
+
blockers: [blocker]
|
|
293
|
+
},
|
|
294
|
+
cleanup: async () => {
|
|
295
|
+
try {
|
|
296
|
+
await removeOwnedTempRoot();
|
|
297
|
+
return { ok: true, status: 'cleaned', outcome: 'setup_failure_cleaned', cleanup_required: false, blockers: [] };
|
|
298
|
+
}
|
|
299
|
+
catch (cleanupFailure) {
|
|
300
|
+
const blocker = `native_codex_auth_cleanup_failed:${String(cleanupFailure?.message || cleanupFailure)}`;
|
|
301
|
+
return { ok: false, status: 'blocked', outcome: 'failed', cleanup_required: true, blockers: [blocker] };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function parseStrictChatGptAuth(sourceText, opts = {}) {
|
|
308
|
+
let parsed;
|
|
309
|
+
try {
|
|
310
|
+
parsed = JSON.parse(sourceText);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
throw new Error('native_codex_auth_source_invalid_json');
|
|
314
|
+
}
|
|
315
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
316
|
+
throw new Error('native_codex_auth_source_unknown_shape');
|
|
317
|
+
}
|
|
318
|
+
const authMode = String(parsed.auth_mode || '').trim().toLowerCase();
|
|
319
|
+
if (authMode !== 'chatgpt') {
|
|
320
|
+
throw new Error(authMode.includes('api') ? 'native_codex_auth_api_key_forbidden' : 'native_codex_auth_mode_unsupported');
|
|
321
|
+
}
|
|
322
|
+
const tokens = parsed.tokens;
|
|
323
|
+
if (!tokens || typeof tokens !== 'object' || Array.isArray(tokens)) {
|
|
324
|
+
throw new Error('native_codex_auth_oauth_tokens_missing');
|
|
325
|
+
}
|
|
326
|
+
const accessToken = typeof tokens?.access_token === 'string' ? tokens.access_token.trim() : '';
|
|
327
|
+
const refreshToken = typeof tokens?.refresh_token === 'string' ? tokens.refresh_token.trim() : '';
|
|
328
|
+
const idToken = typeof tokens?.id_token === 'string' ? tokens.id_token.trim() : '';
|
|
329
|
+
if (!accessToken || (!refreshToken && !idToken)) {
|
|
330
|
+
throw new Error('native_codex_auth_oauth_tokens_missing');
|
|
331
|
+
}
|
|
332
|
+
if (opts.forbidTopLevelApiKeys && Object.keys(parsed).some((key) => isTopLevelApiKeyField(key))) {
|
|
333
|
+
throw new Error('native_codex_auth_destination_api_key_field_forbidden');
|
|
334
|
+
}
|
|
335
|
+
return parsed;
|
|
336
|
+
}
|
|
337
|
+
function removeTopLevelApiKeyFields(auth) {
|
|
338
|
+
return Object.fromEntries(Object.entries(auth).filter(([key]) => !isTopLevelApiKeyField(key)));
|
|
339
|
+
}
|
|
340
|
+
function isTopLevelApiKeyField(key) {
|
|
341
|
+
const normalized = key.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
342
|
+
return normalized === 'key' || normalized === 'apikey' || normalized === 'openaiapikey';
|
|
343
|
+
}
|
|
344
|
+
async function readValidatedAuthFile(filePath, opts) {
|
|
345
|
+
const noFollow = process.platform === 'win32' ? 0 : fsConstants.O_NOFOLLOW;
|
|
346
|
+
const pathStat = await fsp.lstat(filePath);
|
|
347
|
+
if (pathStat.isSymbolicLink())
|
|
348
|
+
throw new Error(`${opts.errorPrefix}_symlink_forbidden`);
|
|
349
|
+
if (!pathStat.isFile())
|
|
350
|
+
throw new Error(`${opts.errorPrefix}_not_file`);
|
|
351
|
+
const handle = await fsp.open(filePath, fsConstants.O_RDONLY | noFollow);
|
|
352
|
+
try {
|
|
353
|
+
const stat = await handle.stat();
|
|
354
|
+
if (!stat.isFile() || stat.dev !== pathStat.dev || stat.ino !== pathStat.ino
|
|
355
|
+
|| (opts.expectedIdentity && (stat.dev !== opts.expectedIdentity.dev || stat.ino !== opts.expectedIdentity.ino))) {
|
|
356
|
+
throw new Error(`${opts.errorPrefix}_identity_changed`);
|
|
357
|
+
}
|
|
358
|
+
if (opts.requireSingleLink && stat.nlink !== 1)
|
|
359
|
+
throw new Error(`${opts.errorPrefix}_link_count_invalid`);
|
|
360
|
+
if (opts.requirePrivateMode && process.platform !== 'win32' && (stat.mode & 0o077) !== 0) {
|
|
361
|
+
throw new Error(`${opts.errorPrefix}_permissions_too_open`);
|
|
362
|
+
}
|
|
363
|
+
return await handle.readFile('utf8');
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
await handle.close();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function assertSourceAuthUnchanged(sourcePath, expectedIdentity, expectedFingerprint) {
|
|
370
|
+
try {
|
|
371
|
+
const currentText = await readValidatedAuthFile(sourcePath, {
|
|
372
|
+
expectedIdentity,
|
|
373
|
+
requirePrivateMode: true,
|
|
374
|
+
errorPrefix: 'native_codex_auth_source'
|
|
375
|
+
});
|
|
376
|
+
if (fingerprintText(currentText) !== expectedFingerprint)
|
|
377
|
+
throw new Error('native_codex_auth_source_conflict');
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (String(error?.message || error) === 'native_codex_auth_source_conflict')
|
|
381
|
+
throw error;
|
|
382
|
+
throw new Error('native_codex_auth_source_conflict');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function writeAuthFileAtomic(sourcePath, contents, revalidateSource) {
|
|
386
|
+
const noFollow = process.platform === 'win32' ? 0 : fsConstants.O_NOFOLLOW;
|
|
387
|
+
const temporaryPath = path.join(path.dirname(sourcePath), `.auth.json.sks-refresh-${process.pid}-${randomBytes(8).toString('hex')}`);
|
|
388
|
+
let temporaryCreated = false;
|
|
389
|
+
try {
|
|
390
|
+
const handle = await fsp.open(temporaryPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollow, 0o600);
|
|
391
|
+
temporaryCreated = true;
|
|
392
|
+
try {
|
|
393
|
+
await handle.writeFile(contents, 'utf8');
|
|
394
|
+
await handle.chmod(0o600);
|
|
395
|
+
await handle.sync();
|
|
396
|
+
const stat = await handle.stat();
|
|
397
|
+
if (!stat.isFile() || stat.nlink !== 1 || (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o600)) {
|
|
398
|
+
throw new Error('native_codex_auth_writeback_temp_validation_failed');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
await handle.close();
|
|
403
|
+
}
|
|
404
|
+
await revalidateSource();
|
|
405
|
+
await fsp.rename(temporaryPath, sourcePath);
|
|
406
|
+
temporaryCreated = false;
|
|
407
|
+
const persistedText = await readValidatedAuthFile(sourcePath, {
|
|
408
|
+
requirePrivateMode: true,
|
|
409
|
+
requireSingleLink: true,
|
|
410
|
+
errorPrefix: 'native_codex_auth_writeback'
|
|
411
|
+
});
|
|
412
|
+
if (fingerprintText(persistedText) !== fingerprintText(contents)) {
|
|
413
|
+
throw new Error('native_codex_auth_writeback_verification_failed');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
if (temporaryCreated)
|
|
418
|
+
await fsp.rm(temporaryPath, { force: true });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function fingerprintText(value) {
|
|
422
|
+
return createHash('sha256').update(value).digest('hex');
|
|
423
|
+
}
|
|
424
|
+
function fingerprintJson(value) {
|
|
425
|
+
return fingerprintText(stableJson(value));
|
|
426
|
+
}
|
|
427
|
+
function stableJson(value) {
|
|
428
|
+
if (Array.isArray(value))
|
|
429
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
430
|
+
if (value && typeof value === 'object') {
|
|
431
|
+
return `{${Object.entries(value)
|
|
432
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
433
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`)
|
|
434
|
+
.join(',')}}`;
|
|
435
|
+
}
|
|
436
|
+
return JSON.stringify(value);
|
|
437
|
+
}
|
|
438
|
+
function applyCleanupProof(proof, result, retainedTempRoot) {
|
|
439
|
+
if (!proof)
|
|
440
|
+
return;
|
|
441
|
+
proof.cleanup_required = result.cleanup_required;
|
|
442
|
+
proof.cleanup_status = result.status;
|
|
443
|
+
proof.cleanup_outcome = result.outcome;
|
|
444
|
+
proof.refreshed_auth_persisted = result.outcome === 'refreshed_persisted';
|
|
445
|
+
proof.recovery_temp_root_retained = result.cleanup_required;
|
|
446
|
+
if (retainedTempRoot)
|
|
447
|
+
proof.recovery_temp_root = retainedTempRoot;
|
|
448
|
+
if (result.blockers.length)
|
|
449
|
+
proof.cleanup_blockers = result.blockers;
|
|
450
|
+
else
|
|
451
|
+
delete proof.cleanup_blockers;
|
|
452
|
+
}
|
|
453
|
+
function isPathInside(parent, child) {
|
|
454
|
+
if (!parent)
|
|
455
|
+
return false;
|
|
456
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
457
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
458
|
+
}
|