@cohortapp/agent-sdk 2.3.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/.claude/commands/init-agent.md +104 -0
- package/.claude/commands/init-maestro.md +1187 -0
- package/.claude/settings.json +161 -0
- package/.env.example +216 -0
- package/README.md +632 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/communications/agent.md +96 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/archetypes/altitudes/c-suite.yaml +58 -0
- package/archetypes/altitudes/founder.yaml +68 -0
- package/archetypes/altitudes/senior-manager.yaml +63 -0
- package/archetypes/altitudes/svp.yaml +60 -0
- package/archetypes/altitudes/vp.yaml +50 -0
- package/archetypes/archetype.schema.json +77 -0
- package/archetypes/base.yaml +47 -0
- package/archetypes/capabilities/commercial-leader.yaml +159 -0
- package/archetypes/capabilities/compliance-officer.yaml +159 -0
- package/archetypes/capabilities/executive-operator.yaml +169 -0
- package/archetypes/capabilities/finance-leader.yaml +162 -0
- package/archetypes/capabilities/operations-leader.yaml +154 -0
- package/archetypes/capabilities/product-leader.yaml +148 -0
- package/archetypes/capabilities/technical-leader.yaml +146 -0
- package/archetypes/functions/commercial-leader.yaml +62 -0
- package/archetypes/functions/compliance-officer.yaml +64 -0
- package/archetypes/functions/executive-operator.yaml +70 -0
- package/archetypes/functions/finance-leader.yaml +70 -0
- package/archetypes/functions/operations-leader.yaml +62 -0
- package/archetypes/functions/product-leader.yaml +61 -0
- package/archetypes/functions/technical-leader.yaml +57 -0
- package/bin/cohort-mcp.mjs +81 -0
- package/bin/maestro.mjs +3516 -0
- package/bin/maestro.test.mjs +1015 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2239 -0
- package/docs/architecture/archetype-agent-factory.md +110 -0
- package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +585 -0
- package/docs/architecture/system-architecture.md +1272 -0
- package/docs/company-context/README.md +40 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/guides/agents-observe-setup.md +64 -0
- package/docs/guides/billing-console-keys.md +88 -0
- package/docs/guides/ccxray-diagnostics.md +65 -0
- package/docs/guides/channel-bus.md +127 -0
- package/docs/guides/claude-mem-setup.md +79 -0
- package/docs/guides/claude-pace-setup.md +56 -0
- package/docs/guides/claudraband-sessions.md +98 -0
- package/docs/guides/clawteam-swarm.md +116 -0
- package/docs/guides/code-review-graph-setup.md +86 -0
- package/docs/guides/email-setup.md +431 -0
- package/docs/guides/mac-mini.md +119 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/model-routing.md +162 -0
- package/docs/guides/observability-otel.md +265 -0
- package/docs/guides/org-onboarding.md +132 -0
- package/docs/guides/outbound-governance-setup.md +437 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +563 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/self-optimization-pattern.md +82 -0
- package/docs/guides/setup-wizard.md +178 -0
- package/docs/guides/slack-setup.md +350 -0
- package/docs/guides/telegram-setup.md +227 -0
- package/docs/guides/twilio-subaccounts-setup.md +223 -0
- package/docs/guides/verification.md +128 -0
- package/docs/guides/voice-mode.md +188 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/webhook-relay-setup.md +349 -0
- package/docs/guides/whatsapp-setup.md +288 -0
- package/docs/prompts/board-pack-cover-template.md +36 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/research/hallucinated-tool-output-investigation.md +151 -0
- package/docs/runbooks/backup-restore.md +205 -0
- package/docs/runbooks/cohort-cutover.md +129 -0
- package/docs/runbooks/fleet-operations.md +200 -0
- package/docs/runbooks/incident-response.md +226 -0
- package/docs/runbooks/mac-mini-bootstrap.md +431 -0
- package/docs/runbooks/perpetual-operations.md +509 -0
- package/docs/runbooks/recovery-and-failover.md +260 -0
- package/framework-features.json +267 -0
- package/ingest/README.md +87 -0
- package/lib/action-executor.js +689 -0
- package/lib/action-executor.test.mjs +871 -0
- package/lib/agent-root.mjs +37 -0
- package/lib/archetype.mjs +236 -0
- package/lib/archetype.test.mjs +132 -0
- package/lib/autonomy.mjs +114 -0
- package/lib/autonomy.test.mjs +66 -0
- package/lib/backlog.mjs +358 -0
- package/lib/backlog.test.mjs +266 -0
- package/lib/budget-guard.mjs +279 -0
- package/lib/budget-guard.test.mjs +291 -0
- package/lib/cadence-bus-schedule.test.mjs +194 -0
- package/lib/cadence-bus.mjs +1120 -0
- package/lib/cadence-bus.test.mjs +720 -0
- package/lib/cadences.mjs +205 -0
- package/lib/cadences.test.mjs +125 -0
- package/lib/capability.mjs +154 -0
- package/lib/capability.test.mjs +78 -0
- package/lib/channels/base-adapter.mjs +719 -0
- package/lib/channels/base-adapter.test.mjs +590 -0
- package/lib/channels/channel.mjs +128 -0
- package/lib/channels/channels.test.mjs +371 -0
- package/lib/channels/contract.mjs +215 -0
- package/lib/channels/contract.test.mjs +137 -0
- package/lib/channels/conversation-resolver.mjs +95 -0
- package/lib/channels/gmail/adapter.mjs +87 -0
- package/lib/channels/inbox-item.mjs +255 -0
- package/lib/channels/inbox-item.test.mjs +335 -0
- package/lib/channels/index.mjs +94 -0
- package/lib/channels/orgmail/adapter.mjs +353 -0
- package/lib/channels/orgmail/adapter.test.mjs +311 -0
- package/lib/channels/pairing.mjs +363 -0
- package/lib/channels/pairing.test.mjs +270 -0
- package/lib/channels/registry.mjs +164 -0
- package/lib/channels/slack/adapter.mjs +317 -0
- package/lib/channels/slack-adapter.test.mjs +212 -0
- package/lib/channels/sms/adapter.mjs +43 -0
- package/lib/channels/telegram/adapter.mjs +432 -0
- package/lib/channels/telegram-adapter.test.mjs +306 -0
- package/lib/channels/voice/adapter.mjs +301 -0
- package/lib/channels/voice/adapter.test.mjs +278 -0
- package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
- package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
- package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
- package/lib/charter.mjs +256 -0
- package/lib/charter.test.mjs +89 -0
- package/lib/claude-bin.mjs +134 -0
- package/lib/claude-bin.test.mjs +75 -0
- package/lib/collective/capture.mjs +185 -0
- package/lib/collective/capture.test.mjs +121 -0
- package/lib/collective/cards.mjs +201 -0
- package/lib/collective/cards.test.mjs +114 -0
- package/lib/collective/config.mjs +186 -0
- package/lib/collective/config.test.mjs +123 -0
- package/lib/collective/global-config.mjs +113 -0
- package/lib/collective/global-config.test.mjs +75 -0
- package/lib/collective/presence.mjs +201 -0
- package/lib/collective/presence.test.mjs +95 -0
- package/lib/collective/recall.mjs +215 -0
- package/lib/collective/recall.test.mjs +116 -0
- package/lib/comms/send-gate.mjs +554 -0
- package/lib/comms/send-gate.test.mjs +577 -0
- package/lib/comms.mjs +67 -0
- package/lib/comms.test.mjs +41 -0
- package/lib/diagnostics/alerts.mjs +424 -0
- package/lib/diagnostics/alerts.test.mjs +318 -0
- package/lib/diagnostics/backup-freshness.mjs +188 -0
- package/lib/diagnostics/backup-freshness.test.mjs +185 -0
- package/lib/diagnostics/counters.mjs +269 -0
- package/lib/diagnostics/counters.test.mjs +206 -0
- package/lib/diagnostics/events.mjs +188 -0
- package/lib/diagnostics/events.test.mjs +290 -0
- package/lib/diagnostics/otel.mjs +237 -0
- package/lib/diagnostics/otel.test.mjs +196 -0
- package/lib/diagnostics/trace.mjs +216 -0
- package/lib/diagnostics/trace.test.mjs +251 -0
- package/lib/env-compat.mjs +74 -0
- package/lib/env-compat.test.mjs +104 -0
- package/lib/feature-init.mjs +331 -0
- package/lib/fs-atomic.mjs +112 -0
- package/lib/fs-atomic.test.mjs +72 -0
- package/lib/fs-ownership.mjs +111 -0
- package/lib/fs-ownership.test.mjs +158 -0
- package/lib/hooks/bus.mjs +347 -0
- package/lib/hooks/bus.test.mjs +387 -0
- package/lib/index.js +16 -0
- package/lib/learning/config.mjs +106 -0
- package/lib/learning/config.test.mjs +75 -0
- package/lib/learning/counters.mjs +156 -0
- package/lib/learning/counters.test.mjs +69 -0
- package/lib/learning/curator-consolidate.test.mjs +238 -0
- package/lib/learning/curator.mjs +453 -0
- package/lib/learning/curator.test.mjs +106 -0
- package/lib/learning/log.mjs +40 -0
- package/lib/learning/reflect.mjs +534 -0
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.mjs +352 -0
- package/lib/learning/session-index.test.mjs +125 -0
- package/lib/learning/skill-writer.mjs +474 -0
- package/lib/learning/skill-writer.test.mjs +210 -0
- package/lib/mcp/server.mjs +328 -0
- package/lib/mcp/server.test.mjs +400 -0
- package/lib/model-router/auth-profiles.mjs +758 -0
- package/lib/model-router/auth-profiles.test.mjs +580 -0
- package/lib/model-router/catalog/anthropic.yaml +153 -0
- package/lib/model-router/catalog/deepseek.yaml +86 -0
- package/lib/model-router/catalog/moonshot.yaml +81 -0
- package/lib/model-router/catalog/qwen.yaml +114 -0
- package/lib/model-router/catalog.mjs +925 -0
- package/lib/model-router/catalog.test.mjs +385 -0
- package/lib/model-router/economics.mjs +564 -0
- package/lib/model-router/economics.test.mjs +344 -0
- package/lib/model-router/failover.mjs +298 -0
- package/lib/model-router/failover.test.mjs +439 -0
- package/lib/model-router/health.mjs +453 -0
- package/lib/model-router/health.test.mjs +338 -0
- package/lib/model-router/integration-coverage.test.mjs +829 -0
- package/lib/model-router/integration.test.mjs +564 -0
- package/lib/model-router/ledger.mjs +402 -0
- package/lib/model-router/ledger.test.mjs +382 -0
- package/lib/model-router/llm-task.mjs +515 -0
- package/lib/model-router/llm-task.test.mjs +392 -0
- package/lib/model-router/org-credentials.mjs +260 -0
- package/lib/model-router/org-credentials.test.mjs +265 -0
- package/lib/model-router/pricing-refresh.mjs +463 -0
- package/lib/model-router/pricing-refresh.test.mjs +286 -0
- package/lib/model-router/reconcile.mjs +429 -0
- package/lib/model-router/reconcile.test.mjs +316 -0
- package/lib/model-router/repair.mjs +471 -0
- package/lib/model-router/repair.test.mjs +180 -0
- package/lib/model-router/resolve.mjs +1206 -0
- package/lib/model-router/spawn.mjs +497 -0
- package/lib/model-router/spawn.test.mjs +425 -0
- package/lib/model-router/taxonomy.mjs +893 -0
- package/lib/model-router/taxonomy.test.mjs +410 -0
- package/lib/model-router.mjs +677 -0
- package/lib/model-router.test.mjs +907 -0
- package/lib/org/activity.mjs +211 -0
- package/lib/org/activity.test.mjs +134 -0
- package/lib/org/approvals.mjs +448 -0
- package/lib/org/approvals.test.mjs +216 -0
- package/lib/org/awareness.mjs +222 -0
- package/lib/org/awareness.test.mjs +159 -0
- package/lib/org/board.mjs +229 -0
- package/lib/org/board.test.mjs +177 -0
- package/lib/org/bootstrap-context.mjs +169 -0
- package/lib/org/bootstrap-context.test.mjs +153 -0
- package/lib/org/client.mjs +1628 -0
- package/lib/org/client.test.mjs +1107 -0
- package/lib/org/cohort-client.mjs +67 -0
- package/lib/org/cohort-client.test.mjs +126 -0
- package/lib/org/cost-sync.mjs +227 -0
- package/lib/org/cost-sync.test.mjs +153 -0
- package/lib/org/doctor.mjs +212 -0
- package/lib/org/doctor.test.mjs +212 -0
- package/lib/org/handoff.mjs +293 -0
- package/lib/org/handoff.test.mjs +269 -0
- package/lib/org/integration-tools.mjs +182 -0
- package/lib/org/integration-tools.test.mjs +160 -0
- package/lib/org/keys.mjs +131 -0
- package/lib/org/keys.test.mjs +92 -0
- package/lib/org/knowledge.mjs +463 -0
- package/lib/org/knowledge.test.mjs +319 -0
- package/lib/org/leases.mjs +335 -0
- package/lib/org/leases.test.mjs +235 -0
- package/lib/org/mesh-integration.test.mjs +127 -0
- package/lib/org/mesh.mjs +459 -0
- package/lib/org/mesh.test.mjs +345 -0
- package/lib/org/messaging.mjs +503 -0
- package/lib/org/messaging.test.mjs +238 -0
- package/lib/org/policy.mjs +345 -0
- package/lib/org/policy.test.mjs +237 -0
- package/lib/org/protocol.checksum +1 -0
- package/lib/org/protocol.checksum.test.mjs +90 -0
- package/lib/org/protocol.mjs +967 -0
- package/lib/org/protocol.test.mjs +264 -0
- package/lib/org/registry.mjs +194 -0
- package/lib/org/registry.test.mjs +100 -0
- package/lib/org/tool-surface-integration.test.mjs +120 -0
- package/lib/org/tool-surface.mjs +2535 -0
- package/lib/org/tool-surface.test.mjs +589 -0
- package/lib/org/ui-parity.mjs +3236 -0
- package/lib/org/ui-parity.test.mjs +348 -0
- package/lib/org/verify.mjs +176 -0
- package/lib/org/verify.test.mjs +194 -0
- package/lib/rag/embed.mjs +188 -0
- package/lib/rag/indexer.mjs +425 -0
- package/lib/rag/rag.test.mjs +505 -0
- package/lib/rag/search.mjs +475 -0
- package/lib/rate-guard.mjs +246 -0
- package/lib/rate-guard.test.mjs +201 -0
- package/lib/render.mjs +112 -0
- package/lib/render.test.mjs +68 -0
- package/lib/resource-governor.mjs +297 -0
- package/lib/resource-governor.test.mjs +262 -0
- package/lib/scheduling/dynamic-jobs.mjs +675 -0
- package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
- package/lib/scheduling/jitter.mjs +0 -0
- package/lib/scheduling/jitter.test.mjs +140 -0
- package/lib/secrets/broker.mjs +315 -0
- package/lib/secrets/broker.test.mjs +280 -0
- package/lib/secrets/providers.mjs +461 -0
- package/lib/secrets/providers.test.mjs +274 -0
- package/lib/security/audit-engine.mjs +684 -0
- package/lib/security/audit-engine.test.mjs +389 -0
- package/lib/security/coerce-args.mjs +552 -0
- package/lib/security/coerce-args.test.mjs +281 -0
- package/lib/security/dangerous-tools.mjs +97 -0
- package/lib/security/dangerous-tools.test.mjs +68 -0
- package/lib/security/external-content.mjs +145 -0
- package/lib/security/external-content.test.mjs +67 -0
- package/lib/security/redact.mjs +592 -0
- package/lib/security/redact.test.mjs +441 -0
- package/lib/security/secret-equal.mjs +73 -0
- package/lib/security/secret-equal.test.mjs +55 -0
- package/lib/session-permissions.mjs +101 -0
- package/lib/session-permissions.test.mjs +100 -0
- package/lib/setup/claude-probe.mjs +74 -0
- package/lib/setup/completeness.mjs +175 -0
- package/lib/setup/completeness.test.mjs +110 -0
- package/lib/setup/context-pack.mjs +173 -0
- package/lib/setup/context-pack.test.mjs +89 -0
- package/lib/setup/enrich.mjs +277 -0
- package/lib/setup/enrich.test.mjs +115 -0
- package/lib/setup/enroll-from-cohort.mjs +441 -0
- package/lib/setup/enroll-from-cohort.test.mjs +233 -0
- package/lib/setup/integration.test.mjs +162 -0
- package/lib/setup/io.mjs +360 -0
- package/lib/setup/io.test.mjs +77 -0
- package/lib/setup/run-generator.mjs +81 -0
- package/lib/setup/runner.mjs +244 -0
- package/lib/setup/runner.test.mjs +132 -0
- package/lib/setup/sections/comms.mjs +173 -0
- package/lib/setup/sections/company.mjs +120 -0
- package/lib/setup/sections/enrich.mjs +138 -0
- package/lib/setup/sections/identity.mjs +182 -0
- package/lib/setup/sections/identity.test.mjs +140 -0
- package/lib/setup/sections/learning.mjs +153 -0
- package/lib/setup/sections/learning.test.mjs +81 -0
- package/lib/setup/sections/messaging.mjs +219 -0
- package/lib/setup/sections/messaging.test.mjs +127 -0
- package/lib/setup/sections/model.mjs +102 -0
- package/lib/setup/sections/operating-model.mjs +78 -0
- package/lib/setup/sections/org.mjs +475 -0
- package/lib/setup/sections/org.test.mjs +313 -0
- package/lib/setup/sections/orgmail.mjs +173 -0
- package/lib/setup/sections/orgmail.test.mjs +118 -0
- package/lib/setup/sections/recovery.mjs +159 -0
- package/lib/setup/sections/recovery.test.mjs +98 -0
- package/lib/setup/sections/tools.mjs +132 -0
- package/lib/setup/sections/verify.mjs +97 -0
- package/lib/setup/sot.mjs +205 -0
- package/lib/setup/sot.test.mjs +81 -0
- package/lib/setup/state.mjs +151 -0
- package/lib/setup/state.test.mjs +92 -0
- package/lib/singleton.js +229 -0
- package/lib/singleton.test.mjs +135 -0
- package/lib/telemetry/alerts.mjs +216 -0
- package/lib/telemetry/alerts.test.mjs +109 -0
- package/lib/telemetry/collect.mjs +512 -0
- package/lib/telemetry/collect.test.mjs +202 -0
- package/lib/tool-definitions-integration.test.mjs +83 -0
- package/lib/tool-definitions.js +738 -0
- package/lib/tool-definitions.test.mjs +437 -0
- package/lib/util/fetch-timeout.mjs +136 -0
- package/lib/util/fetch-timeout.test.mjs +202 -0
- package/lib/util/reconnect.mjs +343 -0
- package/lib/util/reconnect.test.mjs +369 -0
- package/lib/util/unhandled.mjs +205 -0
- package/lib/util/unhandled.test.mjs +216 -0
- package/lib/voice/context-loader.mjs +466 -0
- package/lib/voice/index.mjs +100 -0
- package/lib/voice/openai-realtime.mjs +510 -0
- package/lib/voice/outbound.mjs +542 -0
- package/lib/voice/outbound.test.mjs +69 -0
- package/lib/voice/post-call-brief.mjs +428 -0
- package/lib/voice/provider.mjs +52 -0
- package/lib/voice/session-rotation.mjs +257 -0
- package/lib/voice/stt.mjs +161 -0
- package/lib/voice/stt.test.mjs +226 -0
- package/lib/voice/tool-bridge.mjs +370 -0
- package/lib/voice/tts.mjs +104 -0
- package/lib/voice/twilio-sip-bridge.mjs +288 -0
- package/lib/voice/voice.test.mjs +990 -0
- package/mcp/README.md +80 -0
- package/package.json +151 -0
- package/plugins/maestro-skills/plugin.json +139 -0
- package/plugins/maestro-skills/skills/agents-observe.md +110 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/books-close.md +77 -0
- package/plugins/maestro-skills/skills/brand-steward.md +121 -0
- package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
- package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
- package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
- package/plugins/maestro-skills/skills/claude-pace.md +61 -0
- package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
- package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/files-find.md +65 -0
- package/plugins/maestro-skills/skills/generative-ui.md +228 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/mail-triage.md +86 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
- package/plugins/maestro-skills/skills/org-board.md +133 -0
- package/plugins/maestro-skills/skills/org-credential.md +68 -0
- package/plugins/maestro-skills/skills/org-recall.md +81 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/router-why.md +78 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/session-search.md +71 -0
- package/plugins/maestro-skills/skills/set-reminder.md +93 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/team-activity.md +86 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +114 -0
- package/policies/ai-disclosure.yaml +294 -0
- package/policies/communication-style.md +139 -0
- package/policies/information-barriers.yaml +118 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/icon-dark.png +0 -0
- package/public/assets/icon-dark.svg +9 -0
- package/public/assets/icon-light.svg +9 -0
- package/public/assets/logo-dark.svg +15 -0
- package/public/assets/logo-light.svg +15 -0
- package/scaffold/.mcp.json +7 -0
- package/scaffold/CLAUDE.md +368 -0
- package/scaffold/config/agent.json +55 -0
- package/scaffold/config/agent.ts +76 -0
- package/scaffold/config/agent.ts.example +89 -0
- package/scaffold/config/alerts.yaml +23 -0
- package/scaffold/config/allowlist.yaml.example +25 -0
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scaffold/config/collective.yaml +49 -0
- package/scaffold/config/company.json +20 -0
- package/scaffold/config/known-agents.json +6 -0
- package/scaffold/config/learning.yaml +55 -0
- package/scaffold/config/model-routing.yaml.example +104 -0
- package/scaffold/config/org.yaml +25 -0
- package/scaffold/config/orgmail.yaml.example +19 -0
- package/scaffold/config/recovery.yaml +72 -0
- package/scaffold/config/secrets.yaml +27 -0
- package/scaffold/config/slack.yaml.example +35 -0
- package/scaffold/config/telegram.yaml.example +38 -0
- package/scaffold/config/voice.yaml.example +89 -0
- package/scaffold/config/whatsapp.yaml.example +39 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/brand-steward.md +72 -0
- package/schedules/triggers/daily-evening-wrap.md +159 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/directory-hygiene.md +81 -0
- package/schedules/triggers/dynamic-jobs.md +40 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/messaging-inbound.md +50 -0
- package/schedules/triggers/org-pulse.md +24 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +65 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/archive-email.sh +55 -0
- package/scripts/cadence/cadence-status.mjs +36 -0
- package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
- package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
- package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
- package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
- package/scripts/ci/check-docs-accuracy.mjs +493 -0
- package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
- package/scripts/ci/check-exports-exist.mjs +140 -0
- package/scripts/ci/check-files-exist.mjs +107 -0
- package/scripts/ci/check-no-build-artifacts.mjs +111 -0
- package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
- package/scripts/ci/check-no-confidential.mjs +198 -0
- package/scripts/ci/check-no-conflict-markers.mjs +169 -0
- package/scripts/ci/check-no-residual-identity.mjs +163 -0
- package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
- package/scripts/ci/check-tarball-fidelity.mjs +205 -0
- package/scripts/ci/check-unresolved-tokens.mjs +83 -0
- package/scripts/ci/check.mjs +109 -0
- package/scripts/ci/check.test.mjs +194 -0
- package/scripts/ci/run-coverage.mjs +82 -0
- package/scripts/ci/run-tests.mjs +71 -0
- package/scripts/cloud-relay/README.md +59 -0
- package/scripts/cloud-relay/index.mjs +233 -0
- package/scripts/cloud-relay/package.json +15 -0
- package/scripts/cloud-relay/railway.json +13 -0
- package/scripts/cloud-relay/voice/README.md +94 -0
- package/scripts/cloud-relay/voice/package-lock.json +39 -0
- package/scripts/cloud-relay/voice/package.json +16 -0
- package/scripts/cloud-relay/voice/railway.json +13 -0
- package/scripts/cloud-relay/voice/server.mjs +532 -0
- package/scripts/collective/hook-runner.mjs +211 -0
- package/scripts/collective/hook-runner.test.mjs +90 -0
- package/scripts/collective/org-pulse.mjs +72 -0
- package/scripts/collective/org-sync.mjs +61 -0
- package/scripts/collective/recall.mjs +45 -0
- package/scripts/collective/who.mjs +30 -0
- package/scripts/comms-monitor.sh +288 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +91 -0
- package/scripts/cost/fleet-digest.mjs +407 -0
- package/scripts/cost/fleet-digest.test.mjs +207 -0
- package/scripts/cost/track-claude-usage.mjs +169 -0
- package/scripts/daemon/agent-daemon.mjs +989 -0
- package/scripts/daemon/agent-daemon.test.mjs +525 -0
- package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
- package/scripts/daemon/cadence-consumer.mjs +1080 -0
- package/scripts/daemon/cadence-consumer.test.mjs +770 -0
- package/scripts/daemon/cadence-handlers.mjs +1121 -0
- package/scripts/daemon/cadence-handlers.test.mjs +617 -0
- package/scripts/daemon/classifier.mjs +704 -0
- package/scripts/daemon/classifier.test.mjs +238 -0
- package/scripts/daemon/classify-kind.mjs +54 -0
- package/scripts/daemon/classify-kind.test.mjs +40 -0
- package/scripts/daemon/context-compiler.mjs +605 -0
- package/scripts/daemon/context-compiler.test.mjs +300 -0
- package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
- package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
- package/scripts/daemon/dispatcher.mjs +1516 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/inbox-deferral.mjs +210 -0
- package/scripts/daemon/inbox-deferral.test.mjs +242 -0
- package/scripts/daemon/integration.test.mjs +149 -0
- package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
- package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
- package/scripts/daemon/launchd-wrapper.sh +91 -0
- package/scripts/daemon/lib/session-router.mjs +274 -0
- package/scripts/daemon/lib/session-router.test.mjs +295 -0
- package/scripts/daemon/maestro-daemon.mjs +275 -0
- package/scripts/daemon/prompt-builder.mjs +685 -0
- package/scripts/daemon/prompt-builder.test.mjs +213 -0
- package/scripts/daemon/responder.mjs +854 -0
- package/scripts/daemon/session-lock.mjs +721 -0
- package/scripts/daemon/session-lock.test.mjs +252 -0
- package/scripts/daemon/session-outcomes.mjs +640 -0
- package/scripts/daemon/session-outcomes.test.mjs +533 -0
- package/scripts/daemon/typing-registry.mjs +90 -0
- package/scripts/daemon/typing-registry.test.mjs +77 -0
- package/scripts/daemon/voice-webhook-server.mjs +804 -0
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-principal.html +52 -0
- package/scripts/email-signature.html +60 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +362 -0
- package/scripts/emergency-stop.sh +81 -0
- package/scripts/healthcheck.sh +116 -0
- package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +126 -0
- package/scripts/hooks/pre-send-audit.sh +174 -0
- package/scripts/hooks/pre-send-audit.test.mjs +215 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/hooks/session-start-banner.sh +115 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1229 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/learning/consolidate-skills.mjs +72 -0
- package/scripts/learning/session-search.mjs +125 -0
- package/scripts/llm_email_dedup.py +442 -0
- package/scripts/local-triggers/generate-plists.sh +432 -0
- package/scripts/local-triggers/generate-plists.test.mjs +413 -0
- package/scripts/local-triggers/install-all.sh +49 -0
- package/scripts/local-triggers/plists/.gitkeep +0 -0
- package/scripts/local-triggers/run-trigger.sh +63 -0
- package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
- package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
- package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
- package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/maintenance/health-check.sh +377 -0
- package/scripts/media-generation/README.md +105 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/org/send-orgmail.mjs +227 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +477 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +481 -0
- package/scripts/pdf-generation/README.md +63 -0
- package/scripts/pdf-generation/build-document.mjs +247 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +35 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +192 -0
- package/scripts/poller/imap-client.mjs +289 -0
- package/scripts/poller/inbox-scan-poller.mjs +156 -0
- package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +285 -0
- package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
- package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
- package/scripts/poller/secondary-gmail-poller.mjs +132 -0
- package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
- package/scripts/poller/slack-poller.mjs +854 -0
- package/scripts/poller/slack-socket-mode.mjs +917 -0
- package/scripts/poller/slack-socket-mode.test.mjs +753 -0
- package/scripts/poller/trigger.mjs +75 -0
- package/scripts/poller/utils.mjs +371 -0
- package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
- package/scripts/poller/voice-poller.mjs +236 -0
- package/scripts/poller-launchd/install.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +40 -0
- package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag/build-index.mjs +47 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/restore-from-backup.sh +248 -0
- package/scripts/restore-from-backup.test.mjs +178 -0
- package/scripts/resume-operations.sh +80 -0
- package/scripts/search-secondary-inbox.py +181 -0
- package/scripts/secondary-inbox-poller.py +437 -0
- package/scripts/self-optimization/compute-metrics.py +398 -0
- package/scripts/send-email-as-principal.py +369 -0
- package/scripts/send-email-threaded.py +392 -0
- package/scripts/send-email-with-attachment.py +377 -0
- package/scripts/send-email.sh +131 -0
- package/scripts/send-sms.sh +175 -0
- package/scripts/send-whatsapp.sh +292 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/boot-claude-session.sh +94 -0
- package/scripts/setup/configure-macos.sh +674 -0
- package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
- package/scripts/setup/configure-voice-tunnel.mjs +182 -0
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/generate-agent-package-json.mjs +222 -0
- package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
- package/scripts/setup/generate-autonomy.mjs +60 -0
- package/scripts/setup/generate-backlog.mjs +101 -0
- package/scripts/setup/generate-cadences.mjs +92 -0
- package/scripts/setup/generate-capability.mjs +162 -0
- package/scripts/setup/generate-charter.mjs +76 -0
- package/scripts/setup/generate-comms.mjs +59 -0
- package/scripts/setup/generate-company.mjs +92 -0
- package/scripts/setup/init-agent.sh +547 -0
- package/scripts/setup/init-agent.test.mjs +151 -0
- package/scripts/setup/init-archetype.mjs +74 -0
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-channel-bus.mjs +46 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-learning.mjs +70 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-model-router.mjs +124 -0
- package/scripts/setup/init-rag.mjs +174 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/scripts/setup/init-slack-socket-mode.mjs +260 -0
- package/scripts/setup/init-telegram.mjs +165 -0
- package/scripts/setup/init-voice-realtime.mjs +204 -0
- package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
- package/scripts/setup/install-dev-tools.sh +150 -0
- package/scripts/setup/lib/install-plist.mjs +95 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/setup/render-environment-yaml.mjs +133 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +1045 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +232 -0
- package/scripts/slack-send.sh +287 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +450 -0
- package/scripts/spawn-session.sh +120 -0
- package/scripts/sync-protocol.mjs +217 -0
- package/scripts/system-verify.sh +184 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +659 -0
- package/scripts/validate_outbound.py +1504 -0
- package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
- package/scripts/watchdog/force-reboot.sh +157 -0
- package/scripts/watchdog/memory-watchdog.sh +473 -0
- package/scripts/whatsapp-handler.mjs +538 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/backlog-executor.yaml +141 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +105 -0
- package/workflows/daily/morning-brief.yaml +164 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
- package/workflows/event-driven/pr-review.yaml +107 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/engineering-health.yaml +154 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- package/workflows/weekly/strategic-memo.yaml +79 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/security/redact.mjs
|
|
3
|
+
*
|
|
4
|
+
* URL / secret REDACTION and private-IP classification.
|
|
5
|
+
*
|
|
6
|
+
* Ported from OpenClaw's net-policy package
|
|
7
|
+
* (redact-sensitive-url.ts, url-userinfo.ts, ip.ts) and adapted to maestro
|
|
8
|
+
* conventions: pure ESM, JSDoc instead of TypeScript types, zero third-party
|
|
9
|
+
* dependencies (node:url + node:net only). All functions are pure and
|
|
10
|
+
* side-effect free.
|
|
11
|
+
*
|
|
12
|
+
* Two concerns live here:
|
|
13
|
+
* 1. redactSensitiveUrl(url) — make a URL safe to log/echo by stripping
|
|
14
|
+
* embedded credentials (user:pass@) and masking sensitive query params.
|
|
15
|
+
* 2. isPrivateOrReservedIp(host) — classify a host as a private / loopback /
|
|
16
|
+
* link-local / reserved address. Used for SSRF-style guardrails; it must
|
|
17
|
+
* not be foolable by IPv4-mapped-IPv6 representations (e.g. ::ffff:10.0.0.1)
|
|
18
|
+
* that would otherwise bypass a naive IPv4-only check.
|
|
19
|
+
*
|
|
20
|
+
* Faithful to OpenClaw behaviour: malformed input never throws — callers get a
|
|
21
|
+
* best-effort redaction (or a conservative classification) instead of an
|
|
22
|
+
* exception, because this code runs on the logging / security path.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { isIP } from "node:net";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Mask value placeholder used in place of a redacted secret.
|
|
29
|
+
* @type {string}
|
|
30
|
+
*/
|
|
31
|
+
export const REDACTED = "***";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Query-parameter names whose values are treated as secrets and masked.
|
|
35
|
+
* Matching is case-insensitive and substring-based so e.g. `apiKey`,
|
|
36
|
+
* `access_token`, `client_secret`, `X-Api-Key` all match.
|
|
37
|
+
* @type {readonly string[]}
|
|
38
|
+
*/
|
|
39
|
+
export const SENSITIVE_PARAM_KEYS = Object.freeze([
|
|
40
|
+
"token",
|
|
41
|
+
"key",
|
|
42
|
+
"secret",
|
|
43
|
+
"password",
|
|
44
|
+
"passwd",
|
|
45
|
+
"pwd",
|
|
46
|
+
"apikey",
|
|
47
|
+
"api_key",
|
|
48
|
+
"auth",
|
|
49
|
+
"access_token",
|
|
50
|
+
"refresh_token",
|
|
51
|
+
"sig",
|
|
52
|
+
"signature",
|
|
53
|
+
"credential",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* True if a query-parameter name looks sensitive (case-insensitive substring
|
|
58
|
+
* match against {@link SENSITIVE_PARAM_KEYS}).
|
|
59
|
+
*
|
|
60
|
+
* @param {string} name - The parameter name.
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
export function isSensitiveParamName(name) {
|
|
64
|
+
if (typeof name !== "string" || name.length === 0) return false;
|
|
65
|
+
const lower = name.toLowerCase();
|
|
66
|
+
for (const needle of SENSITIVE_PARAM_KEYS) {
|
|
67
|
+
if (lower.includes(needle)) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Strip the `user:password@` userinfo component from a parsed URL in place,
|
|
74
|
+
* returning whether anything was removed. Mirrors OpenClaw's url-userinfo
|
|
75
|
+
* helper: both username and password are cleared so neither leaks.
|
|
76
|
+
*
|
|
77
|
+
* @param {URL} u - A parsed WHATWG URL (mutated).
|
|
78
|
+
* @returns {boolean} True if userinfo was present and removed.
|
|
79
|
+
*/
|
|
80
|
+
function stripUserinfo(u) {
|
|
81
|
+
const had = u.username !== "" || u.password !== "";
|
|
82
|
+
if (had) {
|
|
83
|
+
u.username = "";
|
|
84
|
+
u.password = "";
|
|
85
|
+
}
|
|
86
|
+
return had;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Best-effort regex fallback for inputs the WHATWG URL parser rejects.
|
|
91
|
+
* Removes a `scheme://user:pass@` style userinfo segment and masks obvious
|
|
92
|
+
* `key=value` secret pairs in the query string. Never throws.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} url
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
function redactWithRegex(url) {
|
|
98
|
+
let out = url;
|
|
99
|
+
|
|
100
|
+
// Strip userinfo: matches `://something@` where `something` has no slash.
|
|
101
|
+
// Keeps the scheme and the host that follows the `@`.
|
|
102
|
+
out = out.replace(/(\/\/)[^/@\s]+@/g, "$1");
|
|
103
|
+
|
|
104
|
+
// Mask sensitive query/fragment params of the form key=value. The leading
|
|
105
|
+
// delimiter is `?`, `&`, `#`, or start-of-string (so a bare `token=...`
|
|
106
|
+
// query fragment with no preceding `?` is still caught — slightly hardened
|
|
107
|
+
// vs. OpenClaw's redactSensitiveUrlLikeString, which only matched `[?&]`).
|
|
108
|
+
out = out.replace(
|
|
109
|
+
/(^|[?&#])([^=&#\s]+)=([^&#\s]*)/g,
|
|
110
|
+
(match, sep, rawName, _value) => {
|
|
111
|
+
let name = rawName;
|
|
112
|
+
try {
|
|
113
|
+
name = decodeURIComponent(rawName);
|
|
114
|
+
} catch {
|
|
115
|
+
// keep raw name if it is not valid percent-encoding
|
|
116
|
+
}
|
|
117
|
+
return isSensitiveParamName(name)
|
|
118
|
+
? `${sep}${rawName}=${REDACTED}`
|
|
119
|
+
: match;
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Return a copy of `url` that is safe to log: any embedded `user:pass@`
|
|
128
|
+
* credentials are removed and any sensitive query-parameter values
|
|
129
|
+
* (token / key / secret / password / apikey / etc.) are replaced with `***`.
|
|
130
|
+
*
|
|
131
|
+
* Benign URLs are returned effectively unchanged (the WHATWG parser may
|
|
132
|
+
* normalise trailing details, but no information is added or masked).
|
|
133
|
+
*
|
|
134
|
+
* Never throws — malformed URLs fall back to a regex-based redaction.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} url - The URL to redact.
|
|
137
|
+
* @returns {string} The redacted URL, or the input coerced to string on total
|
|
138
|
+
* failure.
|
|
139
|
+
*/
|
|
140
|
+
export function redactSensitiveUrl(url) {
|
|
141
|
+
if (typeof url !== "string") {
|
|
142
|
+
if (url === null || url === undefined) return "";
|
|
143
|
+
url = String(url);
|
|
144
|
+
}
|
|
145
|
+
if (url.length === 0) return url;
|
|
146
|
+
|
|
147
|
+
let u;
|
|
148
|
+
try {
|
|
149
|
+
u = new URL(url);
|
|
150
|
+
} catch {
|
|
151
|
+
return redactWithRegex(url);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
stripUserinfo(u);
|
|
155
|
+
|
|
156
|
+
// Mask sensitive query params. Iterate over a snapshot of keys because we
|
|
157
|
+
// mutate searchParams while walking it.
|
|
158
|
+
const keys = [...u.searchParams.keys()];
|
|
159
|
+
for (const key of keys) {
|
|
160
|
+
if (isSensitiveParamName(key)) {
|
|
161
|
+
// set() collapses repeated keys to one; rebuild to preserve multiplicity.
|
|
162
|
+
const values = u.searchParams.getAll(key);
|
|
163
|
+
u.searchParams.delete(key);
|
|
164
|
+
for (let i = 0; i < values.length; i++) {
|
|
165
|
+
u.searchParams.append(key, REDACTED);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return u.toString();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ------------------------------------------------------------------------- *
|
|
174
|
+
* Free-text PII / secret masking
|
|
175
|
+
*
|
|
176
|
+
* `maskText` is the general masker the URL/param redactor above is NOT: it walks
|
|
177
|
+
* arbitrary free text (LLM prompts on non-Anthropic routes, log lines, message
|
|
178
|
+
* bodies) and replaces things that look like secrets or PII with a typed
|
|
179
|
+
* placeholder, returning both the cleaned string and a count of substitutions.
|
|
180
|
+
*
|
|
181
|
+
* Design notes (mirrors the Hermes/OpenClaw pattern corpus referenced in
|
|
182
|
+
* gaps-enterprise-ops G19):
|
|
183
|
+
* - High-signal, low-false-positive patterns only. We mask things with a
|
|
184
|
+
* distinctive shape (vendor key prefixes, bearer tokens, email/phone) plus
|
|
185
|
+
* long high-entropy hex/base64 runs; we deliberately do NOT mask ordinary
|
|
186
|
+
* words, numbers, or short identifiers.
|
|
187
|
+
* - Ordering matters: the most specific / longest patterns run first so a
|
|
188
|
+
* bearer token isn't half-eaten by the generic-secret pass, and an email's
|
|
189
|
+
* local part isn't mistaken for a key.
|
|
190
|
+
* - Bounded: every replacement is a fixed-width placeholder, so masked output
|
|
191
|
+
* can never be longer than a small multiple of the input.
|
|
192
|
+
* - Never throws. A masker that crashes on the logging path is worse than one
|
|
193
|
+
* that lets a borderline token through, so all input is coerced and any
|
|
194
|
+
* internal failure falls back to returning the (coerced) input unchanged.
|
|
195
|
+
* ------------------------------------------------------------------------- */
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Ordered masking rules. Each `re` is a global regex; matched spans are replaced
|
|
199
|
+
* by `{label}` and counted. Order is significant — see the note above.
|
|
200
|
+
*
|
|
201
|
+
* @type {ReadonlyArray<{label:string, re:RegExp}>}
|
|
202
|
+
*/
|
|
203
|
+
const MASK_RULES = Object.freeze([
|
|
204
|
+
// Anthropic keys: sk-ant-... (must precede the generic sk- rule).
|
|
205
|
+
{ label: "ANTHROPIC_KEY", re: /\bsk-ant-[A-Za-z0-9_-]{8,}/g },
|
|
206
|
+
// OpenAI-style project / legacy keys: sk-... and sk-proj-...
|
|
207
|
+
{ label: "API_KEY", re: /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}/g },
|
|
208
|
+
// AWS access key id: AKIA / ASIA + 16 uppercase-alphanumerics.
|
|
209
|
+
{ label: "AWS_KEY", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
|
|
210
|
+
// GitHub tokens: ghp_/gho_/ghu_/ghs_/ghr_ + 36+ chars; fine-grained github_pat_.
|
|
211
|
+
{ label: "GITHUB_TOKEN", re: /\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{22,})\b/g },
|
|
212
|
+
// Slack tokens: xoxb-/xoxp-/xoxa-/xoxr-...
|
|
213
|
+
{ label: "SLACK_TOKEN", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/g },
|
|
214
|
+
// Google API keys: AIza + 35 chars.
|
|
215
|
+
{ label: "GOOGLE_KEY", re: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
216
|
+
// Bearer / Basic auth tokens in an Authorization-style position.
|
|
217
|
+
{ label: "BEARER_TOKEN", re: /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi },
|
|
218
|
+
// JWTs: three dot-separated base64url segments, first starts with eyJ.
|
|
219
|
+
{ label: "JWT", re: /\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\b/g },
|
|
220
|
+
// Private-key PEM blocks.
|
|
221
|
+
{ label: "PRIVATE_KEY", re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g },
|
|
222
|
+
// Email addresses.
|
|
223
|
+
{ label: "EMAIL", re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g },
|
|
224
|
+
// E.164-ish / common phone numbers: optional +, 10–15 digits with separators.
|
|
225
|
+
{ label: "PHONE", re: /(?<![\w.])\+?\d[\d\s().-]{8,16}\d(?![\w.])/g },
|
|
226
|
+
// Long hex secrets (32+ hex chars) — token/session/hash material.
|
|
227
|
+
{ label: "HEX_SECRET", re: /\b[0-9a-fA-F]{32,}\b/g },
|
|
228
|
+
// Long high-entropy base64/base64url runs (40+ chars) — generic secrets.
|
|
229
|
+
{ label: "B64_SECRET", re: /\b[A-Za-z0-9+/_-]{40,}={0,2}\b/g },
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Mask PII and secret-shaped tokens in free text.
|
|
234
|
+
*
|
|
235
|
+
* Replaces each recognized span (vendor API keys, bearer/JWT tokens, PEM private
|
|
236
|
+
* keys, emails, phone numbers, long hex/base64 secrets) with a typed placeholder
|
|
237
|
+
* like `[REDACTED:EMAIL]` and reports how many substitutions were made. Benign
|
|
238
|
+
* text — ordinary words, short numbers, identifiers — is returned untouched.
|
|
239
|
+
*
|
|
240
|
+
* Never throws: non-string input is coerced; on any internal failure the coerced
|
|
241
|
+
* input is returned with `count: 0`. Suitable for the logging / model-router
|
|
242
|
+
* path where a crash is unacceptable.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} text - The text to mask.
|
|
245
|
+
* @param {object} [opts]
|
|
246
|
+
* @param {(label:string)=>string} [opts.placeholder] - Custom placeholder
|
|
247
|
+
* factory; defaults to ``[REDACTED:${label}]``.
|
|
248
|
+
* @returns {{text:string, count:number}} The masked text and substitution count.
|
|
249
|
+
*/
|
|
250
|
+
export function maskText(text, opts = {}) {
|
|
251
|
+
if (typeof text !== "string") {
|
|
252
|
+
if (text === null || text === undefined) return { text: "", count: 0 };
|
|
253
|
+
text = String(text);
|
|
254
|
+
}
|
|
255
|
+
if (text.length === 0) return { text, count: 0 };
|
|
256
|
+
|
|
257
|
+
const placeholder =
|
|
258
|
+
opts && typeof opts.placeholder === "function"
|
|
259
|
+
? opts.placeholder
|
|
260
|
+
: (label) => `[${REDACTED_LABEL_PREFIX}${label}]`;
|
|
261
|
+
|
|
262
|
+
let out = text;
|
|
263
|
+
let count = 0;
|
|
264
|
+
try {
|
|
265
|
+
for (const { label, re } of MASK_RULES) {
|
|
266
|
+
// Fresh lastIndex per pass (global regexes are stateful).
|
|
267
|
+
re.lastIndex = 0;
|
|
268
|
+
out = out.replace(re, (match) => {
|
|
269
|
+
count += 1;
|
|
270
|
+
return placeholder(label);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
// Fail-open: never let masking crash the caller.
|
|
275
|
+
return { text, count: 0 };
|
|
276
|
+
}
|
|
277
|
+
return { text: out, count };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Placeholder prefix for {@link maskText}. Kept distinct from {@link REDACTED}
|
|
282
|
+
* (the URL/param mask placeholder) so the two redaction concerns don't collide.
|
|
283
|
+
* @type {string}
|
|
284
|
+
*/
|
|
285
|
+
export const REDACTED_LABEL_PREFIX = "REDACTED:";
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Convenience: mask free text and return only the cleaned string. Drop-in for
|
|
289
|
+
* an `(input:string)=>string` masker hook (e.g. `llmTask`'s `opts.maskInput`).
|
|
290
|
+
*
|
|
291
|
+
* @param {string} text
|
|
292
|
+
* @param {object} [opts] - Forwarded to {@link maskText}.
|
|
293
|
+
* @returns {string}
|
|
294
|
+
*/
|
|
295
|
+
export function maskTextString(text, opts = {}) {
|
|
296
|
+
return maskText(text, opts).text;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Deep-mask an arbitrary value, applying {@link maskText} to every string found
|
|
301
|
+
* (object values, array elements, nested structures). Returns a new value of the
|
|
302
|
+
* same shape with the total substitution count; the input is never mutated.
|
|
303
|
+
*
|
|
304
|
+
* Strings are masked; numbers / booleans / null / undefined pass through; keys
|
|
305
|
+
* are NOT masked (only values) so structure stays inspectable. Cycles are
|
|
306
|
+
* handled (a revisited reference is left as-is). Never throws.
|
|
307
|
+
*
|
|
308
|
+
* @param {*} obj - The value to deep-mask.
|
|
309
|
+
* @param {object} [opts] - Forwarded to {@link maskText}.
|
|
310
|
+
* @returns {{value:*, count:number}}
|
|
311
|
+
*/
|
|
312
|
+
export function redactObject(obj, opts = {}) {
|
|
313
|
+
let count = 0;
|
|
314
|
+
const seen = new WeakSet();
|
|
315
|
+
|
|
316
|
+
const walk = (value) => {
|
|
317
|
+
if (typeof value === "string") {
|
|
318
|
+
const r = maskText(value, opts);
|
|
319
|
+
count += r.count;
|
|
320
|
+
return r.text;
|
|
321
|
+
}
|
|
322
|
+
if (value === null || typeof value !== "object") return value;
|
|
323
|
+
|
|
324
|
+
if (seen.has(value)) return value; // cycle guard
|
|
325
|
+
seen.add(value);
|
|
326
|
+
|
|
327
|
+
if (Array.isArray(value)) {
|
|
328
|
+
return value.map((v) => walk(v));
|
|
329
|
+
}
|
|
330
|
+
// Plain object: walk own enumerable values, preserve keys verbatim.
|
|
331
|
+
const out = {};
|
|
332
|
+
for (const key of Object.keys(value)) {
|
|
333
|
+
out[key] = walk(value[key]);
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const value = walk(obj);
|
|
340
|
+
return { value, count };
|
|
341
|
+
} catch {
|
|
342
|
+
return { value: obj, count };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* ------------------------------------------------------------------------- *
|
|
347
|
+
* IP classification
|
|
348
|
+
* ------------------------------------------------------------------------- */
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Parse a dotted-quad IPv4 string into its four octets.
|
|
352
|
+
*
|
|
353
|
+
* @param {string} ip
|
|
354
|
+
* @returns {number[] | null} `[a,b,c,d]` or null if not a valid IPv4 literal.
|
|
355
|
+
*/
|
|
356
|
+
function parseIPv4(ip) {
|
|
357
|
+
if (isIP(ip) !== 4) return null;
|
|
358
|
+
const parts = ip.split(".");
|
|
359
|
+
if (parts.length !== 4) return null;
|
|
360
|
+
const octets = parts.map((p) => Number(p));
|
|
361
|
+
for (const o of octets) {
|
|
362
|
+
if (!Number.isInteger(o) || o < 0 || o > 255) return null;
|
|
363
|
+
}
|
|
364
|
+
return octets;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Classify an IPv4 address (given as octets) as private / loopback /
|
|
369
|
+
* link-local / reserved / unspecified / broadcast.
|
|
370
|
+
*
|
|
371
|
+
* Covers, per RFC 1918 / 3927 / 5735 / 6598 / 5771 etc.:
|
|
372
|
+
* 0.0.0.0/8 unspecified / "this network"
|
|
373
|
+
* 10.0.0.0/8 private
|
|
374
|
+
* 100.64.0.0/10 CGNAT shared address space
|
|
375
|
+
* 127.0.0.0/8 loopback
|
|
376
|
+
* 169.254.0.0/16 link-local
|
|
377
|
+
* 172.16.0.0/12 private
|
|
378
|
+
* 192.0.0.0/24 IETF protocol assignments
|
|
379
|
+
* 192.0.2.0/24 TEST-NET-1 (documentation)
|
|
380
|
+
* 192.168.0.0/16 private
|
|
381
|
+
* 198.18.0.0/15 benchmarking
|
|
382
|
+
* 198.51.100.0/24 TEST-NET-2
|
|
383
|
+
* 203.0.113.0/24 TEST-NET-3
|
|
384
|
+
* 224.0.0.0/4 multicast (reserved for our purposes)
|
|
385
|
+
* 240.0.0.0/4 reserved (incl. 255.255.255.255 broadcast)
|
|
386
|
+
*
|
|
387
|
+
* @param {number[]} o - Four octets.
|
|
388
|
+
* @returns {boolean}
|
|
389
|
+
*/
|
|
390
|
+
function isPrivateOrReservedIPv4(o) {
|
|
391
|
+
const [a, b] = o;
|
|
392
|
+
|
|
393
|
+
if (a === 0) return true; // 0.0.0.0/8
|
|
394
|
+
if (a === 10) return true; // 10.0.0.0/8
|
|
395
|
+
if (a === 127) return true; // 127.0.0.0/8 loopback
|
|
396
|
+
if (a === 169 && b === 254) return true; // 169.254.0.0/16 link-local
|
|
397
|
+
if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
|
|
398
|
+
if (a === 192 && b === 168) return true; // 192.168.0.0/16
|
|
399
|
+
if (a === 100 && b >= 64 && b <= 127) return true; // 100.64.0.0/10 CGNAT
|
|
400
|
+
if (a === 192 && b === 0 && o[2] === 0) return true; // 192.0.0.0/24
|
|
401
|
+
if (a === 192 && b === 0 && o[2] === 2) return true; // 192.0.2.0/24 TEST-NET-1
|
|
402
|
+
if (a === 198 && (b === 18 || b === 19)) return true; // 198.18.0.0/15 bench
|
|
403
|
+
if (a === 198 && b === 51 && o[2] === 100) return true; // 198.51.100.0/24
|
|
404
|
+
if (a === 203 && b === 0 && o[2] === 113) return true; // 203.0.113.0/24
|
|
405
|
+
if (a >= 224 && a <= 239) return true; // 224.0.0.0/4 multicast
|
|
406
|
+
if (a >= 240) return true; // 240.0.0.0/4 reserved + broadcast
|
|
407
|
+
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Expand an IPv6 literal into an array of 8 16-bit groups (numbers).
|
|
413
|
+
* Handles `::` zero-compression and trailing IPv4-dotted form
|
|
414
|
+
* (e.g. `::ffff:10.0.0.1`). Returns null on anything invalid.
|
|
415
|
+
*
|
|
416
|
+
* @param {string} ip
|
|
417
|
+
* @returns {number[] | null} Eight 16-bit groups, or null.
|
|
418
|
+
*/
|
|
419
|
+
function parseIPv6Groups(ip) {
|
|
420
|
+
if (isIP(ip) !== 6) return null;
|
|
421
|
+
|
|
422
|
+
// Strip a zone id (%eth0) if present — irrelevant to classification.
|
|
423
|
+
const pct = ip.indexOf("%");
|
|
424
|
+
if (pct !== -1) ip = ip.slice(0, pct);
|
|
425
|
+
|
|
426
|
+
let head = ip;
|
|
427
|
+
let tailV4Groups = [];
|
|
428
|
+
|
|
429
|
+
// Embedded IPv4 (last 32 bits written in dotted-quad form).
|
|
430
|
+
const lastColon = head.lastIndexOf(":");
|
|
431
|
+
const maybeV4 = lastColon === -1 ? "" : head.slice(lastColon + 1);
|
|
432
|
+
if (maybeV4.includes(".")) {
|
|
433
|
+
const v4 = parseIPv4(maybeV4);
|
|
434
|
+
if (!v4) return null;
|
|
435
|
+
tailV4Groups = [(v4[0] << 8) | v4[1], (v4[2] << 8) | v4[3]];
|
|
436
|
+
head = head.slice(0, lastColon + 1); // keep trailing ':' for splitting
|
|
437
|
+
// remove the now-dangling segment after the colon
|
|
438
|
+
head = head.slice(0, -1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const doubleColon = head.indexOf("::");
|
|
442
|
+
/** @type {string[]} */
|
|
443
|
+
let leftParts = [];
|
|
444
|
+
/** @type {string[]} */
|
|
445
|
+
let rightParts = [];
|
|
446
|
+
|
|
447
|
+
if (doubleColon !== -1) {
|
|
448
|
+
const left = head.slice(0, doubleColon);
|
|
449
|
+
const right = head.slice(doubleColon + 2);
|
|
450
|
+
leftParts = left.length ? left.split(":") : [];
|
|
451
|
+
rightParts = right.length ? right.split(":") : [];
|
|
452
|
+
} else {
|
|
453
|
+
leftParts = head.length ? head.split(":") : [];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** @type {number[]} */
|
|
457
|
+
const leftGroups = leftParts.map((h) => parseInt(h, 16));
|
|
458
|
+
/** @type {number[]} */
|
|
459
|
+
const rightGroups = rightParts.map((h) => parseInt(h, 16));
|
|
460
|
+
|
|
461
|
+
for (const g of leftGroups) if (!Number.isInteger(g) || g < 0 || g > 0xffff) return null;
|
|
462
|
+
for (const g of rightGroups) if (!Number.isInteger(g) || g < 0 || g > 0xffff) return null;
|
|
463
|
+
|
|
464
|
+
const totalKnown = leftGroups.length + rightGroups.length + tailV4Groups.length;
|
|
465
|
+
if (doubleColon === -1) {
|
|
466
|
+
if (totalKnown !== 8) return null;
|
|
467
|
+
return [...leftGroups, ...tailV4Groups];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const fill = 8 - totalKnown;
|
|
471
|
+
if (fill < 0) return null;
|
|
472
|
+
return [
|
|
473
|
+
...leftGroups,
|
|
474
|
+
...new Array(fill).fill(0),
|
|
475
|
+
...rightGroups,
|
|
476
|
+
...tailV4Groups,
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Classify an IPv6 address (given as 8 groups) as private / loopback /
|
|
482
|
+
* link-local / reserved.
|
|
483
|
+
*
|
|
484
|
+
* Covers:
|
|
485
|
+
* :: unspecified
|
|
486
|
+
* ::1 loopback
|
|
487
|
+
* ::ffff:0:0/96 IPv4-mapped → delegated to the embedded IPv4 check
|
|
488
|
+
* ::/96 (deprecated IPv4-compatible) → embedded IPv4 check
|
|
489
|
+
* 64:ff9b::/96 NAT64 → embedded IPv4 check
|
|
490
|
+
* fc00::/7 unique local (ULA)
|
|
491
|
+
* fe80::/10 link-local
|
|
492
|
+
* fec0::/10 site-local (deprecated, reserved)
|
|
493
|
+
* ff00::/8 multicast
|
|
494
|
+
*
|
|
495
|
+
* @param {number[]} g - Eight 16-bit groups.
|
|
496
|
+
* @returns {boolean}
|
|
497
|
+
*/
|
|
498
|
+
function isPrivateOrReservedIPv6(g) {
|
|
499
|
+
// Unspecified ::
|
|
500
|
+
if (g.every((x) => x === 0)) return true;
|
|
501
|
+
// Loopback ::1
|
|
502
|
+
if (g[0] === 0 && g[1] === 0 && g[2] === 0 && g[3] === 0 && g[4] === 0 && g[5] === 0 && g[6] === 0 && g[7] === 1) {
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const firstSixZero = g[0] === 0 && g[1] === 0 && g[2] === 0 && g[3] === 0 && g[4] === 0;
|
|
507
|
+
|
|
508
|
+
// IPv4-mapped ::ffff:a.b.c.d → classify by the embedded IPv4.
|
|
509
|
+
if (firstSixZero && g[5] === 0xffff) {
|
|
510
|
+
return isPrivateOrReservedIPv4(groupsToV4Octets(g[6], g[7]));
|
|
511
|
+
}
|
|
512
|
+
// IPv4-compatible ::a.b.c.d (deprecated) — also bypass-prone; check embedded.
|
|
513
|
+
if (g[0] === 0 && g[1] === 0 && g[2] === 0 && g[3] === 0 && g[4] === 0 && g[5] === 0 && !(g[6] === 0 && g[7] === 0) && !(g[6] === 0 && g[7] === 1)) {
|
|
514
|
+
return isPrivateOrReservedIPv4(groupsToV4Octets(g[6], g[7]));
|
|
515
|
+
}
|
|
516
|
+
// NAT64 64:ff9b::/96 — embeds an IPv4; classify by it.
|
|
517
|
+
if (g[0] === 0x64 && g[1] === 0xff9b && g[2] === 0 && g[3] === 0 && g[4] === 0 && g[5] === 0) {
|
|
518
|
+
return isPrivateOrReservedIPv4(groupsToV4Octets(g[6], g[7]));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const first = g[0];
|
|
522
|
+
if ((first & 0xfe00) === 0xfc00) return true; // fc00::/7 ULA
|
|
523
|
+
if ((first & 0xffc0) === 0xfe80) return true; // fe80::/10 link-local
|
|
524
|
+
if ((first & 0xffc0) === 0xfec0) return true; // fec0::/10 site-local
|
|
525
|
+
if ((first & 0xff00) === 0xff00) return true; // ff00::/8 multicast
|
|
526
|
+
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Convert two 16-bit groups into four IPv4 octets.
|
|
532
|
+
*
|
|
533
|
+
* @param {number} hi
|
|
534
|
+
* @param {number} lo
|
|
535
|
+
* @returns {number[]}
|
|
536
|
+
*/
|
|
537
|
+
function groupsToV4Octets(hi, lo) {
|
|
538
|
+
return [(hi >> 8) & 0xff, hi & 0xff, (lo >> 8) & 0xff, lo & 0xff];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* True if `host` is a private, loopback, link-local, or otherwise
|
|
543
|
+
* reserved/non-routable IP address.
|
|
544
|
+
*
|
|
545
|
+
* Accepts IPv4 and IPv6 literals, with or without surrounding brackets
|
|
546
|
+
* (`[::1]`) and with an optional IPv6 zone id (`fe80::1%eth0`). Crucially it is
|
|
547
|
+
* NOT fooled by IPv4-mapped-IPv6 forms such as `::ffff:10.0.0.1` or
|
|
548
|
+
* `::ffff:127.0.0.1`, which embed a private/loopback IPv4 inside an IPv6
|
|
549
|
+
* literal — those return true.
|
|
550
|
+
*
|
|
551
|
+
* Hostnames (anything that is not a valid IP literal) return false: this
|
|
552
|
+
* function classifies addresses, not names. Resolve names to addresses first
|
|
553
|
+
* if you need to guard against DNS-based bypasses. Never throws.
|
|
554
|
+
*
|
|
555
|
+
* @param {string} host - An IP literal (IPv4 or IPv6), optionally bracketed.
|
|
556
|
+
* @returns {boolean}
|
|
557
|
+
*/
|
|
558
|
+
export function isPrivateOrReservedIp(host) {
|
|
559
|
+
if (typeof host !== "string") return false;
|
|
560
|
+
|
|
561
|
+
let h = host.trim();
|
|
562
|
+
if (h.length === 0) return false;
|
|
563
|
+
|
|
564
|
+
// Strip surrounding brackets used for IPv6 in URLs: [::1] -> ::1
|
|
565
|
+
if (h.startsWith("[") && h.endsWith("]")) {
|
|
566
|
+
h = h.slice(1, -1);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const kind = isIP(h);
|
|
570
|
+
if (kind === 4) {
|
|
571
|
+
const o = parseIPv4(h);
|
|
572
|
+
return o ? isPrivateOrReservedIPv4(o) : false;
|
|
573
|
+
}
|
|
574
|
+
if (kind === 6) {
|
|
575
|
+
const g = parseIPv6Groups(h);
|
|
576
|
+
return g ? isPrivateOrReservedIPv6(g) : false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return false; // not an IP literal
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export default {
|
|
583
|
+
REDACTED,
|
|
584
|
+
SENSITIVE_PARAM_KEYS,
|
|
585
|
+
isSensitiveParamName,
|
|
586
|
+
redactSensitiveUrl,
|
|
587
|
+
isPrivateOrReservedIp,
|
|
588
|
+
REDACTED_LABEL_PREFIX,
|
|
589
|
+
maskText,
|
|
590
|
+
maskTextString,
|
|
591
|
+
redactObject,
|
|
592
|
+
};
|