From 7cf6bc9f5d6dbcd0a655aa12a02137f39d314233 Mon Sep 17 00:00:00 2001 From: St0rmz1 Date: Thu, 16 Apr 2026 13:12:00 -0700 Subject: [PATCH 1/2] fix(osa) better secret handling and reduces number of restarts --- CHANGELOG.md | 1 + README.md | 5 ----- index.ts | 14 ++++++++++++++ src/auth/token-store.ts | 13 ++++++++++--- src/openclaw-sdk.d.ts | 15 +++++++++++++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f91154a..8f4e611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- First-time device auth no longer triggers a brief gateway restart after the token is captured. The plugin now registers `reload.noopPrefixes` for its own config subtree, so the SecretRef patch written to `openclaw.json` after device auth is classified as a noop by the gateway reload planner instead of falling through to the default `plugins.* → restart` rule. The security checkup report is returned in the same response with no connection interruption. - Release workflow: consolidated post-publish git/GitHub operations into a single atomic step with retries, eliminating a race condition where the version bump commit and tag could be pushed separately. Registry verification is now informational-only and never blocks tag/release steps. - Release workflow: added a `Reconcile latest dist-tag` step that automatically repoints `npm dist-tags.latest` back to the highest stable version after a dev publish, preventing npm's first-publish auto-assign behavior from routing plain `npm install` users to a prerelease. diff --git a/README.md b/README.md index cd71d43..1cf21cd 100644 --- a/README.md +++ b/README.md @@ -125,11 +125,6 @@ connection. Then run `/security-checkup` again. The plugin will pick up the approval, persist your auth token, run the checkup, and return the report in the same response. -> **Note:** after the token is saved, OpenClaw briefly reloads to apply -> the new credential. You'll see a short connection blip in the chat -> UI. This is expected and only happens on first auth. Subsequent -> checkups run instantly without any reload. - For every run after the first, no auth prompt appears. The saved token is reused automatically. diff --git a/index.ts b/index.ts index c29b38a..81ff5ed 100644 --- a/index.ts +++ b/index.ts @@ -294,6 +294,20 @@ export default definePluginEntry({ name: "OpenClaw Security Advisor", description: "Run a security checkup of your OpenClaw instance and get an expert analysis report from KiloCode.", + // The gateway reload planner classifies any change under `plugins.*` + // as `kind: "restart"` by default. writeStoredToken() patches + // plugins.entries.openclaw-security-advisor.config.authToken with a + // SecretRef after device auth, which would force a full gateway + // restart on first-time token capture. Plugin-registered reload + // rules are evaluated before the base rules (first-match wins), so + // declaring our own config subtree as a noop shadows the base + // restart rule for this plugin without affecting anything else. + // The plugin reads the token directly via readTokenFromFile(), so + // there's no need for a hot-resolve of api.pluginConfig.authToken — + // noop is sufficient. + reload: { + noopPrefixes: ["plugins.entries.openclaw-security-advisor.config"], + }, // The SDK's OpenClawPluginApi type is large and internal. We narrow // to our own structural PluginApi (declared above) immediately on // entry so everything inside this function is strongly typed. diff --git a/src/auth/token-store.ts b/src/auth/token-store.ts index c055955..3cb3631 100644 --- a/src/auth/token-store.ts +++ b/src/auth/token-store.ts @@ -53,9 +53,16 @@ async function ensureSecretsDir(): Promise { * 2. Register a file-based SecretRef provider in config * 3. Point the plugin authToken config at that provider * - * This triggers one gateway restart. On restart, OpenClaw resolves the - * SecretRef → api.pluginConfig.authToken = the token string, available - * in the plugin closure forever after. + * The config write does NOT trigger a gateway restart: the plugin + * declares `reload.noopPrefixes` for `plugins.entries..config` in + * index.ts, which shadows the gateway reload planner's default + * `plugins.* → restart` rule for our own config subtree. The plugin + * reads the token directly from the secrets file via + * readTokenFromFile() on every invocation, so no hot-resolve of + * api.pluginConfig.authToken is needed — the SecretRef in + * openclaw.json exists for discoverability (so operators inspecting + * config can see where the token lives) and to align with openclaw's + * SecretRef direction. */ export async function writeStoredToken( api: TokenStoreApi, diff --git a/src/openclaw-sdk.d.ts b/src/openclaw-sdk.d.ts index 6c2c8ea..b1af34c 100644 --- a/src/openclaw-sdk.d.ts +++ b/src/openclaw-sdk.d.ts @@ -14,6 +14,20 @@ */ declare module "openclaw/plugin-sdk/plugin-entry" { + /** + * Subset of the SDK's OpenClawPluginReloadRegistration. Entries here let a + * plugin override the gateway reload planner's default classification for + * specific config prefixes. First-match wins, and plugin-registered rules + * are evaluated before the base `plugins.* -> restart` rule, so declaring + * `plugins.entries..config` here overrides the base restart for our + * own config subtree. + */ + export type PluginReloadRegistration = { + restartPrefixes?: string[]; + hotPrefixes?: string[]; + noopPrefixes?: string[]; + }; + /** * Register a plugin with the OpenClaw runtime. The `register` callback * receives a runtime-provided plugin API object. The SDK's concrete @@ -24,6 +38,7 @@ declare module "openclaw/plugin-sdk/plugin-entry" { id: string; name: string; description: string; + reload?: PluginReloadRegistration; register: (api: any) => void; }): unknown; } From 465611b26c3d7892dbc88f5359b1b41403567cda Mon Sep 17 00:00:00 2001 From: St0rmz1 Date: Thu, 16 Apr 2026 13:22:48 -0700 Subject: [PATCH 2/2] fixes --- CHANGELOG.md | 2 +- index.ts | 18 ++++++++++++------ src/auth/token-store.ts | 14 ++++++++------ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4e611..7d5e9c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- First-time device auth no longer triggers a brief gateway restart after the token is captured. The plugin now registers `reload.noopPrefixes` for its own config subtree, so the SecretRef patch written to `openclaw.json` after device auth is classified as a noop by the gateway reload planner instead of falling through to the default `plugins.* → restart` rule. The security checkup report is returned in the same response with no connection interruption. +- First-time device auth no longer triggers a brief gateway restart after the token is captured. The plugin now registers `reload.noopPrefixes` for `plugins.entries.openclaw-security-advisor.config.authToken`, so the SecretRef patch written to `openclaw.json` after device auth is classified as a noop by the gateway reload planner instead of falling through to the default `plugins.* → restart` rule. The security checkup report is returned in the same response with no connection interruption. Scope is intentionally limited to the `authToken` field — `apiBaseUrl` and other config changes still take effect via the normal restart path. - Release workflow: consolidated post-publish git/GitHub operations into a single atomic step with retries, eliminating a race condition where the version bump commit and tag could be pushed separately. Registry verification is now informational-only and never blocks tag/release steps. - Release workflow: added a `Reconcile latest dist-tag` step that automatically repoints `npm dist-tags.latest` back to the highest stable version after a dev publish, preventing npm's first-publish auto-assign behavior from routing plain `npm install` users to a prerelease. diff --git a/index.ts b/index.ts index 81ff5ed..6299974 100644 --- a/index.ts +++ b/index.ts @@ -300,13 +300,19 @@ export default definePluginEntry({ // SecretRef after device auth, which would force a full gateway // restart on first-time token capture. Plugin-registered reload // rules are evaluated before the base rules (first-match wins), so - // declaring our own config subtree as a noop shadows the base - // restart rule for this plugin without affecting anything else. - // The plugin reads the token directly via readTokenFromFile(), so - // there's no need for a hot-resolve of api.pluginConfig.authToken — - // noop is sufficient. + // declaring just the authToken path as a noop shadows the base + // restart rule for that one field without affecting anything else. + // + // Scope is intentionally narrow — only `.config.authToken`, NOT the + // full `.config` subtree. `apiBaseUrl` is captured as a snapshot in + // register() (see `pluginConfig` below), so runtime updates to it + // still need to fall through to the base `plugins.* → restart` rule + // to take effect. The plugin reads the token directly from disk via + // readTokenFromFile() on every invocation, so authToken noop is safe. reload: { - noopPrefixes: ["plugins.entries.openclaw-security-advisor.config"], + noopPrefixes: [ + "plugins.entries.openclaw-security-advisor.config.authToken", + ], }, // The SDK's OpenClawPluginApi type is large and internal. We narrow // to our own structural PluginApi (declared above) immediately on diff --git a/src/auth/token-store.ts b/src/auth/token-store.ts index 3cb3631..cf58f89 100644 --- a/src/auth/token-store.ts +++ b/src/auth/token-store.ts @@ -54,12 +54,14 @@ async function ensureSecretsDir(): Promise { * 3. Point the plugin authToken config at that provider * * The config write does NOT trigger a gateway restart: the plugin - * declares `reload.noopPrefixes` for `plugins.entries..config` in - * index.ts, which shadows the gateway reload planner's default - * `plugins.* → restart` rule for our own config subtree. The plugin - * reads the token directly from the secrets file via - * readTokenFromFile() on every invocation, so no hot-resolve of - * api.pluginConfig.authToken is needed — the SecretRef in + * declares `reload.noopPrefixes` for + * `plugins.entries..config.authToken` in index.ts, which shadows + * the gateway reload planner's default `plugins.* → restart` rule for + * just that one field. Other `.config.*` fields (e.g. `apiBaseUrl`) + * intentionally still hit the default restart rule so runtime edits + * take effect. The plugin reads the token directly from the secrets + * file via readTokenFromFile() on every invocation, so no hot-resolve + * of api.pluginConfig.authToken is needed — the SecretRef in * openclaw.json exists for discoverability (so operators inspecting * config can see where the token lives) and to align with openclaw's * SecretRef direction.