@iislee/opencodex 2.11.0 → 2.35.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/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -29,13 +29,184 @@
|
|
|
29
29
|
* hardenSecretDir — same contract for directories.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import { existsSync } from "node:fs";
|
|
32
|
+
import { existsSync, statSync } from "node:fs";
|
|
33
33
|
import { env, platform } from "node:process";
|
|
34
|
+
import { resolveTrustedWindowsIcaclsExe } from "./windows-elevation";
|
|
35
|
+
import {
|
|
36
|
+
resolveCurrentWindowsPrincipal,
|
|
37
|
+
resolveCurrentWindowsPrincipalAsync,
|
|
38
|
+
setSyntheticWindowsPrincipalForTests,
|
|
39
|
+
} from "./windows-user-principal";
|
|
40
|
+
|
|
41
|
+
const hardenedDirectories = new Map<string, HardenedIdentity>();
|
|
42
|
+
const hardenedPaths = new Map<string, HardenedIdentity>();
|
|
43
|
+
/**
|
|
44
|
+
* Paths whose harden TIMED OUT this process: do not re-stall every loadConfig on them.
|
|
45
|
+
* `false` means one explicitly authorized recovery attempt remains; `true` means
|
|
46
|
+
* that attempt was consumed. Ordinary callers never consume it.
|
|
47
|
+
*/
|
|
48
|
+
const timedOutPaths = new Map<string, boolean>();
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The memo value: `object:freshness` for a file a harden was actually attributed
|
|
52
|
+
* to.
|
|
53
|
+
*
|
|
54
|
+
* There is deliberately no null member. An observation that cannot be read is
|
|
55
|
+
* not stored at all — the entry is deleted — because a "recorded as unverifiable"
|
|
56
|
+
* value was dead code the moment attribution became a before/after comparison,
|
|
57
|
+
* and a branch nothing can reach is a branch no test can defend.
|
|
58
|
+
*/
|
|
59
|
+
type HardenedIdentity = string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What a stat can tell us about WHICH OBJECT is at a path.
|
|
63
|
+
*
|
|
64
|
+
* Two fields, deliberately separated, because conflating them shipped a bug:
|
|
65
|
+
*
|
|
66
|
+
* - `object` — `dev:ino`. Answers "is this the same file". Survives an ACL or
|
|
67
|
+
* permission change, which is exactly what we need across an icacls call.
|
|
68
|
+
* - `freshness` — `ctimeNs`. Answers "has this file's metadata moved since". It
|
|
69
|
+
* distinguishes an unlink/recreate that ext4 gave the same inode back for, and
|
|
70
|
+
* it MOVES when permissions change.
|
|
71
|
+
*
|
|
72
|
+
* The first version used `dev:ino:ctimeNs` for both jobs. Since chmod bumps ctime
|
|
73
|
+
* — probed, `{ctimeChangedByChmod: true}` — and icacls is a permission change, the
|
|
74
|
+
* before/after comparison would have rejected its own successful harden and failed
|
|
75
|
+
* closed on every first harden on Windows. Requiring the identity to be unchanged
|
|
76
|
+
* across an operation whose entire purpose is to change it is not a strict check;
|
|
77
|
+
* it is a broken one.
|
|
78
|
+
*/
|
|
79
|
+
interface PathObservation {
|
|
80
|
+
readonly object: string;
|
|
81
|
+
readonly freshness: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Observe which object is at a path, and how fresh it is.
|
|
86
|
+
*
|
|
87
|
+
* `dev:ino` alone is not enough to detect a replacement, which a Linux CI run
|
|
88
|
+
* proved: ext4 reuses the inode of an unlinked file immediately — 100 of 100
|
|
89
|
+
* unlink/recreate cycles produced the SAME `ino`, while macOS reused none in 200
|
|
90
|
+
* and happily reported the earlier fix working. That is why `freshness` exists;
|
|
91
|
+
* `ctimeNs` differed in 100 of 100 of those same cycles.
|
|
92
|
+
*
|
|
93
|
+
* `bigint: true` is used because `ctimeNs` exists only in that variant.
|
|
94
|
+
*
|
|
95
|
+
* Test seam: `setStatForTests` replaces this reader so a test can vary `dev`,
|
|
96
|
+
* `ino`, and `ctimeNs` independently. Mirroring the implementation's string
|
|
97
|
+
* format in test setup proves nothing about which components production uses —
|
|
98
|
+
* an audit removed `dev` and all forty tests still passed.
|
|
99
|
+
*
|
|
100
|
+
* UNVERIFIED: the plain `ino` is reported to be 0 on NTFS while the bigint form
|
|
101
|
+
* carries the file index, and the zero-ino guard exists for that case. Neither
|
|
102
|
+
* Darwin nor Linux CI can confirm it and no pinned-Bun Windows probe has run.
|
|
103
|
+
* It is defensive code, not a demonstrated platform fact.
|
|
104
|
+
*/
|
|
105
|
+
type StatReader = (path: string) => { dev: bigint; ino: bigint; ctimeNs: bigint };
|
|
106
|
+
|
|
107
|
+
const defaultStatReader: StatReader = path => {
|
|
108
|
+
const s = statSync(path, { bigint: true });
|
|
109
|
+
return { dev: s.dev, ino: s.ino, ctimeNs: s.ctimeNs };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
let statReader: StatReader = defaultStatReader;
|
|
113
|
+
|
|
114
|
+
/** Test seam: drive dev / ino / ctime independently. */
|
|
115
|
+
export function setStatForTests(reader: StatReader | null): void {
|
|
116
|
+
statReader = reader ?? defaultStatReader;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function observe(targetPath: string): PathObservation | null {
|
|
120
|
+
try {
|
|
121
|
+
const s = statReader(targetPath);
|
|
122
|
+
if (s.ino === 0n) return null;
|
|
123
|
+
return { object: `${s.dev}:${s.ino}`, freshness: `${s.ctimeNs}` };
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function memoValue(seen: PathObservation): HardenedIdentity {
|
|
130
|
+
return `${seen.object}:${seen.freshness}`;
|
|
131
|
+
}
|
|
34
132
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
133
|
+
/**
|
|
134
|
+
* True only when this exact FILE was hardened, not merely this pathname.
|
|
135
|
+
*
|
|
136
|
+
* The memo used to be a `Set<string>` of paths. A stable destination — such as
|
|
137
|
+
* the coordinator database `hardenStableLockFile` hardens — can be unlinked and
|
|
138
|
+
* recreated at the same name, and the replacement inherited the previous file's
|
|
139
|
+
* hardening while never having been through icacls. Ephemeral temps escaped this
|
|
140
|
+
* only because atomic writers call `forgetEphemeralSecretPath` once the temp is
|
|
141
|
+
* gone; nothing does that for a stable path.
|
|
142
|
+
*/
|
|
143
|
+
function memoSatisfied(cache: Map<string, HardenedIdentity>, targetPath: string): boolean {
|
|
144
|
+
const remembered = cache.get(targetPath);
|
|
145
|
+
if (remembered === undefined) return false;
|
|
146
|
+
const current = observe(targetPath);
|
|
147
|
+
// Unreadable now is not "unchanged": re-harden rather than trust a value we
|
|
148
|
+
// cannot confirm still describes what is there.
|
|
149
|
+
//
|
|
150
|
+
// A miss RETIRES the entry rather than leaving it. Keeping it left the cache in
|
|
151
|
+
// a state nothing could justify: after a mismatch and a failed re-harden, the
|
|
152
|
+
// stale value survived, so restoring the old identity would satisfy it again
|
|
153
|
+
// without any ACL work. That needs exact-identity ABA to bite — outside the
|
|
154
|
+
// proof bound this unit claims — but "the consequence is out of scope" is not a
|
|
155
|
+
// reason to keep an entry we have just proven does not describe what is there.
|
|
156
|
+
if (current === null || memoValue(current) !== remembered) {
|
|
157
|
+
cache.delete(targetPath);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Record a harden ONLY if the file we hardened is still the file at that path.
|
|
165
|
+
*
|
|
166
|
+
* Reading identity after the ACL sequence returns answers "what is there now",
|
|
167
|
+
* which is not the same question as "what did icacls operate on". A replacement
|
|
168
|
+
* landing mid-sequence — probed by swapping the file during the final
|
|
169
|
+
* `/remove:g` — made the memo remember the REPLACEMENT as hardened, so the next
|
|
170
|
+
* acquisition skipped ACL work on a file that had never seen it:
|
|
171
|
+
*
|
|
172
|
+
* {identityChangedDuringHarden: true, callsForOriginal: 3, totalCalls: 3,
|
|
173
|
+
* replacementWasHardened: false}
|
|
174
|
+
*
|
|
175
|
+
* So the OBJECT is captured before the sequence and compared after it. Only the
|
|
176
|
+
* object — `dev:ino` — because icacls changes permissions, and `ctimeNs` moves
|
|
177
|
+
* when permissions change (probed: `{ctimeChangedByChmod: true}`). Comparing the
|
|
178
|
+
* full identity across the call would have rejected every successful harden and
|
|
179
|
+
* failed closed on the first harden on Windows: the check would have been
|
|
180
|
+
* demanding that an operation not do the thing it exists to do.
|
|
181
|
+
*
|
|
182
|
+
* The memo then stores the object plus the freshness read AFTER hardening, which
|
|
183
|
+
* is the state a later lookup should match.
|
|
184
|
+
*
|
|
185
|
+
* A changed object, or an unreadable observation at either end, means we cannot
|
|
186
|
+
* say what was hardened: the memo is cleared rather than written, and required
|
|
187
|
+
* callers fail closed. An optional caller soft-fails, as it does for every other
|
|
188
|
+
* unproven ACL.
|
|
189
|
+
*
|
|
190
|
+
* Returns true when the harden may be reported successful.
|
|
191
|
+
*/
|
|
192
|
+
function recordHarden(
|
|
193
|
+
cache: Map<string, HardenedIdentity>,
|
|
194
|
+
targetPath: string,
|
|
195
|
+
before: PathObservation | null,
|
|
196
|
+
): boolean {
|
|
197
|
+
const after = observe(targetPath);
|
|
198
|
+
if (before === null || after === null || before.object !== after.object) {
|
|
199
|
+
// Never leave a memo behind for a file we cannot vouch for, including one
|
|
200
|
+
// written by an earlier successful harden of a now-replaced file.
|
|
201
|
+
cache.delete(targetPath);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
cache.set(targetPath, memoValue(after));
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const SUBSTITUTED_DIAGNOSTIC =
|
|
209
|
+
"ACL hardening could not be attributed — the file at this path changed during hardening";
|
|
39
210
|
|
|
40
211
|
export interface HardenResult {
|
|
41
212
|
ok: boolean;
|
|
@@ -51,6 +222,12 @@ export interface HardenOptions {
|
|
|
51
222
|
* Must NOT be a parent directory — directory ACLs are not authoritative for new files.
|
|
52
223
|
*/
|
|
53
224
|
timeoutMemoKey?: string;
|
|
225
|
+
/**
|
|
226
|
+
* Consume the one recovery attempt for a previously timed-out memo key.
|
|
227
|
+
* Only a caller that owns its own single-flight and bounded retry policy should
|
|
228
|
+
* set this. It never clears or bypasses an already-consumed timeout memo.
|
|
229
|
+
*/
|
|
230
|
+
retryTimedOutOnce?: boolean;
|
|
54
231
|
}
|
|
55
232
|
|
|
56
233
|
/**
|
|
@@ -58,9 +235,23 @@ export interface HardenOptions {
|
|
|
58
235
|
* timeout retry and the diagnostic verification pass (no per-attempt fresh budget:
|
|
59
236
|
* loadConfig hardens dir+config+auth sequentially, so per-attempt budgets stack
|
|
60
237
|
* into multi-minute startup stalls). Override with OPENCODEX_ACL_TIMEOUT_MS
|
|
61
|
-
* (integer ms, clamped to [1000, 60000]; invalid values fall back to
|
|
238
|
+
* (integer ms, clamped to [1000, 60000]; invalid values fall back to 30000).
|
|
239
|
+
*
|
|
240
|
+
* The default was 5s until #1156. One envelope has to cover the whole sequence —
|
|
241
|
+
* `/grant:r`, `/inheritance:r`, `/remove:g`, plus the conditional `/findsid`
|
|
242
|
+
* verification — and on machines where icacls is slow (Defender real-time scanning,
|
|
243
|
+
* roaming profiles, a domain-controller round trip) 5s ran out mid-sequence. The
|
|
244
|
+
* harden then failed closed, the native-main owner published a permanent
|
|
245
|
+
* `unavailable`, and every native request returned 503 until restart. A slow start
|
|
246
|
+
* is recoverable; that is not.
|
|
247
|
+
*
|
|
248
|
+
* The cost is honest and worth stating: because loadConfig hardens three paths
|
|
249
|
+
* sequentially, the timeout-path worst case at load is ~90s, and the owner path
|
|
250
|
+
* (initial call + one recovery) is ~60.25s. Both require icacls to be
|
|
251
|
+
* pathologically slow on every call; a healthy machine finishes in milliseconds
|
|
252
|
+
* and sees no change. Operators who prefer the old bound can set the env override.
|
|
62
253
|
*/
|
|
63
|
-
const HARDEN_DEADLINE_DEFAULT_MS =
|
|
254
|
+
const HARDEN_DEADLINE_DEFAULT_MS = 30_000;
|
|
64
255
|
const HARDEN_DEADLINE_MIN_MS = 1_000;
|
|
65
256
|
const HARDEN_DEADLINE_MAX_MS = 60_000;
|
|
66
257
|
|
|
@@ -83,22 +274,55 @@ export interface IcaclsResult {
|
|
|
83
274
|
type IcaclsRunner = (args: string[], timeoutMs: number) => IcaclsResult;
|
|
84
275
|
type AsyncIcaclsRunner = (args: string[], timeoutMs: number) => Promise<IcaclsResult>;
|
|
85
276
|
|
|
277
|
+
function resolveIcaclsExecutable(): string {
|
|
278
|
+
// Same authority as schtasks/powershell: never take icacls from PATH.
|
|
279
|
+
// A bun-shim or stripped PATH makes `icacls.exe` throw ENOENT, which used to
|
|
280
|
+
// surface as "filesystem may not support per-user NTFS ACLs".
|
|
281
|
+
return resolveTrustedWindowsIcaclsExe();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function spawnFailedResult(): IcaclsResult {
|
|
285
|
+
return { success: false, exitCode: null, timedOut: false, stdout: "" };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Spawn icacls asynchronously, or return null when the executable cannot be
|
|
290
|
+
* launched. The pipe/ignore stdio literals stay inferred here so `stdout` keeps
|
|
291
|
+
* its `ReadableStream` type instead of widening to the generic default.
|
|
292
|
+
*/
|
|
293
|
+
function trySpawnIcacls(args: string[]) {
|
|
294
|
+
try {
|
|
295
|
+
return Bun.spawn([resolveIcaclsExecutable(), ...args], {
|
|
296
|
+
stdin: "ignore",
|
|
297
|
+
stdout: "pipe",
|
|
298
|
+
stderr: "ignore",
|
|
299
|
+
windowsHide: true,
|
|
300
|
+
});
|
|
301
|
+
} catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
86
306
|
function defaultIcaclsRunner(args: string[], timeoutMs: number): IcaclsResult {
|
|
87
307
|
// Bun.spawnSync with windowsHide: Node execFileSync has hung under the GUI/proxy even
|
|
88
308
|
// with windowsHide, and console-subsystem tools flash a visible window otherwise.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
309
|
+
try {
|
|
310
|
+
const result = Bun.spawnSync([resolveIcaclsExecutable(), ...args], {
|
|
311
|
+
stdin: "ignore",
|
|
312
|
+
stdout: "pipe",
|
|
313
|
+
stderr: "ignore",
|
|
314
|
+
timeout: timeoutMs,
|
|
315
|
+
windowsHide: true,
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
success: result.success,
|
|
319
|
+
exitCode: result.exitCode,
|
|
320
|
+
timedOut: result.exitedDueToTimeout ?? false,
|
|
321
|
+
stdout: result.stdout ? result.stdout.toString() : "",
|
|
322
|
+
};
|
|
323
|
+
} catch {
|
|
324
|
+
return spawnFailedResult();
|
|
325
|
+
}
|
|
102
326
|
}
|
|
103
327
|
|
|
104
328
|
/**
|
|
@@ -107,12 +331,8 @@ function defaultIcaclsRunner(args: string[], timeoutMs: number): IcaclsResult {
|
|
|
107
331
|
* we still await process exit before classifying so settlement is confirmed.
|
|
108
332
|
*/
|
|
109
333
|
async function defaultAsyncIcaclsRunner(args: string[], timeoutMs: number): Promise<IcaclsResult> {
|
|
110
|
-
const proc =
|
|
111
|
-
|
|
112
|
-
stdout: "pipe",
|
|
113
|
-
stderr: "ignore",
|
|
114
|
-
windowsHide: true,
|
|
115
|
-
});
|
|
334
|
+
const proc = trySpawnIcacls(args);
|
|
335
|
+
if (!proc) return spawnFailedResult();
|
|
116
336
|
let timedOutByUs = false;
|
|
117
337
|
const timer = setTimeout(() => {
|
|
118
338
|
timedOutByUs = true;
|
|
@@ -151,9 +371,21 @@ export function setAsyncIcaclsRunnerForTests(runner: AsyncIcaclsRunner | null):
|
|
|
151
371
|
asyncIcaclsRunner = runner ?? defaultAsyncIcaclsRunner;
|
|
152
372
|
}
|
|
153
373
|
|
|
154
|
-
/**
|
|
374
|
+
/**
|
|
375
|
+
* Test seam: force the platform gate (e.g. "win32") so CI on POSIX reaches the runner.
|
|
376
|
+
*
|
|
377
|
+
* Faking win32 on a host without System32 also has to supply a principal, or
|
|
378
|
+
* every forced-branch test would fail on the identity lookup instead of
|
|
379
|
+
* exercising icacls. The synthetic value is registered with the resolver, not
|
|
380
|
+
* chosen here, so a test that injects its own runner still wins.
|
|
381
|
+
*/
|
|
382
|
+
const SYNTHETIC_TEST_PRINCIPAL = "*S-1-5-21-1-2-3-1001";
|
|
383
|
+
|
|
155
384
|
export function setPlatformForTests(value: string | null): void {
|
|
156
385
|
platformOverride = value;
|
|
386
|
+
setSyntheticWindowsPrincipalForTests(
|
|
387
|
+
value === "win32" && platform !== "win32" ? SYNTHETIC_TEST_PRINCIPAL : null,
|
|
388
|
+
);
|
|
157
389
|
}
|
|
158
390
|
|
|
159
391
|
/** Test seam: injectable clock for deadline tests (no real sleeps). */
|
|
@@ -173,11 +405,44 @@ export function forgetHardenedSecretPath(targetPath: string): void {
|
|
|
173
405
|
hardenedPaths.delete(targetPath);
|
|
174
406
|
}
|
|
175
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Ephemeral-path lifecycle release: clears the success memo AND any timeout
|
|
410
|
+
* memo keyed by THIS TEMP path in both namespaces. Call only after the temp is
|
|
411
|
+
* proven absent (successful rename, successful unlink, ENOENT, or an explicit
|
|
412
|
+
* non-existence check). Never pass a stable destination: destination-keyed
|
|
413
|
+
* timeout memos are intentional anti-restall state and are not touched here.
|
|
414
|
+
*/
|
|
415
|
+
export function forgetEphemeralSecretPath(tempPath: string): void {
|
|
416
|
+
hardenedPaths.delete(tempPath);
|
|
417
|
+
timedOutPaths.delete(`required:${tempPath}`);
|
|
418
|
+
timedOutPaths.delete(`optional:${tempPath}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Directory counterpart for a proven-absent ephemeral staging root. */
|
|
422
|
+
export function forgetEphemeralSecretDir(tempPath: string): void {
|
|
423
|
+
hardenedDirectories.delete(tempPath);
|
|
424
|
+
timedOutPaths.delete(`required:${tempPath}`);
|
|
425
|
+
timedOutPaths.delete(`optional:${tempPath}`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Test seam: timeout memo sets return to baseline after ephemeral cleanup. */
|
|
429
|
+
export function timedOutSecretPathCountForTests(): number {
|
|
430
|
+
return timedOutPaths.size;
|
|
431
|
+
}
|
|
432
|
+
|
|
176
433
|
/** Test seam for proving ephemeral success memos do not grow across replacements. */
|
|
177
434
|
export function hardenedSecretPathCountForTests(): number {
|
|
178
435
|
return hardenedPaths.size;
|
|
179
436
|
}
|
|
180
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Directory counterpart. It had no seam, and that absence hid a real gap: a
|
|
440
|
+
* directory-only pathname memo passed every file-based test in this suite.
|
|
441
|
+
*/
|
|
442
|
+
export function hardenedSecretDirCountForTests(): number {
|
|
443
|
+
return hardenedDirectories.size;
|
|
444
|
+
}
|
|
445
|
+
|
|
181
446
|
function effectivePlatform(): string {
|
|
182
447
|
return platformOverride ?? platform;
|
|
183
448
|
}
|
|
@@ -197,17 +462,26 @@ function icaclsError(step: string, result: IcaclsResult): NodeJS.ErrnoException
|
|
|
197
462
|
}
|
|
198
463
|
|
|
199
464
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
465
|
+
* The ACL principal is the effective token SID and nothing else.
|
|
466
|
+
*
|
|
467
|
+
* There is no name-shaped fallback here, and that absence is the fix for #1149
|
|
468
|
+
* rather than an omission. `USERDOMAIN\USERNAME` has the right shape but is not
|
|
469
|
+
* evidence of the current token's subject, and both variables are writable by
|
|
470
|
+
* the process that launched us. Granting Full Control to a wrong principal and
|
|
471
|
+
* then running `/inheritance:r` is destructive in both directions: another
|
|
472
|
+
* account can be left holding the secret, or the file can be left with no ACE
|
|
473
|
+
* the current user can use. When the SID cannot be resolved we decline.
|
|
474
|
+
*
|
|
475
|
+
* Non-Windows hosts that force this branch through `setPlatformForTests` get
|
|
476
|
+
* their principal from `setSyntheticWindowsPrincipalForTests`, which lives with
|
|
477
|
+
* the resolver so an injected runner can still take precedence over it.
|
|
203
478
|
*/
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return domain ? `${domain}\\${username}` : username;
|
|
479
|
+
function currentWindowsPrincipal(deadline: number): string {
|
|
480
|
+
return resolveCurrentWindowsPrincipal(deadline - nowFn());
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function currentWindowsPrincipalAsync(deadline: number): Promise<string> {
|
|
484
|
+
return resolveCurrentWindowsPrincipalAsync(deadline - nowFn());
|
|
211
485
|
}
|
|
212
486
|
|
|
213
487
|
/**
|
|
@@ -227,10 +501,7 @@ function grantAce(user: string, directory: boolean): string {
|
|
|
227
501
|
}
|
|
228
502
|
|
|
229
503
|
function runIcacls(targetPath: string, directory: boolean, deadline: number): void {
|
|
230
|
-
const
|
|
231
|
-
if (!user) {
|
|
232
|
-
throw new Error("Cannot determine current Windows user for ACL hardening");
|
|
233
|
-
}
|
|
504
|
+
const principal = currentWindowsPrincipal(deadline);
|
|
234
505
|
|
|
235
506
|
// The deadline is owned by hardenEntry (total budget incl. retry + verification).
|
|
236
507
|
const run = (step: string, args: string[]): IcaclsResult => {
|
|
@@ -247,7 +518,7 @@ function runIcacls(targetPath: string, directory: boolean, deadline: number): vo
|
|
|
247
518
|
|
|
248
519
|
// Step 1: grant current user full control BEFORE any destructive ACL change.
|
|
249
520
|
// If this fails, inheritance is untouched and the writer keeps inherited access.
|
|
250
|
-
runOrThrow("/grant:r", [targetPath, "/grant:r", grantAce(
|
|
521
|
+
runOrThrow("/grant:r", [targetPath, "/grant:r", grantAce(principal, directory)]);
|
|
251
522
|
|
|
252
523
|
// Step 2: disable inheritance and remove inherited ACEs. The explicit owner ACE
|
|
253
524
|
// from step 1 survives this transition, so a later failure still leaves cleanup access.
|
|
@@ -276,10 +547,7 @@ function runIcacls(targetPath: string, directory: boolean, deadline: number): vo
|
|
|
276
547
|
|
|
277
548
|
/** Async counterpart of runIcacls — same step order and timeout/error classification (#612). */
|
|
278
549
|
async function runIcaclsAsync(targetPath: string, directory: boolean, deadline: number): Promise<void> {
|
|
279
|
-
const
|
|
280
|
-
if (!user) {
|
|
281
|
-
throw new Error("Cannot determine current Windows user for ACL hardening");
|
|
282
|
-
}
|
|
550
|
+
const principal = await currentWindowsPrincipalAsync(deadline);
|
|
283
551
|
|
|
284
552
|
const run = async (step: string, args: string[]): Promise<IcaclsResult> => {
|
|
285
553
|
const remaining = deadline - nowFn();
|
|
@@ -293,7 +561,7 @@ async function runIcaclsAsync(targetPath: string, directory: boolean, deadline:
|
|
|
293
561
|
if (!result.success) throw icaclsError(step, result);
|
|
294
562
|
};
|
|
295
563
|
|
|
296
|
-
await runOrThrow("/grant:r", [targetPath, "/grant:r", grantAce(
|
|
564
|
+
await runOrThrow("/grant:r", [targetPath, "/grant:r", grantAce(principal, directory)]);
|
|
297
565
|
await runOrThrow("/inheritance:r", [targetPath, "/inheritance:r"]);
|
|
298
566
|
|
|
299
567
|
const removal = await run("/remove:g", [targetPath, "/remove:g", ...BROAD_SIDS]);
|
|
@@ -327,6 +595,8 @@ function sanitizeDiagnostics(error: unknown): string {
|
|
|
327
595
|
return `ACL hardening failed (${code}) — permission denied running icacls`;
|
|
328
596
|
case "EICACLS":
|
|
329
597
|
return "ACL hardening failed (EICACLS) — icacls command error; filesystem may not support per-user NTFS ACLs";
|
|
598
|
+
case "EACLIDENTITY":
|
|
599
|
+
return "ACL hardening failed (EACLIDENTITY) — the effective Windows account SID could not be resolved";
|
|
330
600
|
default:
|
|
331
601
|
return `ACL hardening failed${code ? ` (${code})` : ""} — filesystem may not support per-user NTFS ACLs`;
|
|
332
602
|
}
|
|
@@ -337,6 +607,60 @@ function isTimeoutError(error: unknown): boolean {
|
|
|
337
607
|
&& String((error as NodeJS.ErrnoException).code) === "ETIMEDOUT";
|
|
338
608
|
}
|
|
339
609
|
|
|
610
|
+
/** Preserve only the bounded machine-readable cause on a sanitized public error. */
|
|
611
|
+
function sanitizedAclError(diagnostics: string, cause: unknown): NodeJS.ErrnoException {
|
|
612
|
+
const error = new Error(diagnostics) as NodeJS.ErrnoException;
|
|
613
|
+
const code = cause && typeof cause === "object" && "code" in cause
|
|
614
|
+
? String((cause as { code?: unknown }).code)
|
|
615
|
+
: "";
|
|
616
|
+
// EACLIDENTITY belongs here for the same reason as the rest: a caller that
|
|
617
|
+
// catches a required-mode failure has to tell "the SID could not be resolved"
|
|
618
|
+
// apart from "icacls stalled". Without it the code was dropped and only the
|
|
619
|
+
// message carried the cause, which no caller can branch on.
|
|
620
|
+
if (
|
|
621
|
+
code === "ETIMEDOUT" ||
|
|
622
|
+
code === "EICACLS" ||
|
|
623
|
+
code === "EACCES" ||
|
|
624
|
+
code === "EPERM" ||
|
|
625
|
+
code === "EACLIDENTITY"
|
|
626
|
+
) {
|
|
627
|
+
error.code = code;
|
|
628
|
+
}
|
|
629
|
+
return error;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function previousTimeoutError(retryConsumed: boolean): NodeJS.ErrnoException {
|
|
633
|
+
if (retryConsumed) {
|
|
634
|
+
const error = new Error(
|
|
635
|
+
"ACL hardening skipped — the previous timeout recovery was already consumed",
|
|
636
|
+
) as NodeJS.ErrnoException;
|
|
637
|
+
error.code = "EACLRETRYEXHAUSTED";
|
|
638
|
+
return error;
|
|
639
|
+
}
|
|
640
|
+
return sanitizedAclError(
|
|
641
|
+
"ACL hardening skipped — previous attempt timed out",
|
|
642
|
+
Object.assign(new Error("timeout"), { code: "ETIMEDOUT" }),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** Consume, but never reset, the single explicit recovery attempt for this key. */
|
|
647
|
+
function timeoutMemoErrorIfBlocked(
|
|
648
|
+
memoKey: string,
|
|
649
|
+
opts: HardenOptions,
|
|
650
|
+
): NodeJS.ErrnoException | null {
|
|
651
|
+
const retryConsumed = timedOutPaths.get(memoKey);
|
|
652
|
+
if (retryConsumed === undefined) return null;
|
|
653
|
+
if (opts.retryTimedOutOnce && retryConsumed === false) {
|
|
654
|
+
timedOutPaths.set(memoKey, true);
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
return previousTimeoutError(retryConsumed);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function recordTimeout(memoKey: string): void {
|
|
661
|
+
if (!timedOutPaths.has(memoKey)) timedOutPaths.set(memoKey, false);
|
|
662
|
+
}
|
|
663
|
+
|
|
340
664
|
/**
|
|
341
665
|
* Diagnostic-only post-timeout probe (never promotes to ok:true — a clean /findsid
|
|
342
666
|
* does not prove inheritance was disabled or the user grant ran; only a fully
|
|
@@ -393,16 +717,18 @@ function hardenEntry(
|
|
|
393
717
|
targetPath: string,
|
|
394
718
|
directory: boolean,
|
|
395
719
|
opts: HardenOptions,
|
|
396
|
-
cache:
|
|
720
|
+
cache: Map<string, HardenedIdentity>,
|
|
397
721
|
): HardenResult {
|
|
398
|
-
|
|
722
|
+
// Observed absence retires the memo. Leaving it would let a later file at this
|
|
723
|
+
// path satisfy the cache if the filesystem ever hands back a matching identity.
|
|
724
|
+
if (!existsSync(targetPath)) { cache.delete(targetPath); return { ok: true }; }
|
|
399
725
|
if (effectivePlatform() !== "win32") return { ok: true };
|
|
400
|
-
if (cache
|
|
726
|
+
if (memoSatisfied(cache, targetPath)) return { ok: true };
|
|
401
727
|
const memoKey = timeoutMemoKey(targetPath, opts);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (opts.required) throw
|
|
405
|
-
return { ok: false, diagnostics };
|
|
728
|
+
const timeoutMemoError = timeoutMemoErrorIfBlocked(memoKey, opts);
|
|
729
|
+
if (timeoutMemoError) {
|
|
730
|
+
if (opts.required) throw timeoutMemoError;
|
|
731
|
+
return { ok: false, diagnostics: timeoutMemoError.message };
|
|
406
732
|
}
|
|
407
733
|
|
|
408
734
|
const deadline = nowFn() + resolveHardenDeadlineMs();
|
|
@@ -410,10 +736,18 @@ function hardenEntry(
|
|
|
410
736
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
411
737
|
if (attempt > 0 && deadline - nowFn() <= 0) break; // retry only while budget remains
|
|
412
738
|
try {
|
|
739
|
+
// Captured BEFORE the sequence: this is the file we are about to harden.
|
|
740
|
+
const before = observe(targetPath);
|
|
413
741
|
runIcacls(targetPath, directory, deadline);
|
|
414
|
-
cache
|
|
742
|
+
if (!recordHarden(cache, targetPath, before)) {
|
|
743
|
+
if (opts.required) throw new Error(SUBSTITUTED_DIAGNOSTIC);
|
|
744
|
+
return { ok: false, diagnostics: SUBSTITUTED_DIAGNOSTIC };
|
|
745
|
+
}
|
|
746
|
+
timedOutPaths.delete(memoKey);
|
|
415
747
|
return { ok: true };
|
|
416
748
|
} catch (err) {
|
|
749
|
+
// A substitution is not a transient icacls stall; do not spend the retry on it.
|
|
750
|
+
if (err instanceof Error && err.message === SUBSTITUTED_DIAGNOSTIC) throw err;
|
|
417
751
|
lastErr = err;
|
|
418
752
|
if (!isTimeoutError(err)) break; // real failures do not retry
|
|
419
753
|
}
|
|
@@ -421,14 +755,14 @@ function hardenEntry(
|
|
|
421
755
|
|
|
422
756
|
const diagnostics = sanitizeDiagnostics(lastErr);
|
|
423
757
|
if (isTimeoutError(lastErr)) {
|
|
424
|
-
|
|
758
|
+
recordTimeout(memoKey);
|
|
425
759
|
const state = describeAclStateAfterTimeout(targetPath, deadline);
|
|
426
760
|
const annotated = `${diagnostics}; ${state}`;
|
|
427
|
-
if (opts.required) throw
|
|
761
|
+
if (opts.required) throw sanitizedAclError(annotated, lastErr);
|
|
428
762
|
console.warn(`[opencodex] ${annotated} — continuing without NTFS ACL harden`);
|
|
429
763
|
return { ok: false, diagnostics: annotated };
|
|
430
764
|
}
|
|
431
|
-
if (opts.required) throw
|
|
765
|
+
if (opts.required) throw sanitizedAclError(diagnostics, lastErr);
|
|
432
766
|
return { ok: false, diagnostics };
|
|
433
767
|
}
|
|
434
768
|
|
|
@@ -437,16 +771,16 @@ async function hardenEntryAsync(
|
|
|
437
771
|
targetPath: string,
|
|
438
772
|
directory: boolean,
|
|
439
773
|
opts: HardenOptions,
|
|
440
|
-
cache:
|
|
774
|
+
cache: Map<string, HardenedIdentity>,
|
|
441
775
|
): Promise<HardenResult> {
|
|
442
|
-
if (!existsSync(targetPath)) return { ok: true };
|
|
776
|
+
if (!existsSync(targetPath)) { cache.delete(targetPath); return { ok: true }; }
|
|
443
777
|
if (effectivePlatform() !== "win32") return { ok: true };
|
|
444
|
-
if (cache
|
|
778
|
+
if (memoSatisfied(cache, targetPath)) return { ok: true };
|
|
445
779
|
const memoKey = timeoutMemoKey(targetPath, opts);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (opts.required) throw
|
|
449
|
-
return { ok: false, diagnostics };
|
|
780
|
+
const timeoutMemoError = timeoutMemoErrorIfBlocked(memoKey, opts);
|
|
781
|
+
if (timeoutMemoError) {
|
|
782
|
+
if (opts.required) throw timeoutMemoError;
|
|
783
|
+
return { ok: false, diagnostics: timeoutMemoError.message };
|
|
450
784
|
}
|
|
451
785
|
|
|
452
786
|
const deadline = nowFn() + resolveHardenDeadlineMs();
|
|
@@ -454,10 +788,16 @@ async function hardenEntryAsync(
|
|
|
454
788
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
455
789
|
if (attempt > 0 && deadline - nowFn() <= 0) break;
|
|
456
790
|
try {
|
|
791
|
+
const before = observe(targetPath);
|
|
457
792
|
await runIcaclsAsync(targetPath, directory, deadline);
|
|
458
|
-
cache
|
|
793
|
+
if (!recordHarden(cache, targetPath, before)) {
|
|
794
|
+
if (opts.required) throw new Error(SUBSTITUTED_DIAGNOSTIC);
|
|
795
|
+
return { ok: false, diagnostics: SUBSTITUTED_DIAGNOSTIC };
|
|
796
|
+
}
|
|
797
|
+
timedOutPaths.delete(memoKey);
|
|
459
798
|
return { ok: true };
|
|
460
799
|
} catch (err) {
|
|
800
|
+
if (err instanceof Error && err.message === SUBSTITUTED_DIAGNOSTIC) throw err;
|
|
461
801
|
lastErr = err;
|
|
462
802
|
if (!isTimeoutError(err)) break;
|
|
463
803
|
}
|
|
@@ -465,14 +805,14 @@ async function hardenEntryAsync(
|
|
|
465
805
|
|
|
466
806
|
const diagnostics = sanitizeDiagnostics(lastErr);
|
|
467
807
|
if (isTimeoutError(lastErr)) {
|
|
468
|
-
|
|
808
|
+
recordTimeout(memoKey);
|
|
469
809
|
const state = await describeAclStateAfterTimeoutAsync(targetPath, deadline);
|
|
470
810
|
const annotated = `${diagnostics}; ${state}`;
|
|
471
|
-
if (opts.required) throw
|
|
811
|
+
if (opts.required) throw sanitizedAclError(annotated, lastErr);
|
|
472
812
|
console.warn(`[opencodex] ${annotated} — continuing without NTFS ACL harden`);
|
|
473
813
|
return { ok: false, diagnostics: annotated };
|
|
474
814
|
}
|
|
475
|
-
if (opts.required) throw
|
|
815
|
+
if (opts.required) throw sanitizedAclError(diagnostics, lastErr);
|
|
476
816
|
return { ok: false, diagnostics };
|
|
477
817
|
}
|
|
478
818
|
|