@orkastery/cli 0.5.6 → 0.6.1
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/adapters/README.md +18 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +1 -0
- package/adapters/claude-code/README.md +37 -5
- package/adapters/claude-code/agents/ork-check.md +1 -0
- package/adapters/claude-code/agents/ork-go.md +1 -0
- package/adapters/claude-code/agents/ork-goal.md +1 -0
- package/adapters/claude-code/agents/ork-master.md +1 -0
- package/adapters/claude-code/agents/ork-plan.md +1 -0
- package/adapters/claude-code/agents/ork-ship.md +1 -0
- package/adapters/claude-code/hooks/hooks.json +2 -1
- package/adapters/claude-code/hooks/ork-guard.js +15 -1
- package/adapters/claude-code/hooks/ork-resposta.js +91 -0
- package/adapters/claude-code/hooks/ork-sensor.js +18 -2
- package/adapters/claude-code/hooks/quota-signal.js +14 -0
- package/adapters/claude-code/mods/ork-mensageiro/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/caixa.ts +215 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/painel.tsx +165 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/recepcao.ts +256 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/register.tsx +481 -0
- package/adapters/claude-code/mods/ork-mensageiro/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mensageiro/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +9 -5
- package/adapters/claude-code/mods/ork-ritual/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/nota.ts +88 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/register.tsx +494 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/replay.tsx +271 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/ritual.ts +218 -0
- package/adapters/claude-code/mods/ork-ritual/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ritual/types/index.d.ts +106 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +1 -1
- package/adapters/codex/skills/ork/SKILL.md +2 -0
- package/adapters/hermes/README.md +64 -1
- package/adapters/hermes/bin/ork-conhecimento-colecoes.sh +14 -0
- package/adapters/hermes/bin/ork-conhecimento-listar.sh +15 -0
- package/adapters/hermes/bin/ork-conhecimento-mostrar.sh +16 -0
- package/adapters/hermes/bin/ork-conhecimento-verificar.sh +15 -0
- package/adapters/hermes/bin/ork-vigia-batida.py +127 -0
- package/adapters/hermes/hermes.plugin.json +4 -0
- package/adapters/hermes/hitl-ingress/__init__.py +101 -8
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +2 -2
- package/adapters/openclaw/README.md +81 -9
- package/adapters/openclaw/bin/ork-vigia-batida.js +86 -0
- package/adapters/openclaw/dist/hitl-ingress.js +106 -9
- package/adapters/openclaw/dist/index.js +86 -0
- package/adapters/openclaw/openclaw.plugin.json +34 -4
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +103 -9
- package/adapters/openclaw/src/index.ts +94 -0
- package/assets/adapters-en/claude-code/README.md +403 -0
- package/assets/adapters-en/claude-code/agents/ork-check.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-go.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-goal.md +34 -0
- package/assets/adapters-en/claude-code/agents/ork-master.md +35 -0
- package/assets/adapters-en/claude-code/agents/ork-plan.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-ship.md +33 -0
- package/assets/adapters-en/claude-code/commands/check.md +46 -0
- package/assets/adapters-en/claude-code/commands/go.md +46 -0
- package/assets/adapters-en/claude-code/commands/goal.md +55 -0
- package/assets/adapters-en/claude-code/commands/master.md +54 -0
- package/assets/adapters-en/claude-code/commands/onboarding.md +22 -0
- package/assets/adapters-en/claude-code/commands/ork.md +126 -0
- package/assets/adapters-en/claude-code/commands/plan.md +47 -0
- package/assets/adapters-en/claude-code/commands/ship.md +45 -0
- package/assets/adapters-en/codex/skills/ork/SKILL.md +85 -0
- package/assets/adapters-en/hermes/README.md +273 -0
- package/assets/adapters-en/hermes/skills/orkastery-devmaster/SKILL.md +169 -0
- package/assets/references-en/README.md +25 -0
- package/assets/references-en/code-review-axes.md +82 -0
- package/assets/references-en/definition-of-done.md +74 -0
- package/assets/references-en/performance-checklist.md +53 -0
- package/assets/references-en/security-checklist.md +101 -0
- package/assets/references-en/testing-patterns.md +51 -0
- package/assets/security-scan/defaults/.golangci.yml +2 -0
- package/assets/security-scan/defaults/eslint.config.mjs +11 -0
- package/assets/security-scan/defaults/gitleaks.toml +3 -0
- package/assets/security-scan/defaults/ruff.toml +1 -0
- package/assets/security-scan/tools-agil.json +87 -0
- package/assets/security-scan/tools.json +625 -0
- package/assets/skills-en/core/onboarding/SKILL.md +58 -0
- package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +119 -0
- package/assets/skills-en/core/thread-state/SKILL.md +88 -0
- package/assets/skills-en/governance/decision-triage/SKILL.md +65 -0
- package/assets/skills-en/governance/narrative-guardian/SKILL.md +66 -0
- package/assets/skills-en/governance/roadmap-keeper/SKILL.md +60 -0
- package/assets/skills-en/governance/scope-check-capability-map/SKILL.md +71 -0
- package/assets/skills-en/observability/thread-tracing/SKILL.md +63 -0
- package/assets/skills-en/phases/check-quality/SKILL.md +103 -0
- package/assets/skills-en/phases/go-implementation/SKILL.md +78 -0
- package/assets/skills-en/phases/goal-definition/SKILL.md +69 -0
- package/assets/skills-en/phases/master-metrics/SKILL.md +72 -0
- package/assets/skills-en/phases/plan-specification/SKILL.md +67 -0
- package/assets/skills-en/phases/security-remediation/SKILL.md +70 -0
- package/assets/skills-en/phases/ship-release/SKILL.md +71 -0
- package/assets/skills-en/reviewers/code-reviewer/SKILL.md +61 -0
- package/assets/skills-en/reviewers/security-auditor/SKILL.md +93 -0
- package/assets/skills-en/reviewers/test-engineer/SKILL.md +66 -0
- package/assets/skills-en/reviewers/web-performance-auditor/SKILL.md +63 -0
- package/dist/adapters/claude-bg.js +123 -9
- package/dist/adapters/codex-controller-worker.js +12 -5
- package/dist/adapters/codex-events.js +16 -4
- package/dist/adapters/codex.js +3 -1
- package/dist/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +612 -0
- package/dist/adapters/grok-mcp.js +178 -0
- package/dist/adapters/grok.js +686 -0
- package/dist/agendador-cpu.js +92 -0
- package/dist/atualizar-branch.js +245 -0
- package/dist/auditoria.js +13 -11
- package/dist/auditrun.js +51 -46
- package/dist/autorizacao-duravel.js +327 -0
- package/dist/board.js +94 -40
- package/dist/branch-de-estado.js +23 -10
- package/dist/busca-semantica.js +7 -5
- package/dist/catalogo.js +2 -1
- package/dist/changelog-reconciliacao.js +290 -0
- package/dist/check-agil.js +528 -0
- package/dist/check-modo.js +41 -0
- package/dist/check-paralelo.js +238 -0
- package/dist/ci.js +248 -45
- package/dist/ciclos.js +8 -6
- package/dist/claim-lint.js +12 -8
- package/dist/claims-conferir.js +47 -39
- package/dist/claims.js +10 -9
- package/dist/company-brain-client.js +10 -3
- package/dist/company-brain-migration.js +11 -5
- package/dist/conducao-batida.js +759 -0
- package/dist/conducao-sentinela.js +25 -8
- package/dist/conducao.js +89 -29
- package/dist/conhecimento-consulta.js +270 -0
- package/dist/conhecimento.js +111 -87
- package/dist/conta-esgotada.js +127 -0
- package/dist/dado-privado.js +816 -0
- package/dist/decisao-autonoma.js +5 -4
- package/dist/demo.js +14 -12
- package/dist/dependencias-da-worktree.js +25 -0
- package/dist/divida.js +38 -36
- package/dist/docs.js +89 -89
- package/dist/doctor-forja.js +343 -0
- package/dist/doctor-pulse-cron.js +113 -1
- package/dist/doctor.js +164 -6
- package/dist/dsn-da-memoria.js +417 -0
- package/dist/entrega-por-mod.js +503 -0
- package/dist/entrega-pr.js +154 -54
- package/dist/esgotamentos.js +13 -11
- package/dist/estado-thread.js +21 -18
- package/dist/evalrunner.js +35 -32
- package/dist/fabrica-batida.js +34 -21
- package/dist/fabrica-estado.js +33 -21
- package/dist/fix.js +21 -19
- package/dist/forja/adaptador.js +6 -0
- package/dist/forja/ambiente-de-ci.js +27 -0
- package/dist/forja/api.js +256 -0
- package/dist/forja/cache.js +89 -0
- package/dist/forja/cli.js +60 -0
- package/dist/forja/contrato.js +9 -0
- package/dist/forja/endereco.js +43 -0
- package/dist/forja/estado.js +22 -0
- package/dist/forja/executor.js +73 -0
- package/dist/forja/github/ci.js +95 -0
- package/dist/forja/github/dono-da-rede.js +18 -0
- package/dist/forja/github/metadados.js +15 -0
- package/dist/forja/github/pedidos.js +200 -0
- package/dist/forja/gitlab/ci.js +141 -0
- package/dist/forja/gitlab/consultas.js +59 -0
- package/dist/forja/gitlab/dono-da-rede.js +43 -0
- package/dist/forja/gitlab/metadados.js +15 -0
- package/dist/forja/gitlab/pedidos-abertos.js +158 -0
- package/dist/forja/gitlab/pedidos.js +141 -0
- package/dist/forja/produtor.js +32 -0
- package/dist/forja/registro.js +31 -0
- package/dist/forja/resolver.js +121 -0
- package/dist/forja/texto.js +13 -0
- package/dist/forja/versao.js +71 -0
- package/dist/forja.js +123 -80
- package/dist/handoff.js +23 -13
- package/dist/hitl-contract.js +46 -43
- package/dist/hitl-curto.js +37 -25
- package/dist/hitl-gates.js +13 -11
- package/dist/hitl-local-receipt.js +51 -0
- package/dist/hitl-local.js +11 -2
- package/dist/hitl-lock.js +5 -11
- package/dist/hitl-public-receipt.js +11 -0
- package/dist/hitl-resumo.js +44 -1
- package/dist/hitl-sessions.js +62 -9
- package/dist/hitl-texto-livre.js +7 -4
- package/dist/hitl.js +34 -31
- package/dist/horario.js +20 -1
- package/dist/hosts.js +104 -134
- package/dist/index.js +864 -554
- package/dist/indice-vetorial.js +14 -12
- package/dist/init.js +11 -2
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +123 -69
- package/dist/intelligence-graph-query.js +37 -22
- package/dist/leases.js +19 -18
- package/dist/ledger-stats.js +17 -16
- package/dist/ledger.js +31 -1
- package/dist/licoes.js +12 -11
- package/dist/locale-accounts.js +228 -0
- package/dist/locale-adapter.js +291 -0
- package/dist/locale-ajuda.js +86 -10
- package/dist/locale-audit.js +199 -0
- package/dist/locale-claims.js +213 -0
- package/dist/locale-conducao.js +138 -0
- package/dist/locale-conhecimento.js +181 -0
- package/dist/locale-decisao.js +205 -0
- package/dist/locale-docs.js +230 -0
- package/dist/locale-estado.js +101 -0
- package/dist/locale-forja.js +288 -0
- package/dist/locale-gerados.js +5 -0
- package/dist/locale-grafo.js +129 -0
- package/dist/locale-master.js +83 -0
- package/dist/locale-network.js +524 -0
- package/dist/locale-operacao-restante.js +931 -0
- package/dist/locale-projetos.js +100 -0
- package/dist/locale-prompts.js +194 -0
- package/dist/locale-pulse.js +140 -2
- package/dist/locale-reservas.js +87 -0
- package/dist/locale-retry.js +183 -6
- package/dist/locale-security-scan.js +69 -0
- package/dist/locale-setup.js +192 -0
- package/dist/locale-ship.js +177 -66
- package/dist/locale-thread.js +149 -5
- package/dist/locale-verify.js +80 -4
- package/dist/locale-worktree.js +246 -0
- package/dist/locale.js +553 -9
- package/dist/maestro-runtime.js +22 -3
- package/dist/manifest.js +293 -12
- package/dist/maquina-ociosa.js +211 -0
- package/dist/maquina.js +10 -5
- package/dist/master-nota-local.js +200 -0
- package/dist/master-nota.js +10 -2
- package/dist/master.js +118 -41
- package/dist/mcp-conhecimento.js +52 -0
- package/dist/mcp-experiencia.js +26 -5
- package/dist/mcp-install.js +90 -15
- package/dist/mcp-noctum.js +101 -0
- package/dist/mcp-server.js +116 -22
- package/dist/mcp-ship-transporte.js +977 -0
- package/dist/mcp-ship.js +68 -24
- package/dist/mcp-transportes.js +17 -0
- package/dist/memoria.js +63 -58
- package/dist/merge-da-entrega.js +85 -0
- package/dist/modos-migracao.js +8 -6
- package/dist/modos.js +17 -18
- package/dist/network-roadmap.js +94 -54
- package/dist/noctum-aceite.js +929 -0
- package/dist/noctum-arco.js +540 -33
- package/dist/noctum-capacidade.js +3 -3
- package/dist/noctum-changelog.js +399 -0
- package/dist/noctum-cli.js +539 -0
- package/dist/noctum-coordenacao.js +1225 -0
- package/dist/noctum-entrega.js +1217 -0
- package/dist/noctum-envelope.js +253 -17
- package/dist/noctum-execucao.js +100 -24
- package/dist/noctum-fechamento.js +416 -0
- package/dist/noctum-fila.js +49 -10
- package/dist/noctum-guarda.js +333 -0
- package/dist/noctum-integracao.js +507 -0
- package/dist/noctum-maquinas.js +387 -0
- package/dist/noctum-medicao.js +450 -0
- package/dist/noctum-modo.js +390 -0
- package/dist/noctum-pergunta-canal.js +96 -0
- package/dist/noctum-pergunta.js +115 -0
- package/dist/noctum-provas.js +838 -0
- package/dist/noctum-reconciliacao.js +594 -0
- package/dist/noctum-rede.js +102 -0
- package/dist/noctum-registro.js +215 -0
- package/dist/noctum-relatorio.js +861 -0
- package/dist/noctum-resposta.js +207 -0
- package/dist/noctum-retomada-rede.js +71 -0
- package/dist/noctum-sessoes-em-voo.js +93 -0
- package/dist/noctum-supervisao.js +649 -0
- package/dist/noctum-terminal.js +459 -0
- package/dist/noctum-transporte.js +655 -0
- package/dist/noctum-vigia.js +679 -0
- package/dist/ocupacao.js +3 -1
- package/dist/onboarding.js +16 -5
- package/dist/orkmind.js +39 -23
- package/dist/orquestracao.js +31 -35
- package/dist/parado-no-condutor.js +160 -139
- package/dist/phase.js +96 -42
- package/dist/policies.js +33 -0
- package/dist/postura-local.js +11 -6
- package/dist/preflight.js +2 -1
- package/dist/procedencia.js +9 -14
- package/dist/projeto-alvo.js +59 -20
- package/dist/prompts.js +136 -42
- package/dist/prova-minima.js +5 -6
- package/dist/pulse-cadencia.js +5 -4
- package/dist/pulse-consentimento.js +53 -1
- package/dist/pulse-delivery.js +143 -27
- package/dist/pulse-resposta.js +368 -4
- package/dist/pulse.js +136 -3
- package/dist/ratelimit.js +13 -4
- package/dist/recall.js +14 -13
- package/dist/rede-adesao.js +13 -8
- package/dist/rede-forja.js +108 -24
- package/dist/rede-status.js +63 -45
- package/dist/rede.js +86 -46
- package/dist/retomada-checkpoint.js +101 -0
- package/dist/retry.js +326 -78
- package/dist/roadmap-reservas.js +32 -32
- package/dist/runtime-ambiente.js +5 -0
- package/dist/runtime-context.js +279 -85
- package/dist/runtime-escada.js +429 -0
- package/dist/runtime-profiles.js +130 -63
- package/dist/runtimes.js +42 -5
- package/dist/saida-segura.js +3 -2
- package/dist/security-scan/agil.js +366 -0
- package/dist/security-scan/cli.js +76 -0
- package/dist/security-scan/compare.js +149 -0
- package/dist/security-scan/config.js +93 -0
- package/dist/security-scan/detect.js +97 -0
- package/dist/security-scan/executor.js +196 -0
- package/dist/security-scan/guard.js +180 -0
- package/dist/security-scan/imagem-de-lint.js +96 -0
- package/dist/security-scan/normalize/actionlint.js +12 -0
- package/dist/security-scan/normalize/eslint.js +12 -0
- package/dist/security-scan/normalize/gitleaks.js +13 -0
- package/dist/security-scan/normalize/golangci-lint.js +13 -0
- package/dist/security-scan/normalize/govulncheck.js +36 -0
- package/dist/security-scan/normalize/index.js +25 -0
- package/dist/security-scan/normalize/npm-audit.js +30 -0
- package/dist/security-scan/normalize/osv-scanner.js +32 -0
- package/dist/security-scan/normalize/oxlint.js +51 -0
- package/dist/security-scan/normalize/pip-audit.js +16 -0
- package/dist/security-scan/normalize/ruff.js +12 -0
- package/dist/security-scan/normalize/semgrep.js +20 -0
- package/dist/security-scan/normalize/trivy.js +33 -0
- package/dist/security-scan/normalize/util.js +204 -0
- package/dist/security-scan/normalize/zizmor.js +19 -0
- package/dist/security-scan/report.js +156 -0
- package/dist/security-scan/run.js +448 -0
- package/dist/security-scan/tools.js +62 -0
- package/dist/security-scan/trabalhador.js +74 -0
- package/dist/security-scan/types.js +15 -0
- package/dist/session-events.js +19 -2
- package/dist/session-watcher-claude.js +74 -19
- package/dist/session-watcher-grok.js +425 -0
- package/dist/session-watcher.js +174 -13
- package/dist/sessoes-contas.js +42 -5
- package/dist/sessoes-inventario.js +1 -1
- package/dist/sessoes.js +25 -23
- package/dist/setup.js +67 -66
- package/dist/ship-publicar.js +263 -92
- package/dist/ship.js +160 -17
- package/dist/superficie.js +14 -11
- package/dist/thread-close.js +9 -9
- package/dist/thread.js +121 -18
- package/dist/tokens.js +15 -11
- package/dist/trabalho-thread.js +297 -0
- package/dist/transporte-do-canal.js +189 -0
- package/dist/types.js +12 -1
- package/dist/util.js +5 -2
- package/dist/varredura-acoes.js +500 -0
- package/dist/verify.js +491 -32
- package/dist/worktree-local.js +12 -10
- package/dist/worktree.js +81 -85
- package/eval/casos/check-quality.json +77 -0
- package/eval/casos/security-auditor.json +148 -0
- package/eval/casos/security-remediation.json +161 -0
- package/eval/fixtures/security-scan/README.md +8 -0
- package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/go/Dockerfile +4 -0
- package/eval/fixtures/security-scan/go/README.md +4 -0
- package/eval/fixtures/security-scan/go/config.go +4 -0
- package/eval/fixtures/security-scan/go/go.mod +5 -0
- package/eval/fixtures/security-scan/go/go.sum +2 -0
- package/eval/fixtures/security-scan/go/main.go +31 -0
- package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/js/Dockerfile +3 -0
- package/eval/fixtures/security-scan/js/README.md +4 -0
- package/eval/fixtures/security-scan/js/package-lock.json +844 -0
- package/eval/fixtures/security-scan/js/package.json +10 -0
- package/eval/fixtures/security-scan/js/src/config.js +4 -0
- package/eval/fixtures/security-scan/js/src/server.js +17 -0
- package/eval/fixtures/security-scan/js/src/util.ts +4 -0
- package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
- package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/python/Dockerfile +3 -0
- package/eval/fixtures/security-scan/python/README.md +4 -0
- package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
- package/eval/fixtures/security-scan/python/app/config.py +2 -0
- package/eval/fixtures/security-scan/python/app/main.py +19 -0
- package/eval/fixtures/security-scan/python/requirements.txt +1 -0
- package/package.json +10 -5
- package/references/security-checklist.md +3 -3
- package/skills/README.md +1 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/check-quality/SKILL.md +16 -0
- package/skills/phases/go-implementation/SKILL.md +6 -0
- package/skills/phases/security-remediation/SKILL.md +70 -0
- package/skills/reviewers/security-auditor/SKILL.md +25 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RM-069: a DSN da memória fora do ambiente dos agentes. Domicílio único de onde a DSN vem (a variável de ambiente
|
|
4
|
+
* nomeada em `memory.database_url_env`, ou o arquivo da pasta de segredos do `ork` nomeado em
|
|
5
|
+
* `memory.database_url_file`), de como o arquivo é lido e de como o `ork doctor` procura DSN no ambiente sem imprimir
|
|
6
|
+
* valor.
|
|
7
|
+
*
|
|
8
|
+
* As regras (G2 a G4 do GOAL da ork-dsnforadoamb, D18 a D21 do GO-FIX 1, D24 do GO-FIX 2):
|
|
9
|
+
* - uma fonte só: com `database_url_file`, só o arquivo vale, e o `ork` nunca cai para a variável;
|
|
10
|
+
* - o manifesto vem do repositório, então só nomeia arquivo da pasta de segredos (`~/.orkastery/segredos/<nome>`):
|
|
11
|
+
* ele não escolhe outro arquivo do dono para mandar ao `memory.cli` (P6 da fronteira de confiança);
|
|
12
|
+
* - a raiz da pasta do usuário do `ork` sai do caminho real da casa do usuário (`HOME`): vinda de `ORK_USUARIO_DIR`
|
|
13
|
+
* ou `ORKASTERY_HOME`, é um caminho absoluto sem `..`, dentro da casa, e uma pasta do próprio usuário; fora disso,
|
|
14
|
+
* a recusa vem antes de abrir qualquer coisa;
|
|
15
|
+
* - o caminho é conferido antes de abrir: cada pasta da casa até a de segredos é pasta comum, sem link simbólico, e o
|
|
16
|
+
* caminho real do arquivo é o próprio caminho;
|
|
17
|
+
* - o arquivo é comum, do usuário que roda o `ork`, sem link simbólico, com permissão 0600 ou 0400 exata (os bits
|
|
18
|
+
* especiais contam), até 4 KiB e uma linha só com a DSN (`esquema://...`), e é lido a cada uso;
|
|
19
|
+
* - nenhum detalhe repete o valor, e o caminho sai com `~` ou com o nome da variável da pasta do usuário;
|
|
20
|
+
* - a DSN e a senha dela, de qualquer tamanho, nunca saem do `ork`: a resposta do processo filho que as repete é
|
|
21
|
+
* recusada inteira na fronteira (`contemFragmentoDaDsn`).
|
|
22
|
+
*
|
|
23
|
+
* Só importa `node:*` e `maquina.ts`: o `orkmind.ts` carrega este módulo sem o parser do manifesto.
|
|
24
|
+
*/
|
|
25
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
28
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
29
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(o, k2, desc);
|
|
32
|
+
}) : (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
o[k2] = m[k];
|
|
35
|
+
}));
|
|
36
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
37
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
38
|
+
}) : function(o, v) {
|
|
39
|
+
o["default"] = v;
|
|
40
|
+
});
|
|
41
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
42
|
+
var ownKeys = function(o) {
|
|
43
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
44
|
+
var ar = [];
|
|
45
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
46
|
+
return ar;
|
|
47
|
+
};
|
|
48
|
+
return ownKeys(o);
|
|
49
|
+
};
|
|
50
|
+
return function (mod) {
|
|
51
|
+
if (mod && mod.__esModule) return mod;
|
|
52
|
+
var result = {};
|
|
53
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
54
|
+
__setModuleDefault(result, mod);
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
})();
|
|
58
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
|
+
exports.CAUSAS_DA_DSN = exports.LIMITE_DO_ARQUIVO_DA_DSN = exports.PREFIXO_DO_ARQUIVO_DA_DSN = exports.PASTA_DE_SEGREDOS = void 0;
|
|
60
|
+
exports.textoDaCausa = textoDaCausa;
|
|
61
|
+
exports.arquivoDaDsnValido = arquivoDaDsnValido;
|
|
62
|
+
exports.rotuloDoArquivoDaDsn = rotuloDoArquivoDaDsn;
|
|
63
|
+
exports.resolverDsn = resolverDsn;
|
|
64
|
+
exports.fragmentosDaDsn = fragmentosDaDsn;
|
|
65
|
+
exports.contemFragmento = contemFragmento;
|
|
66
|
+
exports.contemFragmentoDaDsn = contemFragmentoDaDsn;
|
|
67
|
+
exports.variaveisComUrlComSenha = variaveisComUrlComSenha;
|
|
68
|
+
exports.variaveisComDsn = variaveisComDsn;
|
|
69
|
+
const fs = __importStar(require("node:fs"));
|
|
70
|
+
const os = __importStar(require("node:os"));
|
|
71
|
+
const path = __importStar(require("node:path"));
|
|
72
|
+
const maquina_1 = require("./maquina");
|
|
73
|
+
/** A pasta de segredos, dentro da pasta do usuário do `ork`. */
|
|
74
|
+
exports.PASTA_DE_SEGREDOS = 'segredos';
|
|
75
|
+
/** A única forma aceita de `memory.database_url_file`: este prefixo e um nome de arquivo. */
|
|
76
|
+
exports.PREFIXO_DO_ARQUIVO_DA_DSN = '~/.orkastery/segredos/';
|
|
77
|
+
const NOME_DO_ARQUIVO = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
78
|
+
/** Maior arquivo de DSN aceito, em bytes. */
|
|
79
|
+
exports.LIMITE_DO_ARQUIVO_DA_DSN = 4096;
|
|
80
|
+
/** D18: as permissões aceitas, comparadas com os bits especiais (setuid, setgid, sticky); nenhuma outra passa. */
|
|
81
|
+
const PERMISSOES_ACEITAS = [0o600, 0o400];
|
|
82
|
+
/** D19: o esquema de URL (RFC 3986), `://` e algo depois; DSN de vários hosts da libpq não é URL para o `new URL`. */
|
|
83
|
+
const DSN_COM_ESQUEMA = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S/;
|
|
84
|
+
exports.CAUSAS_DA_DSN = ['forma', 'raiz', 'nao-existe', 'nao-abre', 'pasta', 'dono-da-pasta',
|
|
85
|
+
'link', 'nao-e-arquivo', 'plataforma', 'dono', 'permissao', 'tamanho', 'vazio', 'codificacao', 'linhas', 'controle',
|
|
86
|
+
'nome-valor', 'esquema', 'espaco', 'leitura'];
|
|
87
|
+
/** O texto pt-BR da causa, sem o valor; `permissao` só entra na causa `permissao`. */
|
|
88
|
+
function textoDaCausa(causa, permissao = '') {
|
|
89
|
+
switch (causa) {
|
|
90
|
+
case 'forma': return `memory.database_url_file fora da forma ${exports.PREFIXO_DO_ARQUIVO_DA_DSN}<nome> (valor omitido)`;
|
|
91
|
+
case 'raiz': return 'a pasta do usuário do ork precisa ser caminho absoluto sem .., dentro da casa do usuário (HOME)';
|
|
92
|
+
case 'nao-existe': return 'não existe';
|
|
93
|
+
case 'nao-abre': return 'não foi possível abrir para leitura';
|
|
94
|
+
case 'pasta': return 'o caminho da pasta de segredos tem link simbólico ou algo que não é pasta';
|
|
95
|
+
case 'dono-da-pasta': return 'a pasta do usuário do ork não é do usuário que roda o ork';
|
|
96
|
+
case 'link': return 'link simbólico não é aceito';
|
|
97
|
+
case 'nao-e-arquivo': return 'não é arquivo comum';
|
|
98
|
+
case 'plataforma': return 'esta plataforma não confere dono nem permissão';
|
|
99
|
+
case 'dono': return 'o dono não é o usuário que roda o ork';
|
|
100
|
+
case 'permissao': return `permissão ${permissao}, diferente de 0600 e de 0400`;
|
|
101
|
+
case 'tamanho': return 'maior que 4 KiB';
|
|
102
|
+
case 'vazio': return 'vazio';
|
|
103
|
+
case 'codificacao': return 'não é texto UTF-8';
|
|
104
|
+
case 'linhas': return 'mais de uma linha';
|
|
105
|
+
case 'controle': return 'caractere de controle';
|
|
106
|
+
case 'nome-valor': return 'formato NOME=valor de arquivo de ambiente; grave só a DSN';
|
|
107
|
+
case 'esquema': return 'não é DSN com esquema (como postgresql://)';
|
|
108
|
+
case 'espaco': return 'espaço dentro da DSN';
|
|
109
|
+
case 'leitura': return 'não foi possível ler';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** A forma aceita de `memory.database_url_file` (G3): `~/.orkastery/segredos/<nome>`, sem subpasta. */
|
|
113
|
+
function arquivoDaDsnValido(valor) {
|
|
114
|
+
return valor.startsWith(exports.PREFIXO_DO_ARQUIVO_DA_DSN) && NOME_DO_ARQUIVO.test(valor.slice(exports.PREFIXO_DO_ARQUIVO_DA_DSN.length));
|
|
115
|
+
}
|
|
116
|
+
/** A variável que dá a pasta do usuário do `ork`, na regra de `pastaDoUsuario`; sem nenhuma, a pasta é `~/.orkastery`. */
|
|
117
|
+
function variavelDaPastaDoUsuario(env) {
|
|
118
|
+
return (env.ORK_USUARIO_DIR ?? '').trim() ? 'ORK_USUARIO_DIR' : (env.ORKASTERY_HOME ?? '').trim() ? 'ORKASTERY_HOME' : null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* O rótulo do arquivo (D2), na regra da pasta do usuário (`pastaDoUsuario`): `ORK_USUARIO_DIR` vence
|
|
122
|
+
* `ORKASTERY_HOME`, que vence `~/.orkastery`. É o que vai a saída, ledger e bundle no lugar do caminho real.
|
|
123
|
+
*/
|
|
124
|
+
function rotuloDoArquivoDaDsn(nome, env = process.env) {
|
|
125
|
+
return `${variavelDaPastaDoUsuario(env) ?? '~/.orkastery'}/${exports.PASTA_DE_SEGREDOS}/${nome}`;
|
|
126
|
+
}
|
|
127
|
+
function uidDoProcesso() {
|
|
128
|
+
return typeof process.getuid === 'function' ? process.getuid() : null;
|
|
129
|
+
}
|
|
130
|
+
function codigoDoErro(e) {
|
|
131
|
+
return String(e?.code ?? '');
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A DSN da memória pela fonte que o bloco `memory` declara. Com `database_url_file`, só o arquivo; com
|
|
135
|
+
* `database_url_env`, o ambiente, como sempre foi; sem nenhuma, `dsn.nao-declarado`. Nunca há segunda candidata.
|
|
136
|
+
*/
|
|
137
|
+
function resolverDsn(memory, opcoes = {}) {
|
|
138
|
+
const env = opcoes.env ?? process.env;
|
|
139
|
+
const arquivo = (memory.database_url_file ?? '').trim();
|
|
140
|
+
if (arquivo)
|
|
141
|
+
return lerArquivoDaDsn(arquivo, env, opcoes.uid === undefined ? uidDoProcesso() : opcoes.uid);
|
|
142
|
+
const variavel = (memory.database_url_env ?? '').trim();
|
|
143
|
+
if (variavel) {
|
|
144
|
+
const dsn = env[variavel] ?? '';
|
|
145
|
+
return dsn.trim()
|
|
146
|
+
? { fonte: 'ambiente', rotulo: variavel, dsn, falha: null, detalhe: '' }
|
|
147
|
+
: { fonte: 'ambiente', rotulo: variavel, dsn: '', falha: 'dsn.env-ausente', detalhe: `${variavel} não está no ambiente` };
|
|
148
|
+
}
|
|
149
|
+
return { fonte: 'nenhuma', rotulo: '', dsn: '', falha: 'dsn.nao-declarado',
|
|
150
|
+
detalhe: 'nenhuma fonte declarada (memory.database_url_file ou memory.database_url_env)' };
|
|
151
|
+
}
|
|
152
|
+
function ausente(e) {
|
|
153
|
+
return { falha: 'dsn.arquivo-ausente', causa: ['ENOENT', 'ENOTDIR'].includes(codigoDoErro(e)) ? 'nao-existe' : 'nao-abre' };
|
|
154
|
+
}
|
|
155
|
+
/** Um componente `..`, nos dois separadores: o `path.resolve` o tira pelo texto, sem seguir o link que venha antes. */
|
|
156
|
+
function temPontoPonto(caminho) {
|
|
157
|
+
return caminho.split(/[\\/]/).includes('..');
|
|
158
|
+
}
|
|
159
|
+
/** As pastas de `caminho` abaixo de `pasta`, quando ele fica dentro dela sem ser ela; senão, `null`. */
|
|
160
|
+
function partesAbaixo(pasta, caminho) {
|
|
161
|
+
const relativo = path.relative(pasta, caminho);
|
|
162
|
+
return relativo && relativo !== '..' && !relativo.startsWith(`..${path.sep}`) && !path.isAbsolute(relativo)
|
|
163
|
+
? relativo.split(path.sep) : null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* D24: a raiz da pasta do usuário do `ork`, canonizada pelo caminho real antes de qualquer leitura. A casa do usuário
|
|
167
|
+
* (`HOME`) vale pelo caminho real: o que fica acima dela é do sistema de quem roda o `ork` e não é conferido. Sem
|
|
168
|
+
* variável, a raiz é a `.orkastery` da casa, como em `pastaDoUsuario`. Vinda de `ORK_USUARIO_DIR` ou de
|
|
169
|
+
* `ORKASTERY_HOME`, é um caminho absoluto, sem `..`, abaixo da casa (escrita como o ambiente a escreve, ou pelo caminho
|
|
170
|
+
* real) e sem ser ela. Fora disso, a causa `raiz`, sem abrir nada; as pastas abaixo da casa, `conferirCaminho` confere.
|
|
171
|
+
*/
|
|
172
|
+
function raizDaPastaDoUsuario(env) {
|
|
173
|
+
const casa = (env.HOME ?? '').trim() || os.homedir();
|
|
174
|
+
const daVariavel = variavelDaPastaDoUsuario(env) !== null;
|
|
175
|
+
const declarada = daVariavel ? (0, maquina_1.pastaDoUsuario)(env) : '';
|
|
176
|
+
if (!path.isAbsolute(casa) || (daVariavel && (!path.isAbsolute(declarada) || temPontoPonto(declarada)))) {
|
|
177
|
+
return { falha: 'dsn.arquivo-invalido', causa: 'raiz' };
|
|
178
|
+
}
|
|
179
|
+
let real;
|
|
180
|
+
try {
|
|
181
|
+
real = fs.realpathSync(casa);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
return ausente(e);
|
|
185
|
+
}
|
|
186
|
+
if (!daVariavel)
|
|
187
|
+
return { casa: real, partes: ['.orkastery'], pasta: path.join(real, '.orkastery'), daVariavel };
|
|
188
|
+
const alvo = path.resolve(declarada);
|
|
189
|
+
// A casa como o ambiente a escreve só vale sem `..`, que o `path.resolve` tiraria pelo texto; a real vale sempre.
|
|
190
|
+
const partes = (temPontoPonto(casa) ? null : partesAbaixo(path.resolve(casa), alvo)) ?? partesAbaixo(real, alvo);
|
|
191
|
+
if (!partes)
|
|
192
|
+
return { falha: 'dsn.arquivo-invalido', causa: 'raiz' };
|
|
193
|
+
return { casa: real, partes, pasta: path.join(real, ...partes), daVariavel };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* D24: o caminho do arquivo conferido componente a componente pelo `lstat`, da casa real ao arquivo, sem seguir link e
|
|
197
|
+
* antes de abrir qualquer coisa: cada pasta da raiz e a de segredos são pastas comuns, a raiz vinda do ambiente é do
|
|
198
|
+
* usuário que roda o `ork`, o arquivo é arquivo comum, e o caminho real do arquivo é o próprio caminho (a segunda prova,
|
|
199
|
+
* contra troca no meio). Devolve o caminho e o `lstat` do arquivo, ou a falha.
|
|
200
|
+
*/
|
|
201
|
+
function conferirCaminho(raiz, nome, uid) {
|
|
202
|
+
let pasta = raiz.casa;
|
|
203
|
+
for (const parte of [...raiz.partes, exports.PASTA_DE_SEGREDOS]) {
|
|
204
|
+
pasta = path.join(pasta, parte);
|
|
205
|
+
let st;
|
|
206
|
+
try {
|
|
207
|
+
st = fs.lstatSync(pasta);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
return ausente(e);
|
|
211
|
+
}
|
|
212
|
+
if (st.isSymbolicLink() || !st.isDirectory())
|
|
213
|
+
return { falha: 'dsn.arquivo-invalido', causa: 'pasta' };
|
|
214
|
+
if (raiz.daVariavel && pasta === raiz.pasta && uid !== null && st.uid !== uid) {
|
|
215
|
+
return { falha: 'dsn.arquivo-inseguro', causa: 'dono-da-pasta' };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const caminho = path.join(pasta, nome);
|
|
219
|
+
let arquivo;
|
|
220
|
+
try {
|
|
221
|
+
arquivo = fs.lstatSync(caminho);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
return ausente(e);
|
|
225
|
+
}
|
|
226
|
+
if (arquivo.isSymbolicLink())
|
|
227
|
+
return { falha: 'dsn.arquivo-invalido', causa: 'link' };
|
|
228
|
+
if (!arquivo.isFile())
|
|
229
|
+
return { falha: 'dsn.arquivo-invalido', causa: 'nao-e-arquivo' };
|
|
230
|
+
try {
|
|
231
|
+
if (fs.realpathSync(caminho) !== caminho)
|
|
232
|
+
return { falha: 'dsn.arquivo-invalido', causa: 'pasta' };
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
return ausente(e);
|
|
236
|
+
}
|
|
237
|
+
return { caminho, arquivo };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* D19: o conteúdo pelo contrato inteiro, sem repetir o valor. Texto UTF-8 de até 4 KiB; um único fim de linha no fim
|
|
241
|
+
* (`\n` ou `\r\n`) é o único aparo antes das guardas de linha e de controle; depois delas, só o espaço comum em volta
|
|
242
|
+
* sai, e o que fica é uma DSN `esquema://...` sem espaço.
|
|
243
|
+
*/
|
|
244
|
+
function dsnDoConteudo(bruto) {
|
|
245
|
+
if (bruto.length > exports.LIMITE_DO_ARQUIVO_DA_DSN)
|
|
246
|
+
return { causa: 'tamanho' };
|
|
247
|
+
let texto;
|
|
248
|
+
try {
|
|
249
|
+
texto = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bruto);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return { causa: 'codificacao' };
|
|
253
|
+
}
|
|
254
|
+
const linha = texto.endsWith('\r\n') ? texto.slice(0, -2) : texto.endsWith('\n') ? texto.slice(0, -1) : texto;
|
|
255
|
+
if (!linha.trim())
|
|
256
|
+
return { causa: 'vazio' };
|
|
257
|
+
if (/[\r\n\u0085\u2028\u2029]/.test(linha))
|
|
258
|
+
return { causa: 'linhas' };
|
|
259
|
+
if (/[\p{Cc}\p{Cf}]/u.test(linha))
|
|
260
|
+
return { causa: 'controle' };
|
|
261
|
+
const dsn = linha.replace(/^ +| +$/g, '');
|
|
262
|
+
if (/^[A-Z_][A-Z0-9_]*=/.test(dsn))
|
|
263
|
+
return { causa: 'nome-valor' };
|
|
264
|
+
if (!DSN_COM_ESQUEMA.test(dsn))
|
|
265
|
+
return { causa: 'esquema' };
|
|
266
|
+
if (/\s/.test(dsn))
|
|
267
|
+
return { causa: 'espaco' };
|
|
268
|
+
return { dsn };
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* G4 (D18, D19, D24): confere a raiz e o caminho, abre o arquivo sem seguir link, confere o descritor aberto (o mesmo
|
|
272
|
+
* arquivo do `lstat`, comum, do usuário, permissão exata, tamanho) e só então lê, até o limite, pelo contrato inteiro.
|
|
273
|
+
*/
|
|
274
|
+
function lerArquivoDaDsn(declarado, env, uid) {
|
|
275
|
+
if (!arquivoDaDsnValido(declarado)) {
|
|
276
|
+
return { fonte: 'arquivo', rotulo: `${exports.PREFIXO_DO_ARQUIVO_DA_DSN}<nome>`, dsn: '', falha: 'dsn.arquivo-invalido',
|
|
277
|
+
detalhe: textoDaCausa('forma'), causa: 'forma' };
|
|
278
|
+
}
|
|
279
|
+
const nome = declarado.slice(exports.PREFIXO_DO_ARQUIVO_DA_DSN.length);
|
|
280
|
+
const rotulo = rotuloDoArquivoDaDsn(nome, env);
|
|
281
|
+
const falhou = (falha, causa, permissao) => ({ fonte: 'arquivo', rotulo, dsn: '', falha, detalhe: `${rotulo}: ${textoDaCausa(causa, permissao)}`, causa,
|
|
282
|
+
...(permissao ? { permissao } : {}) });
|
|
283
|
+
const raiz = raizDaPastaDoUsuario(env);
|
|
284
|
+
if ('falha' in raiz)
|
|
285
|
+
return falhou(raiz.falha, raiz.causa);
|
|
286
|
+
const antes = conferirCaminho(raiz, nome, uid);
|
|
287
|
+
if ('falha' in antes)
|
|
288
|
+
return falhou(antes.falha, antes.causa);
|
|
289
|
+
let fd = null;
|
|
290
|
+
try {
|
|
291
|
+
try {
|
|
292
|
+
fd = fs.openSync(antes.caminho, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
const codigo = codigoDoErro(e);
|
|
296
|
+
return codigo === 'ELOOP' ? falhou('dsn.arquivo-invalido', 'link')
|
|
297
|
+
: falhou('dsn.arquivo-ausente', codigo === 'ENOENT' ? 'nao-existe' : 'nao-abre');
|
|
298
|
+
}
|
|
299
|
+
const st = fs.fstatSync(fd);
|
|
300
|
+
if (!st.isFile())
|
|
301
|
+
return falhou('dsn.arquivo-invalido', 'nao-e-arquivo');
|
|
302
|
+
// O descritor aberto é o arquivo que o `lstat` conferiu: nada foi trocado entre a conferência e a abertura.
|
|
303
|
+
if (st.dev !== antes.arquivo.dev || st.ino !== antes.arquivo.ino)
|
|
304
|
+
return falhou('dsn.arquivo-ausente', 'nao-abre');
|
|
305
|
+
const modo = st.mode & 0o7777;
|
|
306
|
+
const permissao = modo.toString(8).padStart(4, '0');
|
|
307
|
+
if (uid === null)
|
|
308
|
+
return falhou('dsn.arquivo-inseguro', 'plataforma', permissao);
|
|
309
|
+
if (st.uid !== uid)
|
|
310
|
+
return falhou('dsn.arquivo-inseguro', 'dono', permissao);
|
|
311
|
+
if (!PERMISSOES_ACEITAS.includes(modo))
|
|
312
|
+
return falhou('dsn.arquivo-inseguro', 'permissao', permissao);
|
|
313
|
+
if (st.size > exports.LIMITE_DO_ARQUIVO_DA_DSN)
|
|
314
|
+
return falhou('dsn.arquivo-invalido', 'tamanho', permissao);
|
|
315
|
+
// Lê até um byte além do limite: o arquivo que cresceu depois do `fstat` cai no tamanho, sem ser lido inteiro.
|
|
316
|
+
const bruto = Buffer.alloc(exports.LIMITE_DO_ARQUIVO_DA_DSN + 1);
|
|
317
|
+
let lidos = 0, n = -1;
|
|
318
|
+
while (n !== 0 && lidos < bruto.length) {
|
|
319
|
+
n = fs.readSync(fd, bruto, lidos, bruto.length - lidos, null);
|
|
320
|
+
lidos += n;
|
|
321
|
+
}
|
|
322
|
+
const conteudo = dsnDoConteudo(bruto.subarray(0, lidos));
|
|
323
|
+
if ('causa' in conteudo)
|
|
324
|
+
return falhou('dsn.arquivo-invalido', conteudo.causa, permissao);
|
|
325
|
+
return { fonte: 'arquivo', rotulo, dsn: conteudo.dsn, falha: null, detalhe: '', permissao };
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return falhou('dsn.arquivo-ausente', 'leitura');
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
if (fd !== null)
|
|
332
|
+
fs.closeSync(fd);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function decodificada(texto) {
|
|
336
|
+
try {
|
|
337
|
+
return decodeURIComponent(texto);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return texto;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* As senhas que a DSN carrega, cruas como estão no texto: a do usuário (`esquema://usuario:senha@`, inclusive com vários
|
|
345
|
+
* hosts), a que o `new URL` lê, a do parâmetro `password` da consulta e a de `password=` na forma `chave=valor` da libpq.
|
|
346
|
+
*/
|
|
347
|
+
function senhasDaDsn(dsn) {
|
|
348
|
+
const senhas = [];
|
|
349
|
+
const usuario = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/([^/?#]*)@/.exec(dsn)?.[1] ?? '';
|
|
350
|
+
if (usuario.includes(':'))
|
|
351
|
+
senhas.push(usuario.slice(usuario.indexOf(':') + 1));
|
|
352
|
+
try {
|
|
353
|
+
senhas.push(new URL(dsn).password);
|
|
354
|
+
}
|
|
355
|
+
catch { /* não é URL para o `new URL`: as outras leituras valem */ }
|
|
356
|
+
const consulta = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^?#]*\?([^#]*)/.exec(dsn)?.[1] ?? '';
|
|
357
|
+
for (const par of consulta.split('&')) {
|
|
358
|
+
const igual = par.indexOf('=');
|
|
359
|
+
if (igual > 0 && decodificada(par.slice(0, igual)) === 'password')
|
|
360
|
+
senhas.push(par.slice(igual + 1));
|
|
361
|
+
}
|
|
362
|
+
for (const m of dsn.matchAll(/(?:^|\s)password\s*=\s*('(?:[^'\\]|\\.)*'|(?:[^\s'\\]|\\.)+)/g)) {
|
|
363
|
+
senhas.push((m[1].startsWith("'") ? m[1].slice(1, -1) : m[1]).replace(/\\(.)/g, '$1'));
|
|
364
|
+
}
|
|
365
|
+
return senhas.filter(Boolean);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* D6 e D20: o que da DSN nunca pode sair do `ork`: ela inteira (como veio e aparada) e cada senha dela, de qualquer
|
|
369
|
+
* tamanho, crua e decodificada. Senha curta ou comum também recusa a resposta legítima que tiver o mesmo trecho: a
|
|
370
|
+
* barreira prefere a recusa ao vazamento.
|
|
371
|
+
*/
|
|
372
|
+
function fragmentosDaDsn(dsn) {
|
|
373
|
+
const texto = dsn.trim();
|
|
374
|
+
if (!texto)
|
|
375
|
+
return [];
|
|
376
|
+
const fragmentos = [dsn, texto];
|
|
377
|
+
for (const senha of senhasDaDsn(texto))
|
|
378
|
+
fragmentos.push(senha, decodificada(senha));
|
|
379
|
+
return [...new Set(fragmentos.filter(Boolean))];
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* D21: verdadeiro quando algum texto do valor traz um dos fragmentos, em qualquer nível: as strings, as chaves de objeto
|
|
383
|
+
* e os números como saem no JSON. É a barreira da fronteira com o processo filho: a resposta que repete a DSN ou a senha
|
|
384
|
+
* é recusada inteira, com um código sem o valor, e nunca chega a saída, erro, ledger ou bundle.
|
|
385
|
+
*/
|
|
386
|
+
function contemFragmento(valor, fragmentos) {
|
|
387
|
+
const proibidos = fragmentos.filter(Boolean);
|
|
388
|
+
const examinar = (x) => {
|
|
389
|
+
if (typeof x === 'string')
|
|
390
|
+
return proibidos.some((p) => x.includes(p));
|
|
391
|
+
if (typeof x === 'number')
|
|
392
|
+
return examinar(String(x));
|
|
393
|
+
if (!x || typeof x !== 'object')
|
|
394
|
+
return false;
|
|
395
|
+
if (Array.isArray(x))
|
|
396
|
+
return x.some(examinar);
|
|
397
|
+
return Object.entries(x).some(([k, v]) => examinar(k) || examinar(v));
|
|
398
|
+
};
|
|
399
|
+
return proibidos.length > 0 && examinar(valor);
|
|
400
|
+
}
|
|
401
|
+
/** D21: a DSN ou a senha dela em algum texto do valor (`contemFragmento` com `fragmentosDaDsn`). */
|
|
402
|
+
function contemFragmentoDaDsn(valor, dsn) {
|
|
403
|
+
return contemFragmento(valor, fragmentosDaDsn(dsn));
|
|
404
|
+
}
|
|
405
|
+
/** D7: `esquema://usuario:senha@`, com usuário vazio aceito e senha obrigatória, em qualquer ponto do valor. */
|
|
406
|
+
const URL_COM_SENHA = /[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/?#@:]*:[^\s/?#@]+@/;
|
|
407
|
+
/** Os NOMES, em ordem, das variáveis com URL com senha. Nunca o valor. */
|
|
408
|
+
function variaveisComUrlComSenha(env) {
|
|
409
|
+
return Object.keys(env).filter((nome) => URL_COM_SENHA.test(env[nome] ?? '')).sort();
|
|
410
|
+
}
|
|
411
|
+
/** Os NOMES, em ordem, das variáveis cujo valor traz a DSN dada ou a senha dela (D6). Nunca o valor. */
|
|
412
|
+
function variaveisComDsn(env, dsn) {
|
|
413
|
+
const fragmentos = fragmentosDaDsn(dsn);
|
|
414
|
+
if (!fragmentos.length)
|
|
415
|
+
return [];
|
|
416
|
+
return Object.keys(env).filter((nome) => fragmentos.some((f) => (env[nome] ?? '').includes(f))).sort();
|
|
417
|
+
}
|