@reefclaw/openclaw-plugin 0.1.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/.gitignore +8 -0
- package/audit/mode-transition-audit.d.ts +11 -0
- package/audit/mode-transition-audit.js +29 -0
- package/balance-utils.d.ts +36 -0
- package/balance-utils.js +98 -0
- package/bridge/bridge.d.ts +109 -0
- package/bridge/bridge.js +1036 -0
- package/bridge/config.d.ts +43 -0
- package/bridge/config.js +176 -0
- package/bridge/connector.d.ts +52 -0
- package/bridge/connector.js +293 -0
- package/bridge/event-replay-buffer.d.ts +43 -0
- package/bridge/event-replay-buffer.js +109 -0
- package/bridge/gateway/event-parser.d.ts +209 -0
- package/bridge/gateway/event-parser.js +793 -0
- package/bridge/gateway/gateway-config.d.ts +39 -0
- package/bridge/gateway/gateway-config.js +86 -0
- package/bridge/gateway/gateway-http-client.d.ts +50 -0
- package/bridge/gateway/gateway-http-client.js +165 -0
- package/bridge/gateway/gateway-ws-client.d.ts +116 -0
- package/bridge/gateway/gateway-ws-client.js +417 -0
- package/bridge/gateway/poller.d.ts +146 -0
- package/bridge/gateway/poller.js +505 -0
- package/bridge/gateway/tool-discovery.d.ts +25 -0
- package/bridge/gateway/tool-discovery.js +198 -0
- package/bridge/index.d.ts +2 -0
- package/bridge/index.js +253 -0
- package/bridge/logger.d.ts +2 -0
- package/bridge/logger.js +2 -0
- package/bridge/provider.d.ts +156 -0
- package/bridge/provider.js +2 -0
- package/bridge/providers/emergency-commands.d.ts +54 -0
- package/bridge/providers/emergency-commands.js +235 -0
- package/bridge/providers/gateway.d.ts +322 -0
- package/bridge/providers/gateway.js +2302 -0
- package/bridge/providers/mock.d.ts +37 -0
- package/bridge/providers/mock.js +385 -0
- package/bridge/providers/onboarding-commands.d.ts +83 -0
- package/bridge/providers/onboarding-commands.js +213 -0
- package/bridge/providers/risk-calculator.d.ts +96 -0
- package/bridge/providers/risk-calculator.js +369 -0
- package/bridge/setup.d.ts +32 -0
- package/bridge/setup.js +226 -0
- package/bridge/types.d.ts +584 -0
- package/bridge/types.js +50 -0
- package/bridge/utils/reconnect.d.ts +6 -0
- package/bridge/utils/reconnect.js +6 -0
- package/bridge/utils/skill-signing.d.ts +51 -0
- package/bridge/utils/skill-signing.js +138 -0
- package/bridge/utils/skill-version.d.ts +17 -0
- package/bridge/utils/skill-version.js +74 -0
- package/ccxt/binance-ban-gate.d.ts +47 -0
- package/ccxt/binance-ban-gate.js +409 -0
- package/ccxt/binance-private.d.ts +325 -0
- package/ccxt/binance-private.js +1415 -0
- package/ccxt/binance-public.d.ts +18 -0
- package/ccxt/binance-public.js +147 -0
- package/config/agent-config-client.d.ts +55 -0
- package/config/agent-config-client.js +145 -0
- package/config/agent-config-poller.d.ts +25 -0
- package/config/agent-config-poller.js +100 -0
- package/config/brackets-config.d.ts +22 -0
- package/config/brackets-config.js +58 -0
- package/config/gate-store.d.ts +18 -0
- package/config/gate-store.js +61 -0
- package/config/plugin-config-io.d.ts +181 -0
- package/config/plugin-config-io.js +84 -0
- package/config/position-review-config.d.ts +35 -0
- package/config/position-review-config.js +105 -0
- package/config/tool-gate.d.ts +53 -0
- package/config/tool-gate.js +125 -0
- package/config/user-data-stream-config.d.ts +85 -0
- package/config/user-data-stream-config.js +224 -0
- package/connector-supervisor.d.ts +36 -0
- package/connector-supervisor.js +149 -0
- package/exchange-adapter.d.ts +49 -0
- package/exchange-adapter.js +4 -0
- package/index.d.ts +30 -0
- package/index.js +2030 -0
- package/ingest/pending-entry-metadata.d.ts +52 -0
- package/ingest/pending-entry-metadata.js +182 -0
- package/ingest/position-auto-capture.d.ts +98 -0
- package/ingest/position-auto-capture.js +394 -0
- package/ingest/position-decisions-client.d.ts +318 -0
- package/ingest/position-decisions-client.js +296 -0
- package/ingest/reconcile-db-vs-exchange.d.ts +13 -0
- package/ingest/reconcile-db-vs-exchange.js +114 -0
- package/ingest/reconciler-cleanup.d.ts +37 -0
- package/ingest/reconciler-cleanup.js +147 -0
- package/ingest/rest-gap-filler.d.ts +191 -0
- package/ingest/rest-gap-filler.js +565 -0
- package/ingest/touched-symbols-store.d.ts +25 -0
- package/ingest/touched-symbols-store.js +96 -0
- package/ingest/trade-store-client.d.ts +40 -0
- package/ingest/trade-store-client.js +125 -0
- package/ingest/ws-ingest.d.ts +43 -0
- package/ingest/ws-ingest.js +126 -0
- package/learning/setup-family.d.ts +21 -0
- package/learning/setup-family.js +103 -0
- package/lifecycle/install-signal-handlers.d.ts +33 -0
- package/lifecycle/install-signal-handlers.js +112 -0
- package/lifecycle/shutdown-coordinator.d.ts +43 -0
- package/lifecycle/shutdown-coordinator.js +131 -0
- package/live/bracket-id.d.ts +18 -0
- package/live/bracket-id.js +81 -0
- package/live/bracket-ledger.d.ts +54 -0
- package/live/bracket-ledger.js +267 -0
- package/live/bracket-manager.d.ts +82 -0
- package/live/bracket-manager.js +478 -0
- package/live/bracket-params.d.ts +22 -0
- package/live/bracket-params.js +124 -0
- package/live/bracket-reconciler.d.ts +95 -0
- package/live/bracket-reconciler.js +573 -0
- package/live/bracket-types.d.ts +102 -0
- package/live/bracket-types.js +8 -0
- package/live/deposit-tracker.d.ts +62 -0
- package/live/deposit-tracker.js +97 -0
- package/live/emergency-controls.d.ts +32 -0
- package/live/emergency-controls.js +226 -0
- package/live/exchange-errors.d.ts +12 -0
- package/live/exchange-errors.js +130 -0
- package/live/exchange-info-cache.d.ts +35 -0
- package/live/exchange-info-cache.js +119 -0
- package/live/fact-subscriber.d.ts +78 -0
- package/live/fact-subscriber.js +182 -0
- package/live/intent-journal.d.ts +42 -0
- package/live/intent-journal.js +122 -0
- package/live/listen-key-manager.d.ts +70 -0
- package/live/listen-key-manager.js +169 -0
- package/live/live-adapter.d.ts +264 -0
- package/live/live-adapter.js +1665 -0
- package/live/live-balance-enricher.d.ts +32 -0
- package/live/live-balance-enricher.js +104 -0
- package/live/live-bracket-api.d.ts +13 -0
- package/live/live-bracket-api.js +20 -0
- package/live/live-state-store.d.ts +194 -0
- package/live/live-state-store.js +450 -0
- package/live/local-signal-service.d.ts +57 -0
- package/live/local-signal-service.js +146 -0
- package/live/local-strategy-evaluator.d.ts +62 -0
- package/live/local-strategy-evaluator.js +127 -0
- package/live/microstructure-assembler.d.ts +54 -0
- package/live/microstructure-assembler.js +148 -0
- package/live/order-poller.d.ts +29 -0
- package/live/order-poller.js +125 -0
- package/live/position-state-store.d.ts +83 -0
- package/live/position-state-store.js +237 -0
- package/live/proposal-decision-listener.d.ts +64 -0
- package/live/proposal-decision-listener.js +288 -0
- package/live/proposal-manager.d.ts +76 -0
- package/live/proposal-manager.js +140 -0
- package/live/rate-limiter.d.ts +47 -0
- package/live/rate-limiter.js +159 -0
- package/live/reconciler.d.ts +39 -0
- package/live/reconciler.js +175 -0
- package/live/setup-buckets.d.ts +7 -0
- package/live/setup-buckets.js +33 -0
- package/live/slippage-tracker.d.ts +45 -0
- package/live/slippage-tracker.js +78 -0
- package/live/stop-watcher.d.ts +34 -0
- package/live/stop-watcher.js +158 -0
- package/live/user-data-active-probe.d.ts +54 -0
- package/live/user-data-active-probe.js +180 -0
- package/live/user-data-stream-controller.d.ts +200 -0
- package/live/user-data-stream-controller.js +579 -0
- package/live/user-data-stream-ws.d.ts +22 -0
- package/live/user-data-stream-ws.js +63 -0
- package/live/user-data-stream.d.ts +243 -0
- package/live/user-data-stream.js +704 -0
- package/logger.d.ts +2 -0
- package/logger.js +2 -0
- package/mfe.d.ts +21 -0
- package/mfe.js +68 -0
- package/onboarding/mode-ladder.d.ts +1 -0
- package/onboarding/mode-ladder.js +3 -0
- package/onboarding/runtime.d.ts +71 -0
- package/onboarding/runtime.js +153 -0
- package/openclaw.plugin.json +94 -0
- package/package.json +27 -0
- package/paper-adapter.d.ts +24 -0
- package/paper-adapter.js +91 -0
- package/persistence/state-manager.d.ts +42 -0
- package/persistence/state-manager.js +164 -0
- package/pinned-plan.d.ts +9 -0
- package/pinned-plan.js +23 -0
- package/risk/pre-trade-check.d.ts +38 -0
- package/risk/pre-trade-check.js +345 -0
- package/risk/pre-trade-types.d.ts +60 -0
- package/risk/pre-trade-types.js +3 -0
- package/scripts/assemble.mjs +114 -0
- package/shadow/shadow-tracker.d.ts +36 -0
- package/shadow/shadow-tracker.js +151 -0
- package/shadow/types.d.ts +42 -0
- package/shadow/types.js +20 -0
- package/shared/indicators-extended.d.ts +52 -0
- package/shared/indicators-extended.js +291 -0
- package/shared/indicators.d.ts +15 -0
- package/shared/indicators.js +114 -0
- package/signals/conditions/registry.d.ts +16 -0
- package/signals/conditions/registry.js +1274 -0
- package/signals/conditions/types.d.ts +1 -0
- package/signals/conditions/types.js +4 -0
- package/signals/direction-rules.d.ts +3 -0
- package/signals/direction-rules.js +24 -0
- package/signals/entry-rules.d.ts +6 -0
- package/signals/entry-rules.js +33 -0
- package/signals/serialize-context.d.ts +4 -0
- package/signals/serialize-context.js +39 -0
- package/signals/stop-rules.d.ts +3 -0
- package/signals/stop-rules.js +48 -0
- package/signals/strategy-adapter.d.ts +14 -0
- package/signals/strategy-adapter.js +122 -0
- package/signals/types.d.ts +1 -0
- package/signals/types.js +8 -0
- package/simulator/exchange-simulator.d.ts +93 -0
- package/simulator/exchange-simulator.js +684 -0
- package/simulator/fill-engine.d.ts +53 -0
- package/simulator/fill-engine.js +276 -0
- package/simulator/paper-market-feed.d.ts +26 -0
- package/simulator/paper-market-feed.js +104 -0
- package/simulator/realistic-fills.d.ts +59 -0
- package/simulator/realistic-fills.js +175 -0
- package/simulator/types.d.ts +219 -0
- package/simulator/types.js +43 -0
- package/skills/reefclaw/SKILL.md +97 -0
- package/strategy/builtin-strategies.d.ts +2 -0
- package/strategy/builtin-strategies.js +109 -0
- package/strategy/condition-registry.d.ts +3 -0
- package/strategy/condition-registry.js +153 -0
- package/strategy/evaluator.d.ts +67 -0
- package/strategy/evaluator.js +93 -0
- package/tools/assessment-validation.d.ts +118 -0
- package/tools/assessment-validation.js +415 -0
- package/tools/attach-brackets.d.ts +34 -0
- package/tools/attach-brackets.js +363 -0
- package/tools/audit-bracket-protection.d.ts +49 -0
- package/tools/audit-bracket-protection.js +527 -0
- package/tools/cancel-all-orders.d.ts +7 -0
- package/tools/cancel-all-orders.js +5 -0
- package/tools/cancel-order.d.ts +10 -0
- package/tools/cancel-order.js +14 -0
- package/tools/check-position-health.d.ts +46 -0
- package/tools/check-position-health.js +194 -0
- package/tools/clear-exchange-credentials.d.ts +24 -0
- package/tools/clear-exchange-credentials.js +70 -0
- package/tools/close-position.d.ts +22 -0
- package/tools/close-position.js +449 -0
- package/tools/create-order.d.ts +54 -0
- package/tools/create-order.js +338 -0
- package/tools/exit-gate.d.ts +58 -0
- package/tools/exit-gate.js +162 -0
- package/tools/fetch-balance.d.ts +5 -0
- package/tools/fetch-balance.js +4 -0
- package/tools/fetch-ohlcv.d.ts +11 -0
- package/tools/fetch-ohlcv.js +8 -0
- package/tools/fetch-open-orders.d.ts +7 -0
- package/tools/fetch-open-orders.js +4 -0
- package/tools/fetch-positions.d.ts +7 -0
- package/tools/fetch-positions.js +4 -0
- package/tools/fetch-ticker.d.ts +11 -0
- package/tools/fetch-ticker.js +5 -0
- package/tools/get-agent-profile.d.ts +4 -0
- package/tools/get-agent-profile.js +6 -0
- package/tools/get-analytics.d.ts +6 -0
- package/tools/get-analytics.js +7 -0
- package/tools/get-backtest.d.ts +12 -0
- package/tools/get-backtest.js +91 -0
- package/tools/get-basis.d.ts +7 -0
- package/tools/get-basis.js +7 -0
- package/tools/get-bracket-config.d.ts +11 -0
- package/tools/get-bracket-config.js +24 -0
- package/tools/get-cascade-risk.d.ts +7 -0
- package/tools/get-cascade-risk.js +8 -0
- package/tools/get-crypto-metrics.d.ts +18 -0
- package/tools/get-crypto-metrics.js +45 -0
- package/tools/get-cvd.d.ts +6 -0
- package/tools/get-cvd.js +6 -0
- package/tools/get-divergences.d.ts +6 -0
- package/tools/get-divergences.js +6 -0
- package/tools/get-funding-context.d.ts +6 -0
- package/tools/get-funding-context.js +16 -0
- package/tools/get-liquidation-levels.d.ts +7 -0
- package/tools/get-liquidation-levels.js +7 -0
- package/tools/get-liquidation-pulse.d.ts +9 -0
- package/tools/get-liquidation-pulse.js +22 -0
- package/tools/get-market-breadth.d.ts +6 -0
- package/tools/get-market-breadth.js +8 -0
- package/tools/get-market-intel.d.ts +19 -0
- package/tools/get-market-intel.js +116 -0
- package/tools/get-market-structure.d.ts +47 -0
- package/tools/get-market-structure.js +198 -0
- package/tools/get-my-mined-patterns.d.ts +20 -0
- package/tools/get-my-mined-patterns.js +61 -0
- package/tools/get-my-proposed-learnings.d.ts +20 -0
- package/tools/get-my-proposed-learnings.js +55 -0
- package/tools/get-my-recent-reviews.d.ts +22 -0
- package/tools/get-my-recent-reviews.js +66 -0
- package/tools/get-orderbook.d.ts +21 -0
- package/tools/get-orderbook.js +32 -0
- package/tools/get-pattern-scan.d.ts +7 -0
- package/tools/get-pattern-scan.js +8 -0
- package/tools/get-regime.d.ts +6 -0
- package/tools/get-regime.js +7 -0
- package/tools/get-relevant-learnings.d.ts +21 -0
- package/tools/get-relevant-learnings.js +65 -0
- package/tools/get-resting-liquidity.d.ts +6 -0
- package/tools/get-resting-liquidity.js +11 -0
- package/tools/get-risk-scenario.d.ts +29 -0
- package/tools/get-risk-scenario.js +47 -0
- package/tools/get-risk-summary.d.ts +51 -0
- package/tools/get-risk-summary.js +118 -0
- package/tools/get-sentiment.d.ts +4 -0
- package/tools/get-sentiment.js +6 -0
- package/tools/get-session-review.d.ts +7 -0
- package/tools/get-session-review.js +8 -0
- package/tools/get-setup-detail.d.ts +7 -0
- package/tools/get-setup-detail.js +303 -0
- package/tools/get-signals.d.ts +15 -0
- package/tools/get-signals.js +54 -0
- package/tools/get-sizing.d.ts +6 -0
- package/tools/get-sizing.js +6 -0
- package/tools/get-trade-feedback.d.ts +7 -0
- package/tools/get-trade-feedback.js +8 -0
- package/tools/get-trade-flow.d.ts +7 -0
- package/tools/get-trade-flow.js +7 -0
- package/tools/get-volume-analysis.d.ts +21 -0
- package/tools/get-volume-analysis.js +74 -0
- package/tools/get-volume-profile.d.ts +7 -0
- package/tools/get-volume-profile.js +7 -0
- package/tools/helpers.d.ts +25 -0
- package/tools/helpers.js +38 -0
- package/tools/intel-api.d.ts +14 -0
- package/tools/intel-api.js +52 -0
- package/tools/intel-cache.d.ts +25 -0
- package/tools/intel-cache.js +133 -0
- package/tools/list-strategies.d.ts +7 -0
- package/tools/list-strategies.js +6 -0
- package/tools/modify-stop.d.ts +17 -0
- package/tools/modify-stop.js +81 -0
- package/tools/modify-target.d.ts +17 -0
- package/tools/modify-target.js +71 -0
- package/tools/propose-learning.d.ts +22 -0
- package/tools/propose-learning.js +65 -0
- package/tools/query-review-outcomes.d.ts +30 -0
- package/tools/query-review-outcomes.js +64 -0
- package/tools/query-trades.d.ts +21 -0
- package/tools/query-trades.js +37 -0
- package/tools/record-position-reviews.d.ts +38 -0
- package/tools/record-position-reviews.js +147 -0
- package/tools/save-strategy.d.ts +16 -0
- package/tools/save-strategy.js +46 -0
- package/tools/scan-pairs.d.ts +18 -0
- package/tools/scan-pairs.js +220 -0
- package/tools/score-setup.d.ts +31 -0
- package/tools/score-setup.js +268 -0
- package/tools/set-bracket-requirement.d.ts +18 -0
- package/tools/set-bracket-requirement.js +81 -0
- package/tools/set-exchange-credentials.d.ts +25 -0
- package/tools/set-exchange-credentials.js +80 -0
- package/tools/set-trading-mode.d.ts +26 -0
- package/tools/set-trading-mode.js +135 -0
- package/tools/test-exchange-credentials.d.ts +16 -0
- package/tools/test-exchange-credentials.js +100 -0
- package/tools/toggle-strategy.d.ts +8 -0
- package/tools/toggle-strategy.js +8 -0
- package/trading-params-cache.d.ts +26 -0
- package/trading-params-cache.js +52 -0
- package/types.d.ts +110 -0
- package/types.js +7 -0
- package/util/plugin-paths.d.ts +3 -0
- package/util/plugin-paths.js +15 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Ed25519 signature verification for OTA SKILL.md updates.
|
|
2
|
+
//
|
|
3
|
+
// Security finding C1 (docs/PRE_LAUNCH_SECURITY_HARDENING.md §2.5 + Appendix B):
|
|
4
|
+
// the relay is treated as a DUMB TRANSPORT. A pushed/pulled SKILL.md is applied
|
|
5
|
+
// only if it carries a valid signature from the operator's offline private key,
|
|
6
|
+
// whose PUBLIC key is pinned here in the build. A leaked ADMIN_SECRET, a
|
|
7
|
+
// compromised relay process, or a tampered Durable-Object value therefore
|
|
8
|
+
// CANNOT rewrite an agent's trading instructions — they all fail verification.
|
|
9
|
+
//
|
|
10
|
+
// Key provisioning (one-time, operator, OFFLINE):
|
|
11
|
+
// 1. python scripts/gen-skill-signing-key.py
|
|
12
|
+
// 2. paste the printed PUBLIC key (base64) into TRUSTED_SKILL_SIGNING_KEYS below
|
|
13
|
+
// 3. put the printed PRIVATE key into .env.deploy as SKILL_SIGNING_PRIVATE_KEY
|
|
14
|
+
// (gitignored — keep it offline; NEVER on the relay or any server)
|
|
15
|
+
// 4. rebuild + redeploy the skill (deploy-skillmd-ota.py is already wired to sign)
|
|
16
|
+
//
|
|
17
|
+
// Until a key is pinned, OTA is FAIL-CLOSED (every push rejected) while
|
|
18
|
+
// SKILL_OTA_REQUIRE_SIGNATURE is on (the default). Normal trading is unaffected
|
|
19
|
+
// — only the OTA-update path gates on this.
|
|
20
|
+
import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
21
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import { parseSkillVersion } from './skill-version.js';
|
|
25
|
+
/**
|
|
26
|
+
* Pinned Ed25519 PUBLIC keys (base64 of the raw 32-byte key) trusted to sign OTA
|
|
27
|
+
* SKILL.md updates. Multiple entries enable key rotation (accept current + next,
|
|
28
|
+
* retire the old after every client has updated). EMPTY until the operator pins
|
|
29
|
+
* one — see the header for provisioning.
|
|
30
|
+
*/
|
|
31
|
+
export const TRUSTED_SKILL_SIGNING_KEYS = [
|
|
32
|
+
// 'Base64OfRaw32BytePublicKey=' ← paste from scripts/gen-skill-signing-key.py
|
|
33
|
+
];
|
|
34
|
+
const OTA_STATE_PATH = join(homedir(), '.openclaw', 'workspace', '.skill-ota-state.json');
|
|
35
|
+
/** Lowercase hex SHA-256 of the UTF-8 content. MUST match the Python signer. */
|
|
36
|
+
export function sha256Hex(content) {
|
|
37
|
+
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
38
|
+
}
|
|
39
|
+
/** Canonical signed message — MUST match deploy-skillmd-ota.py byte-for-byte. */
|
|
40
|
+
export function signedMessage(version, contentSha256, signedAt) {
|
|
41
|
+
return `${version}\n${contentSha256}\n${signedAt}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Is OTA signature verification enforced?
|
|
45
|
+
*
|
|
46
|
+
* Default **OFF** while C1 (signed OTA) is ON HOLD — see
|
|
47
|
+
* docs/PRE_LAUNCH_SECURITY_HARDENING.md §2.5. Keeping it off by default makes
|
|
48
|
+
* this whole module DORMANT, so deploying skill/relay for UNRELATED reasons can
|
|
49
|
+
* never break the existing (unsigned) OTA flow — no env flag to remember.
|
|
50
|
+
*
|
|
51
|
+
* To ENABLE signed-OTA (when the agent-update mechanism is finalized): pin a
|
|
52
|
+
* public key in TRUSTED_SKILL_SIGNING_KEYS **and** set
|
|
53
|
+
* SKILL_OTA_REQUIRE_SIGNATURE=on. Both are required — a pinned key alone does
|
|
54
|
+
* nothing without the flag, and the flag alone fails closed (no key → reject).
|
|
55
|
+
*/
|
|
56
|
+
export function signatureRequired() {
|
|
57
|
+
return (process.env.SKILL_OTA_REQUIRE_SIGNATURE ?? 'off').toLowerCase() === 'on';
|
|
58
|
+
}
|
|
59
|
+
function publicKeyFromRawB64(b64) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = Buffer.from(b64, 'base64');
|
|
62
|
+
if (raw.length !== 32)
|
|
63
|
+
return null; // raw Ed25519 public key is exactly 32 bytes
|
|
64
|
+
return createPublicKey({
|
|
65
|
+
format: 'jwk',
|
|
66
|
+
key: { kty: 'OKP', crv: 'Ed25519', x: raw.toString('base64url') },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Verify a signed OTA envelope against the pinned (or supplied) trusted keys.
|
|
75
|
+
* Checks: required fields present, content hash matches (no tampering), the
|
|
76
|
+
* frontmatter version matches the signed version (no number-bump replay), and
|
|
77
|
+
* the Ed25519 signature verifies under some trusted key. Pure — does NOT touch
|
|
78
|
+
* disk or the replay clock; the caller enforces monotonic signedAt.
|
|
79
|
+
*/
|
|
80
|
+
export function verifySkillSignature(env, opts) {
|
|
81
|
+
const keys = opts?.trustedKeys ?? TRUSTED_SKILL_SIGNING_KEYS;
|
|
82
|
+
if (keys.length === 0) {
|
|
83
|
+
return { ok: false, reason: 'no trusted signing keys configured — pin a public key in skill-signing.ts (see scripts/gen-skill-signing-key.py)' };
|
|
84
|
+
}
|
|
85
|
+
if (!env.signature || !env.signedAt || !env.contentSha256) {
|
|
86
|
+
return { ok: false, reason: 'missing signature/signedAt/contentSha256 (unsigned push)' };
|
|
87
|
+
}
|
|
88
|
+
if (sha256Hex(env.content) !== env.contentSha256) {
|
|
89
|
+
return { ok: false, reason: 'content hash mismatch (tampered content)' };
|
|
90
|
+
}
|
|
91
|
+
const frontmatterVersion = parseSkillVersion(env.content);
|
|
92
|
+
if (frontmatterVersion !== env.version) {
|
|
93
|
+
return { ok: false, reason: `version mismatch (envelope ${env.version} vs frontmatter ${frontmatterVersion ?? 'none'})` };
|
|
94
|
+
}
|
|
95
|
+
const msg = Buffer.from(signedMessage(env.version, env.contentSha256, env.signedAt), 'utf8');
|
|
96
|
+
let sig;
|
|
97
|
+
try {
|
|
98
|
+
sig = Buffer.from(env.signature, 'base64');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return { ok: false, reason: 'signature is not valid base64' };
|
|
102
|
+
}
|
|
103
|
+
for (const keyB64 of keys) {
|
|
104
|
+
const pub = publicKeyFromRawB64(keyB64);
|
|
105
|
+
if (!pub)
|
|
106
|
+
continue;
|
|
107
|
+
try {
|
|
108
|
+
if (cryptoVerify(null, msg, pub, sig))
|
|
109
|
+
return { ok: true };
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* malformed key/sig for this candidate — try the next */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { ok: false, reason: 'signature does not verify under any trusted key' };
|
|
116
|
+
}
|
|
117
|
+
/** Last applied signedAt as epoch ms, 0 if none/unreadable (replay clock). */
|
|
118
|
+
export function readLastAppliedSignedAtMs() {
|
|
119
|
+
try {
|
|
120
|
+
const raw = readFileSync(OTA_STATE_PATH, 'utf-8');
|
|
121
|
+
const t = JSON.parse(raw).lastAppliedSignedAt;
|
|
122
|
+
const ms = t ? new Date(t).getTime() : 0;
|
|
123
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Persist the signedAt of the just-applied update (monotonic replay clock). */
|
|
130
|
+
export function recordAppliedSignedAt(signedAt) {
|
|
131
|
+
try {
|
|
132
|
+
mkdirSync(join(homedir(), '.openclaw', 'workspace'), { recursive: true });
|
|
133
|
+
writeFileSync(OTA_STATE_PATH, JSON.stringify({ lastAppliedSignedAt: signedAt }), 'utf-8');
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* best-effort — if this fails the replay window is bounded to one process life */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Extract version from SKILL.md frontmatter */
|
|
2
|
+
export declare function parseSkillVersion(content: string): string | null;
|
|
3
|
+
/** Read current SKILL.md version from disk */
|
|
4
|
+
export declare function readLocalSkillVersion(): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Compare two semver strings.
|
|
7
|
+
* Returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
8
|
+
* Non-semver inputs (or null) are treated as -Infinity so a valid semver always
|
|
9
|
+
* wins, which is the safe direction for "should I accept this OTA push?".
|
|
10
|
+
*/
|
|
11
|
+
export declare function compareSemver(a: string | null, b: string | null): number;
|
|
12
|
+
/** Validate SKILL.md content is well-formed */
|
|
13
|
+
export declare function validateSkillContent(content: string): {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
version: string | null;
|
|
16
|
+
error?: string;
|
|
17
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SKILL.md version parsing utility for OTA updates
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
const SKILL_MD_PATH = join(homedir(), '.openclaw', 'workspace', 'SKILL.md');
|
|
6
|
+
/** Extract version from SKILL.md frontmatter */
|
|
7
|
+
export function parseSkillVersion(content) {
|
|
8
|
+
const match = content.match(/^---[\s\S]*?version:\s*(\S+)[\s\S]*?---/);
|
|
9
|
+
return match ? match[1] : null;
|
|
10
|
+
}
|
|
11
|
+
/** Read current SKILL.md version from disk */
|
|
12
|
+
export function readLocalSkillVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(SKILL_MD_PATH, 'utf-8');
|
|
15
|
+
return parseSkillVersion(content);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// 120KB — the full SKILL.md crossed 100KB at v2.20.0 (103.7KB), which silently
|
|
22
|
+
// broke every OTA apply against the old 100KB cap. Keep comfortably under the
|
|
23
|
+
// 128KiB Cloudflare Durable-Object per-value hard limit the relay stores into.
|
|
24
|
+
const MAX_SKILL_CONTENT_SIZE = 120_000;
|
|
25
|
+
const SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
|
|
26
|
+
/**
|
|
27
|
+
* Compare two semver strings.
|
|
28
|
+
* Returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
29
|
+
* Non-semver inputs (or null) are treated as -Infinity so a valid semver always
|
|
30
|
+
* wins, which is the safe direction for "should I accept this OTA push?".
|
|
31
|
+
*/
|
|
32
|
+
export function compareSemver(a, b) {
|
|
33
|
+
const parse = (v) => {
|
|
34
|
+
if (!v || !SEMVER_REGEX.test(v))
|
|
35
|
+
return null;
|
|
36
|
+
const [maj, min, pat] = v.split('.').map(Number);
|
|
37
|
+
return [maj, min, pat];
|
|
38
|
+
};
|
|
39
|
+
const pa = parse(a);
|
|
40
|
+
const pb = parse(b);
|
|
41
|
+
if (!pa && !pb)
|
|
42
|
+
return 0;
|
|
43
|
+
if (!pa)
|
|
44
|
+
return -1;
|
|
45
|
+
if (!pb)
|
|
46
|
+
return 1;
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
if (pa[i] > pb[i])
|
|
49
|
+
return 1;
|
|
50
|
+
if (pa[i] < pb[i])
|
|
51
|
+
return -1;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
/** Validate SKILL.md content is well-formed */
|
|
56
|
+
export function validateSkillContent(content) {
|
|
57
|
+
if (content.length > MAX_SKILL_CONTENT_SIZE) {
|
|
58
|
+
return { valid: false, version: null, error: `Content too large (${content.length} bytes, max ${MAX_SKILL_CONTENT_SIZE})` };
|
|
59
|
+
}
|
|
60
|
+
if (!content.startsWith('---')) {
|
|
61
|
+
return { valid: false, version: null, error: 'Missing frontmatter delimiter' };
|
|
62
|
+
}
|
|
63
|
+
const version = parseSkillVersion(content);
|
|
64
|
+
if (!version) {
|
|
65
|
+
return { valid: false, version: null, error: 'No version in frontmatter' };
|
|
66
|
+
}
|
|
67
|
+
if (!SEMVER_REGEX.test(version)) {
|
|
68
|
+
return { valid: false, version, error: `Invalid version format: ${version} (expected semver x.y.z)` };
|
|
69
|
+
}
|
|
70
|
+
if (content.length < 100) {
|
|
71
|
+
return { valid: false, version, error: 'Content suspiciously short' };
|
|
72
|
+
}
|
|
73
|
+
return { valid: true, version };
|
|
74
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Thrown by `assertNotBanned` while the gate is closed. Carries the
|
|
2
|
+
* remaining cooldown so callers/telemetry can reason about it. Methods
|
|
3
|
+
* catch this in their existing catch block — it is NOT meant to escape
|
|
4
|
+
* the API layer with special handling; it degrades to the method's normal
|
|
5
|
+
* failure contract. */
|
|
6
|
+
export declare class BinanceBannedError extends Error {
|
|
7
|
+
readonly retryAfterMs: number;
|
|
8
|
+
constructor(retryAfterMs: number, context: string);
|
|
9
|
+
}
|
|
10
|
+
/** Thrown by the proactive weight pacer. Subclass of BinanceBannedError so
|
|
11
|
+
* every `instanceof BinanceBannedError` check and every method's existing
|
|
12
|
+
* catch keep working unchanged — it degrades to the same null/[]/throw
|
|
13
|
+
* contract (treated exactly like a transient fetch failure). The distinct
|
|
14
|
+
* name lets journal/telemetry tell prevention apart from a real IP ban. */
|
|
15
|
+
export declare class BinanceWeightPacedError extends BinanceBannedError {
|
|
16
|
+
constructor(retryAfterMs: number, context: string);
|
|
17
|
+
}
|
|
18
|
+
/** Fast pre-flight check. Call as the first line inside a Binance call's
|
|
19
|
+
* try-block. Throws `BinanceBannedError` (no network) while the gate is
|
|
20
|
+
* open. */
|
|
21
|
+
export declare function assertNotBanned(context: string): void;
|
|
22
|
+
/** Inspect a failed call's error and arm/extend the gate on a Binance
|
|
23
|
+
* rate-limit signal. Non-rate-limit errors are ignored (normal transient
|
|
24
|
+
* handling stays in the caller). Safe to call with any error shape. */
|
|
25
|
+
export declare function noteBinanceError(err: unknown): void;
|
|
26
|
+
/** Relax the soft-429 ladder after a clean call. (A hard 418 window is NOT
|
|
27
|
+
* shortened by this — only Binance's stated expiry clears it.) */
|
|
28
|
+
export declare function noteSuccess(): void;
|
|
29
|
+
/** Reconcile the local weight estimate against Binance truth from a response
|
|
30
|
+
* header (`x-mbx-used-weight-1m`). Best-effort: callers pass whatever header
|
|
31
|
+
* bag they have; absent/garbage values are ignored. The local estimate
|
|
32
|
+
* leads (counts in-flight calls Binance hasn't tallied yet), so we take the
|
|
33
|
+
* MAX within the window — never let a lagging header lower our guard. A
|
|
34
|
+
* header far BELOW the estimate means Binance's minute rolled; re-anchor the
|
|
35
|
+
* window to Binance's clock so we don't shed for a stale local total. */
|
|
36
|
+
export declare function noteResponseWeight(headers: Record<string, string> | undefined | null): void;
|
|
37
|
+
/** Test/diagnostic helper — current gate state. */
|
|
38
|
+
export declare function getBanGateState(): {
|
|
39
|
+
banUntilMs: number;
|
|
40
|
+
remainingMs: number;
|
|
41
|
+
rateLimitStep: number;
|
|
42
|
+
weightUsed: number;
|
|
43
|
+
weightCeiling: number;
|
|
44
|
+
cosmeticCeiling: number;
|
|
45
|
+
};
|
|
46
|
+
/** Test-only reset. Not used in production code paths. */
|
|
47
|
+
export declare function __resetBanGateForTests(): void;
|