talos-code 0.0.1 → 0.2.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/CHANGELOG.md +87 -0
- package/LICENSE +661 -0
- package/README.md +116 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +100 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +244 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +46 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +92 -0
- package/dist/i18n/en/errors.js +267 -0
- package/dist/i18n/en/firstrun.js +101 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +774 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +331 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/openrouter-login.js +172 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-archive.js +69 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +265 -0
- package/dist/runtime/talos-composition.js +1011 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/agent-view.js +51 -0
- package/dist/tui/app.js +4098 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +560 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +38 -0
- package/dist/tui/components/terminal-shell.js +214 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/descendant-approvals.js +104 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +148 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +56 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +43 -0
- package/dist/tui/overlays/approval-dialog.js +642 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +77 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +171 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +289 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +791 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/setup-wizard.js +46 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +82 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +27 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +64 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { open, realpath } from 'node:fs/promises';
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* ⛔⛔⛔ 30/8 — questo file portava anche `createPathPolicy()`, la fabbrica
|
|
6
|
+
* che risolveva le campagne JSONL di TALOS-BANCO per la Board — RIMOSSA
|
|
7
|
+
* insieme a `campaign-service.mjs`/`report-source.mjs`/`cost-reader.mjs`
|
|
8
|
+
* (piano `elegant-spinning-dongarra.md`, "Board — da campagne TALOS-BANCO
|
|
9
|
+
* a cruscotto sessioni"): TALOS-BANCO è uno strumento di misura esterno,
|
|
10
|
+
* concettualmente estraneo al prodotto — l'intero server non deve più
|
|
11
|
+
* sapere dov'è. Restano SOLO le due primitive di sicurezza generali,
|
|
12
|
+
* usate da `workspace-files.mjs`/`workspace-tree.mjs` per il containment
|
|
13
|
+
* dei percorsi del workspace — quelle non erano MAI state campagna-specifiche,
|
|
14
|
+
* solo ospitate nello stesso file.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync as esisteSync, realpathSync as realpathSyncNativa } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { basename as nomeBase, dirname as cartellaDi, relative as relativoA, resolve as risolvi, sep as separatore } from 'node:path';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* La home della persona, letta una volta per chiamata e SEMPRE sovrascrivibile dal chiamante
|
|
23
|
+
* (`{ home }`): una prova che dipende dalla macchina su cui gira non è una prova.
|
|
24
|
+
* ⛔ Se il sistema non sa dirla, si torna stringa vuota invece di lanciare: `~` e `$HOME`
|
|
25
|
+
* restano allora testo qualunque — nessuna espansione, e nessuna eccezione da un modulo che
|
|
26
|
+
* sta dentro un cancello di sicurezza.
|
|
27
|
+
*/
|
|
28
|
+
function homeDiSistema() {
|
|
29
|
+
try { return homedir() ?? ''; } catch { return ''; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ⭐⭐⭐ 04/9 — W1-13, I FILE DI CONTROLLO DI TALOS (review 03/09). Sono i file che decidono COSA
|
|
34
|
+
* l'agente può fare: hook, MCP, plugin, registro di fiducia, runtime del
|
|
35
|
+
* provider, istruzioni (`CLAUDE.md`/`AGENTS.md`/`.claude/`), skill, memoria.
|
|
36
|
+
* Una scrittura del modello su uno di questi non è una modifica al
|
|
37
|
+
* progetto: è una modifica alle REGOLE — e va approvata a mano ANCHE in
|
|
38
|
+
* Full access, anche con un permesso per-attrezzo «sempre».
|
|
39
|
+
*
|
|
40
|
+
* `file`: per nome, a qualunque profondità (un `CLAUDE.md` annidato è
|
|
41
|
+
* letto comunque). `cartelleOvunque`: per segmento, a qualunque profondità
|
|
42
|
+
* (anche FUORI dal workspace: `~/.claude/` è controllo per chiunque).
|
|
43
|
+
* `cartelleAllaRadice`: solo come primo segmento sotto il workspace — un
|
|
44
|
+
* progetto che si chiama `skills` non deve diventare tutto intoccabile.
|
|
45
|
+
*/
|
|
46
|
+
export const FILE_DI_CONTROLLO = Object.freeze({
|
|
47
|
+
file: Object.freeze(['.harness-ui-hooks.json', '.harness-ui-mcp.json', '.provider-runtime.json', 'CLAUDE.md', 'AGENTS.md']),
|
|
48
|
+
// ⛔ 14/09 (F01/F02 della review, verificato): `.mcp-trust` e `.plugin-trust` sono i registri dove vivono i consensi ai
|
|
49
|
+
// server MCP e ai plugin (session-registry.mjs), esattamente come `.hooks-trust`. Mancavano da questo elenco: senza,
|
|
50
|
+
// un attrezzo di scrittura del modello poteva scriverci dentro e AUTO-CONCEDERSI la fiducia. `ePercorsoDiControllo`
|
|
51
|
+
// risolve già il realpath, quindi la classificazione vale anche per un alias/symlink verso queste cartelle.
|
|
52
|
+
cartelleOvunque: Object.freeze(['.harness-ui-plugins', '.hooks-trust', '.mcp-trust', '.plugin-trust', '.claude', '.memory-store']),
|
|
53
|
+
cartelleAllaRadice: Object.freeze(['skills']),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/** Il percorso REALE anche di un file che non esiste ancora: si risale al primo antenato esistente, lo si risolve (symlink/junction), si riattacca il resto. */
|
|
57
|
+
function percorsoRealeAncheSeManca(assoluto, realpathFn) {
|
|
58
|
+
const resto = [];
|
|
59
|
+
let corrente = assoluto;
|
|
60
|
+
while (!esisteSync(corrente)) {
|
|
61
|
+
const padre = cartellaDi(corrente);
|
|
62
|
+
if (padre === corrente) return assoluto; // radice del volume inesistente: niente da risolvere
|
|
63
|
+
resto.unshift(nomeBase(corrente));
|
|
64
|
+
corrente = padre;
|
|
65
|
+
}
|
|
66
|
+
return risolvi(realpathFn(corrente), ...resto);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* True se `percorso` (relativo al workspace o assoluto) tocca un file di
|
|
71
|
+
* controllo. ⛔ Confronto sul percorso REALE: `../`, symlink e junction
|
|
72
|
+
* che puntano a uno di quei file non lo aggirano. Mai un'eccezione: un
|
|
73
|
+
* percorso che non si riesce a risolvere torna `true` (fallisce chiuso —
|
|
74
|
+
* meglio una card di troppo che una regola riscritta in silenzio).
|
|
75
|
+
*/
|
|
76
|
+
export function ePercorsoDiControllo(cartella, percorso, { realpathFn = realpathSyncNativa } = {}) {
|
|
77
|
+
if (typeof cartella !== 'string' || cartella.length === 0 || typeof percorso !== 'string' || percorso.length === 0) return false;
|
|
78
|
+
let radice;
|
|
79
|
+
let reale;
|
|
80
|
+
try {
|
|
81
|
+
radice = percorsoRealeAncheSeManca(risolvi(cartella), realpathFn);
|
|
82
|
+
reale = percorsoRealeAncheSeManca(risolvi(cartella, percorso), realpathFn);
|
|
83
|
+
} catch {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (FILE_DI_CONTROLLO.file.includes(nomeBase(reale))) return true;
|
|
87
|
+
const relativo = relativoA(radice, reale);
|
|
88
|
+
const dentro = relativo !== '' && !relativo.startsWith('..') && !relativo.includes(`..${separatore}`);
|
|
89
|
+
const segmenti = (dentro ? relativo : reale).split(/[\\/]+/).filter(Boolean);
|
|
90
|
+
const cartelle = segmenti.slice(0, -1);
|
|
91
|
+
if (cartelle.some((s) => FILE_DI_CONTROLLO.cartelleOvunque.includes(s))) return true;
|
|
92
|
+
if (dentro && cartelle.length > 0 && FILE_DI_CONTROLLO.cartelleAllaRadice.includes(cartelle[0])) return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* ⛔⛔⛔⛔ F15, 17/09/2026 — LA CLASSE DEI PERCORSI SEGRETI. Owner, P0-bis corsia D:
|
|
98
|
+
* «entrambi stretto». È l'INNESCO di una domanda, non un confine.
|
|
99
|
+
*
|
|
100
|
+
* ⛔ LA DISTINZIONE CHE TIENE IN PIEDI TUTTO IL FILE, ed è la stessa che
|
|
101
|
+
* `custom-task.mjs` (28/8) dichiara nel verso opposto: una denylist usata come
|
|
102
|
+
* CONFINE («questi percorsi non si toccano») è la strategia che la ricerca 2026
|
|
103
|
+
* dà per fallita — gli escape documentati stanno proprio in quella categoria
|
|
104
|
+
* (Docker, «AI Coding Agent Horror Stories», 18/05/2026; Pillar, «The Week of
|
|
105
|
+
* Sandbox Escapes», 20/07/2026). Qui la lista NON è il confine: è l'innesco di
|
|
106
|
+
* una DOMANDA alla persona. Chi la aggira (offuscando il percorso, leggendolo
|
|
107
|
+
* da uno script, montandolo altrove) ottiene il comportamento di OGGI, non un
|
|
108
|
+
* permesso in più: un innesco incompleto perde una domanda, un confine
|
|
109
|
+
* incompleto perde una chiave. ⇒ Aggiungere una voce qui non può rompere
|
|
110
|
+
* niente; toglierla non può aprire niente che oggi sia chiuso.
|
|
111
|
+
*
|
|
112
|
+
* ⭐ Ricerca PRIMA di scrivere (fonte + data), per la FORMA dell'elenco, non per copiarlo:
|
|
113
|
+
* - Claude Code, «Configure permissions» (code.claude.com/docs/en/permissions, letta il
|
|
114
|
+
* 17/09/2026): le regole sui file si scrivono con la sintassi **gitignore**, con quattro
|
|
115
|
+
* ancoraggi distinti — `//assoluto`, `~/home`, `/relativo-alla-sorgente`, `relativo`; un
|
|
116
|
+
* nome nudo (`Read(.env)`) equivale allo stesso nome preceduto da un doppio asterisco,
|
|
117
|
+
* cioè vale a QUALUNQUE profondità; su
|
|
118
|
+
* Windows i percorsi sono normalizzati in forma POSIX prima del confronto; e una regola
|
|
119
|
+
* `!` (`Read(*.env)` poi `Read(!sample.env)`) ritaglia le eccezioni.
|
|
120
|
+
* ⇒ da qui tre scelte di questo file: (a) il confronto si fa sui SEGMENTI, non sulla
|
|
121
|
+
* stringa; (b) i separatori Windows si normalizzano PRIMA; (c) esistono le ESENZIONI
|
|
122
|
+
* (`.env.example`, `id_rsa.pub`), perché una domanda inutile addestra a cliccare sì.
|
|
123
|
+
* ⛔ Dalla stessa pagina, il vincolo che questo file eredita: le regole sui percorsi
|
|
124
|
+
* valgono per i comandi di lettura che il nome del file lo DICONO (`cat`, `head`, `sed`,
|
|
125
|
+
* e i bersagli di `>`/`<`), non per chi legge un file senza nominarlo (`grep -r` dalla
|
|
126
|
+
* cartella che lo contiene, o uno script che lo apre da solo). Stesso limite qui, ed è
|
|
127
|
+
* dichiarato: questo è un innesco, non una sandbox.
|
|
128
|
+
*
|
|
129
|
+
* ⛔ I LIMITI NOTI, scritti qui perché nessuno li riscopra credendoli difetti (17/09/2026, dal
|
|
130
|
+
* controllo avversariale — dichiarati, non curati, perché curarli vorrebbe dire smettere di
|
|
131
|
+
* essere un innesco lessicale e diventare una sandbox):
|
|
132
|
+
* - **i link simbolici non si vedono.** L'innesco guarda il TESTO del comando, non il disco:
|
|
133
|
+
* un `ln -s ~/.ssh/id_rsa ./chiave` e poi `cat ./chiave` non chiede. `ePercorsoDiControllo`
|
|
134
|
+
* qui sopra risolve il realpath perché giudica UN percorso; qui i pezzi sono molti e
|
|
135
|
+
* spesso non esistono affatto (glob, variabili, argomenti) — toccare il disco per ognuno
|
|
136
|
+
* sarebbe un costo per ogni comando e comunque non chiuderebbe il buco lessicale.
|
|
137
|
+
* - **falsi positivi plausibili e accettati**: `ls /usr/share/keyrings` (una cartella di
|
|
138
|
+
* chiavi APT pubbliche, che chiede per via del segmento `keyrings`) e un file `.key` di
|
|
139
|
+
* Keynote. Entrambi producono UNA domanda in più, mai un blocco: il prezzo dichiarato del
|
|
140
|
+
* lato conservativo su due nomi che nella stragrande maggioranza dei casi sono segreti.
|
|
141
|
+
* - **lo stato di `cd` non è modellato**: dopo un `cd ~/.ssh`, un `cat config` in un comando
|
|
142
|
+
* SUCCESSIVO non chiede — il pezzo `config` da solo non nomina niente di dichiarato, e
|
|
143
|
+
* questo modulo non sa dove la shell si trova. (Nello STESSO comando, `cd ~/.ssh && cat
|
|
144
|
+
* config` chiede, perché `~/.ssh` è lì da leggere.)
|
|
145
|
+
* - Il registro, sulla cosa da proteggere: la ricerca sulle credenziali degli agenti 2026
|
|
146
|
+
* (GitGuardian, «State of Secrets Sprawl 2026» — 24.008 segreti unici nei file di
|
|
147
|
+
* configurazione MCP pubblici, ~150 segreti per portatile di sviluppatore, e ~40% dentro
|
|
148
|
+
* le cartelle degli strumenti AI; Amazon Q, credenziali AWS vive caricate da
|
|
149
|
+
* `.amazonq/mcp.json` senza consenso) dice che il posto dove i segreti stanno DAVVERO
|
|
150
|
+
* sono le cartelle prevedibili della home e i file di ambiente — che è esattamente
|
|
151
|
+
* questa lista, e non un elenco di comandi pericolosi.
|
|
152
|
+
*
|
|
153
|
+
* `cartelle`: un segmento con questo nome, a QUALUNQUE profondità, rende segreto tutto ciò
|
|
154
|
+
* che sta dentro (e la cartella stessa: `ls ~/.ssh` chiede). `fileInCartella`: solo quel nome
|
|
155
|
+
* dentro quella cartella — `~/.docker/` contiene anche `daemon.json`, che segreto non è.
|
|
156
|
+
* `nomiFile`/`prefissiNome`/`estensioni`: il nome finale, a qualunque profondità.
|
|
157
|
+
* `esenzioni`: ciò che per CONVENZIONE è pubblico e non deve mai generare una domanda.
|
|
158
|
+
*/
|
|
159
|
+
export const PERCORSI_SEGRETI = Object.freeze({
|
|
160
|
+
cartelle: Object.freeze(['.ssh', '.aws', '.gnupg', '.password-store', 'keyrings']),
|
|
161
|
+
fileInCartella: Object.freeze({
|
|
162
|
+
'.docker': Object.freeze(['config.json']),
|
|
163
|
+
'.kube': Object.freeze(['config']),
|
|
164
|
+
}),
|
|
165
|
+
// ⛔ `.provider-runtime.json` è il file dove TALOS custodisce le chiavi dei fornitori
|
|
166
|
+
// (`provider-credential-store.mjs`, `runtimeFile`): sta già in `FILE_DI_CONTROLLO.file`
|
|
167
|
+
// qui sopra, ma quella lista guarda le SCRITTURE del modello — non `shell` né `leggi`.
|
|
168
|
+
// Le due liste dicono due cose diverse sullo stesso file, e vanno tenute entrambe.
|
|
169
|
+
nomiFile: Object.freeze(['.netrc', '_netrc', '.npmrc', '.pypirc', '.pgpass', '.git-credentials', '.provider-runtime.json']),
|
|
170
|
+
prefissiNome: Object.freeze(['id_rsa', 'id_ed25519', 'id_ecdsa', 'id_dsa']),
|
|
171
|
+
estensioni: Object.freeze(['.pem', '.key', '.p12', '.pfx', '.jks', '.keystore', '.ppk']),
|
|
172
|
+
esenzioni: Object.freeze(['.pub', '.example', '.sample', '.template', '.dist']),
|
|
173
|
+
/*
|
|
174
|
+
* ⛔ Le esenzioni che valgono ANCHE dentro una cartella dichiarata segreta, E anche contro il
|
|
175
|
+
* CONFINE (l'unico posto in tutto il file dove un'esenzione scavalca il secondo innesco). È
|
|
176
|
+
* una sola, e per una ragione precisa: `~/.ssh/id_rsa.pub` è una chiave PUBBLICA — si incolla
|
|
177
|
+
* in un pannello di deploy, si stampa a schermo, ed è una delle cose che si chiedono più
|
|
178
|
+
* spesso dentro `~/.ssh`. Chiedere lì insegna a rispondere sì senza leggere, e la volta che
|
|
179
|
+
* conta (`id_rsa`, senza `.pub`) la persona clicca sì per abitudine.
|
|
180
|
+
* ⛔ Solo per il FILE, mai per la cartella: `ls ~/.ssh` resta un segreto — elenca anche ciò
|
|
181
|
+
* che pubblico non è.
|
|
182
|
+
* ⛔ Le altre quattro NON valgono dentro una cartella segreta né contro il confine:
|
|
183
|
+
* `credenziali.sample` dentro `~/.aws` è un file che qualcuno ha chiamato così, non una
|
|
184
|
+
* convenzione pubblica.
|
|
185
|
+
* ⛔ 17/09: questa riga è nata INERTE sul proprio esempio — l'esenzione c'era per la classe e
|
|
186
|
+
* il confine chiedeva lo stesso. Un'esenzione che non esenta il caso che il suo commento
|
|
187
|
+
* cita non è un'esenzione: è una frase. Curata, e provata su entrambi gli inneschi.
|
|
188
|
+
*/
|
|
189
|
+
esenzioniOvunque: Object.freeze(['.pub']),
|
|
190
|
+
// Il portachiavi del sistema, nei tre sistemi operativi: si nomina per percorso…
|
|
191
|
+
coppiePortachiavi: Object.freeze([['library', 'keychains'], ['microsoft', 'credentials'], ['microsoft', 'vault']]),
|
|
192
|
+
// …oppure per il comando che lo apre. ⛔ Deliberatamente CORTO e non ambiguo: `security` da
|
|
193
|
+
// solo è una parola comune, quindi entra solo con il suo sottocomando vero.
|
|
194
|
+
comandiPortachiavi: Object.freeze(['cmdkey', 'vaultcmd', 'secret-tool']),
|
|
195
|
+
frasiPortachiavi: Object.freeze([/\bsecurity\s+(?:find-generic-password|find-internet-password|dump-keychain)\b/i]),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/** Le tre scritture della home, più `~`: un innesco che non le conosce non innesca quasi mai. */
|
|
199
|
+
function conLaHomeEspansa(grezzo, home) {
|
|
200
|
+
let t = String(grezzo).trim().replace(/^["'`]+/, '').replace(/["'`]+$/, '');
|
|
201
|
+
if (t === '') return '';
|
|
202
|
+
if (typeof home === 'string' && home !== '') {
|
|
203
|
+
t = t.replace(/^~(?=$|[\\/])/, home);
|
|
204
|
+
t = t.replace(/%USERPROFILE%|%HOME%|\$\{HOME\}|\$env:USERPROFILE|\$HOME(?![A-Za-z0-9_])/gi, home);
|
|
205
|
+
}
|
|
206
|
+
return t.replace(/\\/g, '/');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** I segmenti veri di un percorso: via i vuoti, via `.` e `..` (che non sono nomi). */
|
|
210
|
+
function segmentiDi(normalizzato) {
|
|
211
|
+
return normalizzato.split('/').filter((s) => s !== '' && s !== '.' && s !== '..');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function eEsente(base) {
|
|
215
|
+
return PERCORSI_SEGRETI.esenzioni.some((e) => base.endsWith(e));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Il NOME finale, da solo, dice che è un segreto? (`.env`, `.env.local`, `prod.env`, `id_rsa`, `*.pem`…) */
|
|
219
|
+
function eNomeSegreto(base) {
|
|
220
|
+
const b = base.toLowerCase();
|
|
221
|
+
if (b === '') return false;
|
|
222
|
+
if (eEsente(b)) return false;
|
|
223
|
+
if (b === '.env' || b.startsWith('.env.') || b.endsWith('.env')) return true;
|
|
224
|
+
if (PERCORSI_SEGRETI.nomiFile.includes(b)) return true;
|
|
225
|
+
if (PERCORSI_SEGRETI.prefissiNome.some((p) => b.startsWith(p))) return true;
|
|
226
|
+
if (PERCORSI_SEGRETI.estensioni.some((e) => b.endsWith(e))) return true;
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Un solo pezzo di testo (un percorso, o un token di un comando): appartiene alla classe dichiarata? */
|
|
231
|
+
function classeDelPezzo(pezzo, home) {
|
|
232
|
+
const normalizzato = conLaHomeEspansa(pezzo, home);
|
|
233
|
+
// Un indirizzo web non è un percorso: `https://x/.well-known/...` non deve chiedere niente.
|
|
234
|
+
if (normalizzato === '' || normalizzato.includes('://')) return null;
|
|
235
|
+
const segmenti = segmentiDi(normalizzato);
|
|
236
|
+
if (segmenti.length === 0) return null;
|
|
237
|
+
const bassi = segmenti.map((s) => s.toLowerCase());
|
|
238
|
+
const pubblicoOvunque = PERCORSI_SEGRETI.esenzioniOvunque.some((e) => bassi[bassi.length - 1].endsWith(e));
|
|
239
|
+
if (!pubblicoOvunque && bassi.some((s) => PERCORSI_SEGRETI.cartelle.includes(s))) return 'segreto';
|
|
240
|
+
for (const [primo, secondo] of PERCORSI_SEGRETI.coppiePortachiavi) {
|
|
241
|
+
for (let i = 0; i + 1 < bassi.length; i += 1) if (bassi[i] === primo && bassi[i + 1] === secondo) return 'portachiavi';
|
|
242
|
+
}
|
|
243
|
+
const base = segmenti[segmenti.length - 1];
|
|
244
|
+
const cartelle = bassi.slice(0, -1);
|
|
245
|
+
for (const [cartella, nomi] of Object.entries(PERCORSI_SEGRETI.fileInCartella)) {
|
|
246
|
+
if (cartelle.includes(cartella) && nomi.includes(base.toLowerCase())) return 'segreto';
|
|
247
|
+
}
|
|
248
|
+
if (eNomeSegreto(base)) return 'segreto';
|
|
249
|
+
if (PERCORSI_SEGRETI.comandiPortachiavi.includes(base.toLowerCase())) return 'portachiavi';
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* I pezzi di un comando di shell che possono essere un percorso. ⛔ Non è un parser di shell e
|
|
255
|
+
* non pretende di esserlo: si spezza su spazi e metacaratteri, perché quello che serve è
|
|
256
|
+
* ACCORGERSI di un nome, non capire il comando. Un percorso fra virgolette CON spazi dentro si
|
|
257
|
+
* spezza — dichiarato: è un innesco, e ciò che gli sfugge resta il comportamento di oggi.
|
|
258
|
+
*/
|
|
259
|
+
export function pezziDelComando(comando) {
|
|
260
|
+
return String(comando ?? '').split(/[\s;|&<>()`"'=,]+/u).filter(Boolean);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* ⛔⛔⛔ CASO 1 — il testo NOMINA un percorso della classe dichiarata?
|
|
265
|
+
* Vale sia per un comando intero (`cat ~/.ssh/id_rsa`) sia per un percorso solo
|
|
266
|
+
* (`~/.aws/credentials`): un percorso è un comando di un pezzo solo.
|
|
267
|
+
*
|
|
268
|
+
* @returns {null | {classe: 'segreto'|'portachiavi', percorso: string}} `percorso` è il pezzo
|
|
269
|
+
* COME LA PERSONA LO VEDRÀ, non risolto: chi risponde deve riconoscere ciò che ha davanti.
|
|
270
|
+
*/
|
|
271
|
+
export function nominaUnSegreto(testo, { home = homeDiSistema() } = {}) {
|
|
272
|
+
const intero = String(testo ?? '');
|
|
273
|
+
if (intero.trim() === '') return null;
|
|
274
|
+
for (const frase of PERCORSI_SEGRETI.frasiPortachiavi) {
|
|
275
|
+
if (frase.test(intero)) return { classe: 'portachiavi', percorso: intero.trim() };
|
|
276
|
+
}
|
|
277
|
+
for (const pezzo of pezziDelComando(intero)) {
|
|
278
|
+
const classe = classeDelPezzo(pezzo, home);
|
|
279
|
+
if (classe) return { classe, percorso: pezzo.replace(/^["'`]+/, '').replace(/["'`]+$/, '') };
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* ⛔⛔⛔ CASO 2 — IL CONFINE STRETTO: il percorso esce dal workspace E finisce dentro qualcosa
|
|
286
|
+
* di NASCOSTO (un segmento che comincia con `.`).
|
|
287
|
+
*
|
|
288
|
+
* ⛔ Sono DUE condizioni, e servono entrambe. Non «tutto ciò che esce dal workspace» (un
|
|
289
|
+
* `cat ../fratello/note.txt` è lavoro normale) e non «tutto ciò che sta sotto la home»
|
|
290
|
+
* (`echo $HOME` non tocca niente). Ogni domanda in più addestra a rispondere sì senza
|
|
291
|
+
* leggere — e una domanda a cui si risponde sì senza leggere non protegge da niente.
|
|
292
|
+
*/
|
|
293
|
+
export function esceDalWorkspaceVersoUnNascosto(percorso, { cartella, home = homeDiSistema() } = {}) {
|
|
294
|
+
const normalizzato = conLaHomeEspansa(percorso, home);
|
|
295
|
+
if (normalizzato === '' || normalizzato.includes('://')) return null;
|
|
296
|
+
// Senza una radice non si può dire «fuori»: nessun innesco, mai un falso allarme.
|
|
297
|
+
if (typeof cartella !== 'string' || cartella === '') return null;
|
|
298
|
+
// Un pezzo che non porta un separatore non è un percorso: `$HOME` non espansa, `--flag`, `npm`.
|
|
299
|
+
if (!/[\\/]/u.test(String(percorso ?? '')) && !String(percorso ?? '').trim().startsWith('~')) return null;
|
|
300
|
+
let radice;
|
|
301
|
+
let assoluto;
|
|
302
|
+
try {
|
|
303
|
+
radice = risolvi(cartella);
|
|
304
|
+
assoluto = risolvi(radice, normalizzato);
|
|
305
|
+
} catch { return null; }
|
|
306
|
+
if (isPathInside(radice, assoluto)) return null;
|
|
307
|
+
const segmenti = segmentiDi(assoluto.replace(/\\/g, '/'));
|
|
308
|
+
/*
|
|
309
|
+
* ⛔⛔⛔ B3 (17/09/2026) — l'esenzione `.pub` vince ANCHE qui, non solo sulla classe.
|
|
310
|
+
* Prima no, e il risultato era una incoerenza: il commento di `esenzioniOvunque` portava come
|
|
311
|
+
* esempio `~/.ssh/id_rsa.pub`, e proprio lì l'esenzione era inerte perché il confine chiedeva
|
|
312
|
+
* lo stesso. Una chiave pubblica è pubblica ovunque stia; chiedere per lei insegna a cliccare
|
|
313
|
+
* sì, e la volta che conta (`id_rsa`, senza `.pub`) la persona clicca sì per abitudine.
|
|
314
|
+
* ⛔ Solo per il FILE, mai per la cartella: `ls ~/.ssh` elenca anche ciò che pubblico non è,
|
|
315
|
+
* e infatti resta un segreto per via della classe, che corre prima di questo controllo.
|
|
316
|
+
*/
|
|
317
|
+
const ultimo = segmenti[segmenti.length - 1] ?? '';
|
|
318
|
+
if (PERCORSI_SEGRETI.esenzioniOvunque.some((e) => ultimo.toLowerCase().endsWith(e))) return null;
|
|
319
|
+
const nascosto = segmenti.find((s) => s.startsWith('.'));
|
|
320
|
+
if (!nascosto) return null;
|
|
321
|
+
return { classe: 'fuori-workspace-nascosto', percorso: String(percorso).trim(), nascosto };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** La copia per la persona: lingua naturale, nessun nome tecnico, e DICE quale file. */
|
|
325
|
+
function frasePerLaPersona(segnalazione, tipo) {
|
|
326
|
+
const azione = tipo === 'leggi'
|
|
327
|
+
? { soggetto: 'Questa lettura apre', coda: 'vuoi che la faccia?' }
|
|
328
|
+
: { soggetto: 'Il comando tocca', coda: 'vuoi che lo esegua?' };
|
|
329
|
+
if (segnalazione.classe === 'portachiavi') {
|
|
330
|
+
const apre = tipo === 'leggi' ? 'Questa lettura apre' : 'Il comando apre';
|
|
331
|
+
return `${apre} il portachiavi del sistema, dove sono custodite le password: ${azione.coda}`;
|
|
332
|
+
}
|
|
333
|
+
const cosa = segnalazione.classe === 'segreto'
|
|
334
|
+
? 'un file che può contenere chiavi o password'
|
|
335
|
+
: 'una cartella nascosta fuori dalla cartella di lavoro';
|
|
336
|
+
return `${azione.soggetto} ${cosa} (${segnalazione.percorso}): ${azione.coda}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* ⛔⛔⛔⛔ F15 — LA DOMANDA UNICA, quella che il kernel chiama: questo comando (o questa
|
|
341
|
+
* lettura) va sottoposto alla persona ANCHE se l'attrezzo è impostato a «sempre»?
|
|
342
|
+
*
|
|
343
|
+
* ⛔ È una funzione PURA e l'unica porta: il kernel non deve rifare nessuna di queste
|
|
344
|
+
* decisioni, e chiunque voglia provarle non ha bisogno di avviare una sessione.
|
|
345
|
+
* ⛔ Torna `null` quando non c'è niente da chiedere — e `null` significa «il comportamento di
|
|
346
|
+
* oggi, bit per bit»: questo modulo non nega mai niente e non consente mai niente, decide
|
|
347
|
+
* soltanto se vale la pena disturbare la persona.
|
|
348
|
+
*
|
|
349
|
+
* @returns {null | {classe: string, percorso: string, frase: string}}
|
|
350
|
+
*/
|
|
351
|
+
export function motivoDaChiedere({ tipo, comando, percorso, cartella, home = homeDiSistema() } = {}) {
|
|
352
|
+
const testo = tipo === 'leggi' ? percorso : comando;
|
|
353
|
+
if (typeof testo !== 'string' || testo.trim() === '') return null;
|
|
354
|
+
const nominato = nominaUnSegreto(testo, { home });
|
|
355
|
+
if (nominato) return { ...nominato, frase: frasePerLaPersona(nominato, tipo) };
|
|
356
|
+
for (const pezzo of pezziDelComando(testo)) {
|
|
357
|
+
const fuori = esceDalWorkspaceVersoUnNascosto(pezzo, { cartella, home });
|
|
358
|
+
if (fuori) return { ...fuori, frase: frasePerLaPersona(fuori, tipo) };
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export class PathPolicyError extends Error {
|
|
364
|
+
constructor(message, code = 'PATH_NOT_ALLOWED') {
|
|
365
|
+
super(message);
|
|
366
|
+
this.name = 'PathPolicyError';
|
|
367
|
+
this.code = code;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function isPathInside(rootRealPath, candidateRealPath) {
|
|
372
|
+
const difference = relative(resolve(rootRealPath), resolve(candidateRealPath));
|
|
373
|
+
return difference === '' || (
|
|
374
|
+
difference !== '..'
|
|
375
|
+
&& !difference.startsWith(`..${sep}`)
|
|
376
|
+
&& !isAbsolute(difference)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function validateRoot(root) {
|
|
381
|
+
if (typeof root !== 'string' || root.trim() === '' || root.includes('\0') || !isAbsolute(root)) {
|
|
382
|
+
throw new PathPolicyError('Radice non valida', 'PATH_NOT_ALLOWED');
|
|
383
|
+
}
|
|
384
|
+
return resolve(root);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function validateCandidate(candidate) {
|
|
388
|
+
if (typeof candidate !== 'string' || candidate.trim() === '' || candidate.includes('\0') || isAbsolute(candidate)) {
|
|
389
|
+
throw new PathPolicyError('Percorso non valido', 'PATH_NOT_ALLOWED');
|
|
390
|
+
}
|
|
391
|
+
// Windows device paths, UNC e alternate data stream non sono percorsi di
|
|
392
|
+
// workspace: non devono poter cambiare semantica fra API diverse.
|
|
393
|
+
if (/^(?:\\\\\?\\|\\\\\.\\|\\\\)/u.test(candidate) || /(?:^|[\\/])[^\\/]+:[^\\/]*$/u.test(candidate)) {
|
|
394
|
+
throw new PathPolicyError('Percorso speciale non consentito', 'PATH_NOT_ALLOWED');
|
|
395
|
+
}
|
|
396
|
+
return candidate;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Risolve un percorso attraverso il filesystem e verifica il containment sul
|
|
401
|
+
* percorso reale, quindi anche junction/symlink che puntano fuori. Per i
|
|
402
|
+
* nuovi file si può usare `allowMissing`: viene risolto il genitore reale e
|
|
403
|
+
* il nome finale resta dentro la radice.
|
|
404
|
+
*/
|
|
405
|
+
export async function resolveContainedRealPath(root, candidate, options = {}) {
|
|
406
|
+
const rootAbsolute = validateRoot(root);
|
|
407
|
+
const relativeCandidate = validateCandidate(candidate);
|
|
408
|
+
const realpathFn = options.realpathFn ?? realpath;
|
|
409
|
+
let rootReal;
|
|
410
|
+
try { rootReal = await realpathFn(rootAbsolute); } catch { throw new PathPolicyError('Radice non leggibile', 'PATH_ROOT_UNREADABLE'); }
|
|
411
|
+
const lexical = resolve(rootReal, relativeCandidate);
|
|
412
|
+
if (!isPathInside(rootReal, lexical)) throw new PathPolicyError('Percorso fuori dall’area autorizzata', 'PATH_NOT_ALLOWED');
|
|
413
|
+
let realCandidate;
|
|
414
|
+
try {
|
|
415
|
+
realCandidate = await realpathFn(lexical);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (!options.allowMissing) throw new PathPolicyError('Percorso non trovato', 'PATH_NOT_FOUND');
|
|
418
|
+
const parent = dirname(lexical);
|
|
419
|
+
let parentReal;
|
|
420
|
+
try { parentReal = await realpathFn(parent); } catch { throw new PathPolicyError('Cartella genitore non trovata', 'PATH_NOT_FOUND'); }
|
|
421
|
+
realCandidate = join(parentReal, lexical.slice(parent.length + 1));
|
|
422
|
+
}
|
|
423
|
+
if (!isPathInside(rootReal, realCandidate)) throw new PathPolicyError('Percorso fuori dall’area autorizzata', 'PATH_NOT_ALLOWED');
|
|
424
|
+
return realCandidate;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Apre un handle soltanto dopo il controllo realpath; il chiamante è tenuto a chiuderlo. */
|
|
428
|
+
export async function openContainedFile(root, candidate, flags = 'r', options = {}) {
|
|
429
|
+
const realCandidate = await resolveContainedRealPath(root, candidate, options);
|
|
430
|
+
const openFn = options.openFn ?? open;
|
|
431
|
+
let handle;
|
|
432
|
+
try { handle = await openFn(realCandidate, flags); } catch { throw new PathPolicyError('File non leggibile', 'PATH_OPEN_FAILED'); }
|
|
433
|
+
try {
|
|
434
|
+
const info = await handle.stat();
|
|
435
|
+
if (!info.isFile()) throw new PathPolicyError('Il percorso non è un file', 'PATH_NOT_FILE');
|
|
436
|
+
return handle;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
await handle.close().catch(() => {});
|
|
439
|
+
if (error instanceof PathPolicyError) throw error;
|
|
440
|
+
throw new PathPolicyError('File non leggibile', 'PATH_OPEN_FAILED');
|
|
441
|
+
}
|
|
442
|
+
}
|