mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix(secrets): restore source-mode contract loading
This commit is contained in:
4
extensions/bluebubbles/contract-api.ts
Normal file
4
extensions/bluebubbles/contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
collectRuntimeConfigAssignments,
|
||||
secretTargetRegistryEntries,
|
||||
} from "./src/secret-contract.js";
|
||||
4
extensions/irc/contract-api.ts
Normal file
4
extensions/irc/contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
collectRuntimeConfigAssignments,
|
||||
secretTargetRegistryEntries,
|
||||
} from "./src/secret-contract.js";
|
||||
4
extensions/msteams/contract-api.ts
Normal file
4
extensions/msteams/contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
collectRuntimeConfigAssignments,
|
||||
secretTargetRegistryEntries,
|
||||
} from "./src/secret-contract.js";
|
||||
4
extensions/nextcloud-talk/contract-api.ts
Normal file
4
extensions/nextcloud-talk/contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
collectRuntimeConfigAssignments,
|
||||
secretTargetRegistryEntries,
|
||||
} from "./src/secret-contract.js";
|
||||
4
extensions/slack/secret-contract-api.ts
Normal file
4
extensions/slack/secret-contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
collectRuntimeConfigAssignments,
|
||||
secretTargetRegistryEntries,
|
||||
} from "./src/secret-contract.js";
|
||||
@@ -1,17 +1,3 @@
|
||||
type UnsupportedSecretRefConfigCandidate = {
|
||||
path: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
export const unsupportedSecretRefSurfacePatterns = [
|
||||
"channels.whatsapp.creds.json",
|
||||
"channels.whatsapp.accounts.*.creds.json",
|
||||
] as const;
|
||||
|
||||
import { whatsappCommandPolicy as whatsappCommandPolicyImpl } from "./src/command-policy.js";
|
||||
import { resolveLegacyGroupSessionKey as resolveLegacyGroupSessionKeyImpl } from "./src/group-session-contract.js";
|
||||
import { __testing as whatsappAccessControlTestingImpl } from "./src/inbound/access-control.js";
|
||||
@@ -28,6 +14,10 @@ import {
|
||||
canonicalizeLegacySessionKey as canonicalizeLegacySessionKeyImpl,
|
||||
isLegacyGroupSessionKey as isLegacyGroupSessionKeyImpl,
|
||||
} from "./src/session-contract.js";
|
||||
export {
|
||||
collectUnsupportedSecretRefConfigCandidates,
|
||||
unsupportedSecretRefSurfacePatterns,
|
||||
} from "./src/security-contract.js";
|
||||
|
||||
export const canonicalizeLegacySessionKey = canonicalizeLegacySessionKeyImpl;
|
||||
export const createWhatsAppPollFixture = createWhatsAppPollFixtureImpl;
|
||||
@@ -39,39 +29,3 @@ export const resolveLegacyGroupSessionKey = resolveLegacyGroupSessionKeyImpl;
|
||||
export const resolveWhatsAppRuntimeGroupPolicy = resolveWhatsAppRuntimeGroupPolicyImpl;
|
||||
export const whatsappAccessControlTesting = whatsappAccessControlTestingImpl;
|
||||
export const whatsappCommandPolicy = whatsappCommandPolicyImpl;
|
||||
|
||||
export function collectUnsupportedSecretRefConfigCandidates(
|
||||
raw: unknown,
|
||||
): UnsupportedSecretRefConfigCandidate[] {
|
||||
if (!isRecord(raw)) {
|
||||
return [];
|
||||
}
|
||||
if (!isRecord(raw.channels) || !isRecord(raw.channels.whatsapp)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const candidates: UnsupportedSecretRefConfigCandidate[] = [];
|
||||
const whatsapp = raw.channels.whatsapp;
|
||||
const creds = isRecord(whatsapp.creds) ? whatsapp.creds : null;
|
||||
if (creds) {
|
||||
candidates.push({
|
||||
path: "channels.whatsapp.creds.json",
|
||||
value: creds.json,
|
||||
});
|
||||
}
|
||||
|
||||
const accounts = isRecord(whatsapp.accounts) ? whatsapp.accounts : null;
|
||||
if (!accounts) {
|
||||
return candidates;
|
||||
}
|
||||
for (const [accountId, account] of Object.entries(accounts)) {
|
||||
if (!isRecord(account) || !isRecord(account.creds)) {
|
||||
continue;
|
||||
}
|
||||
candidates.push({
|
||||
path: `channels.whatsapp.accounts.${accountId}.creds.json`,
|
||||
value: account.creds.json,
|
||||
});
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
4
extensions/whatsapp/security-contract-api.ts
Normal file
4
extensions/whatsapp/security-contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
collectUnsupportedSecretRefConfigCandidates,
|
||||
unsupportedSecretRefSurfacePatterns,
|
||||
} from "./src/security-contract.js";
|
||||
49
extensions/whatsapp/src/security-contract.ts
Normal file
49
extensions/whatsapp/src/security-contract.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
type UnsupportedSecretRefConfigCandidate = {
|
||||
path: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
export const unsupportedSecretRefSurfacePatterns = [
|
||||
"channels.whatsapp.creds.json",
|
||||
"channels.whatsapp.accounts.*.creds.json",
|
||||
] as const;
|
||||
|
||||
export function collectUnsupportedSecretRefConfigCandidates(
|
||||
raw: unknown,
|
||||
): UnsupportedSecretRefConfigCandidate[] {
|
||||
if (!isRecord(raw)) {
|
||||
return [];
|
||||
}
|
||||
if (!isRecord(raw.channels) || !isRecord(raw.channels.whatsapp)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const candidates: UnsupportedSecretRefConfigCandidate[] = [];
|
||||
const whatsapp = raw.channels.whatsapp;
|
||||
const creds = isRecord(whatsapp.creds) ? whatsapp.creds : null;
|
||||
if (creds) {
|
||||
candidates.push({
|
||||
path: "channels.whatsapp.creds.json",
|
||||
value: creds.json,
|
||||
});
|
||||
}
|
||||
|
||||
const accounts = isRecord(whatsapp.accounts) ? whatsapp.accounts : null;
|
||||
if (!accounts) {
|
||||
return candidates;
|
||||
}
|
||||
for (const [accountId, account] of Object.entries(accounts)) {
|
||||
if (!isRecord(account) || !isRecord(account.creds)) {
|
||||
continue;
|
||||
}
|
||||
candidates.push({
|
||||
path: `channels.whatsapp.accounts.${accountId}.creds.json`,
|
||||
value: account.creds.json,
|
||||
});
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
82
src/secrets/channel-contract-api.ts
Normal file
82
src/secrets/channel-contract-api.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "../plugin-sdk/facade-runtime.js";
|
||||
import { listBundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js";
|
||||
import type { ResolverContext, SecretDefaults } from "./runtime-shared.js";
|
||||
import type { SecretTargetRegistryEntry } from "./target-registry-types.js";
|
||||
|
||||
type UnsupportedSecretRefConfigCandidate = {
|
||||
path: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
type BundledChannelContractApi = {
|
||||
collectRuntimeConfigAssignments?: (params: {
|
||||
config: OpenClawConfig;
|
||||
defaults: SecretDefaults | undefined;
|
||||
context: ResolverContext;
|
||||
}) => void;
|
||||
secretTargetRegistryEntries?: readonly SecretTargetRegistryEntry[];
|
||||
unsupportedSecretRefSurfacePatterns?: readonly string[];
|
||||
collectUnsupportedSecretRefConfigCandidates?: (
|
||||
raw: Record<string, unknown>,
|
||||
) => UnsupportedSecretRefConfigCandidate[];
|
||||
};
|
||||
|
||||
function loadBundledChannelPublicArtifact(
|
||||
channelId: string,
|
||||
artifactBasenames: readonly string[],
|
||||
): BundledChannelContractApi | undefined {
|
||||
const metadata = listBundledPluginMetadata({
|
||||
includeChannelConfigs: false,
|
||||
includeSyntheticChannelConfigs: false,
|
||||
}).find((entry) => entry.manifest.channels?.includes(channelId));
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const artifactBasename of artifactBasenames) {
|
||||
if (!metadata.publicSurfaceArtifacts?.includes(artifactBasename)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<BundledChannelContractApi>({
|
||||
dirName: metadata.dirName,
|
||||
artifactBasename,
|
||||
});
|
||||
} catch (error) {
|
||||
if (process.env.OPENCLAW_DEBUG_CHANNEL_CONTRACT_API === "1") {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
process.stderr.write(
|
||||
`[channel-contract-api] failed to load ${channelId} via ${metadata.dirName}/${artifactBasename}: ${detail}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export type BundledChannelSecretContractApi = Pick<
|
||||
BundledChannelContractApi,
|
||||
"collectRuntimeConfigAssignments" | "secretTargetRegistryEntries"
|
||||
>;
|
||||
|
||||
export function loadBundledChannelSecretContractApi(
|
||||
channelId: string,
|
||||
): BundledChannelSecretContractApi | undefined {
|
||||
return loadBundledChannelPublicArtifact(channelId, ["secret-contract-api.js", "contract-api.js"]);
|
||||
}
|
||||
|
||||
export type BundledChannelSecurityContractApi = Pick<
|
||||
BundledChannelContractApi,
|
||||
"unsupportedSecretRefSurfacePatterns" | "collectUnsupportedSecretRefConfigCandidates"
|
||||
>;
|
||||
|
||||
export function loadBundledChannelSecurityContractApi(
|
||||
channelId: string,
|
||||
): BundledChannelSecurityContractApi | undefined {
|
||||
return loadBundledChannelPublicArtifact(channelId, [
|
||||
"security-contract-api.js",
|
||||
"contract-api.js",
|
||||
]);
|
||||
}
|
||||
73
src/secrets/runtime-config-collectors-channels.test.ts
Normal file
73
src/secrets/runtime-config-collectors-channels.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ResolverContext } from "./runtime-shared.js";
|
||||
|
||||
const getBootstrapChannelSecrets = vi.fn();
|
||||
const loadBundledChannelSecretContractApi = vi.fn();
|
||||
|
||||
vi.mock("../channels/plugins/bootstrap-registry.js", () => ({
|
||||
getBootstrapChannelSecrets,
|
||||
}));
|
||||
|
||||
vi.mock("./channel-contract-api.js", () => ({
|
||||
loadBundledChannelSecretContractApi,
|
||||
}));
|
||||
|
||||
describe("runtime channel config collectors", () => {
|
||||
beforeEach(() => {
|
||||
getBootstrapChannelSecrets.mockReset();
|
||||
loadBundledChannelSecretContractApi.mockReset();
|
||||
});
|
||||
|
||||
it("uses the bundled channel contract-api collector when bootstrap secrets are unavailable", async () => {
|
||||
const { collectChannelConfigAssignments } =
|
||||
await import("./runtime-config-collectors-channels.js");
|
||||
const collectRuntimeConfigAssignments = vi.fn();
|
||||
loadBundledChannelSecretContractApi.mockReturnValue({
|
||||
collectRuntimeConfigAssignments,
|
||||
});
|
||||
getBootstrapChannelSecrets.mockReturnValue(undefined);
|
||||
|
||||
collectChannelConfigAssignments({
|
||||
config: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
accounts: {
|
||||
ops: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
defaults: undefined,
|
||||
context: {} as ResolverContext,
|
||||
});
|
||||
|
||||
expect(loadBundledChannelSecretContractApi).toHaveBeenCalledWith("bluebubbles");
|
||||
expect(collectRuntimeConfigAssignments).toHaveBeenCalledOnce();
|
||||
expect(getBootstrapChannelSecrets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to bootstrap secrets when no channel contract-api is published", async () => {
|
||||
const { collectChannelConfigAssignments } =
|
||||
await import("./runtime-config-collectors-channels.js");
|
||||
const collectRuntimeConfigAssignments = vi.fn();
|
||||
loadBundledChannelSecretContractApi.mockReturnValue(undefined);
|
||||
getBootstrapChannelSecrets.mockReturnValue({
|
||||
collectRuntimeConfigAssignments,
|
||||
});
|
||||
|
||||
collectChannelConfigAssignments({
|
||||
config: {
|
||||
channels: {
|
||||
legacy: {},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
defaults: undefined,
|
||||
context: {} as ResolverContext,
|
||||
});
|
||||
|
||||
expect(loadBundledChannelSecretContractApi).toHaveBeenCalledWith("legacy");
|
||||
expect(getBootstrapChannelSecrets).toHaveBeenCalledWith("legacy");
|
||||
expect(collectRuntimeConfigAssignments).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getBootstrapChannelSecrets } from "../channels/plugins/bootstrap-registry.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadBundledChannelSecretContractApi } from "./channel-contract-api.js";
|
||||
import { type ResolverContext, type SecretDefaults } from "./runtime-shared.js";
|
||||
|
||||
export function collectChannelConfigAssignments(params: {
|
||||
@@ -12,10 +13,10 @@ export function collectChannelConfigAssignments(params: {
|
||||
return;
|
||||
}
|
||||
for (const channelId of channelIds) {
|
||||
const secrets = getBootstrapChannelSecrets(channelId);
|
||||
if (!secrets) {
|
||||
continue;
|
||||
}
|
||||
secrets.collectRuntimeConfigAssignments?.(params);
|
||||
const contract = loadBundledChannelSecretContractApi(channelId);
|
||||
const collectRuntimeConfigAssignments =
|
||||
contract?.collectRuntimeConfigAssignments ??
|
||||
getBootstrapChannelSecrets(channelId)?.collectRuntimeConfigAssignments;
|
||||
collectRuntimeConfigAssignments?.(params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,13 @@ const COVERAGE_REGISTRY_ENTRIES = loadCoverageRegistryEntries();
|
||||
const DEBUG_COVERAGE_BATCHES = process.env.OPENCLAW_DEBUG_RUNTIME_COVERAGE === "1";
|
||||
|
||||
let applyResolvedAssignments: typeof import("./runtime-shared.js").applyResolvedAssignments;
|
||||
let clearBootstrapChannelPluginCache: typeof import("../channels/plugins/bootstrap-registry.js").clearBootstrapChannelPluginCache;
|
||||
let clearBundledPluginMetadataCache: typeof import("../plugins/bundled-plugin-metadata.js").clearBundledPluginMetadataCache;
|
||||
let clearPluginManifestRegistryCache: typeof import("../plugins/manifest-registry.js").clearPluginManifestRegistryCache;
|
||||
let collectAuthStoreAssignments: typeof import("./runtime-auth-collectors.js").collectAuthStoreAssignments;
|
||||
let collectConfigAssignments: typeof import("./runtime-config-collectors.js").collectConfigAssignments;
|
||||
let createResolverContext: typeof import("./runtime-shared.js").createResolverContext;
|
||||
let resetFacadeRuntimeStateForTest: typeof import("../plugin-sdk/facade-runtime.js").resetFacadeRuntimeStateForTest;
|
||||
let resolveSecretRefValues: typeof import("./resolve.js").resolveSecretRefValues;
|
||||
let resolveRuntimeWebTools: typeof import("./runtime-web-tools.js").resolveRuntimeWebTools;
|
||||
|
||||
@@ -134,7 +138,11 @@ function resolveCoverageBatchKey(entry: SecretRegistryEntry): string {
|
||||
if (
|
||||
field === "accessToken" ||
|
||||
field === "password" ||
|
||||
(channelId === "slack" && field === "signingSecret")
|
||||
(channelId === "slack" &&
|
||||
(field === "appToken" ||
|
||||
field === "botToken" ||
|
||||
field === "signingSecret" ||
|
||||
field === "userToken"))
|
||||
) {
|
||||
return entry.id;
|
||||
}
|
||||
@@ -419,15 +427,31 @@ async function prepareCoverageSnapshot(params: {
|
||||
|
||||
describe("secrets runtime target coverage", () => {
|
||||
beforeAll(async () => {
|
||||
const [sharedRuntime, authCollectors, configCollectors, resolver, webTools] = await Promise.all(
|
||||
[
|
||||
import("./runtime-shared.js"),
|
||||
import("./runtime-auth-collectors.js"),
|
||||
import("./runtime-config-collectors.js"),
|
||||
import("./resolve.js"),
|
||||
import("./runtime-web-tools.js"),
|
||||
],
|
||||
);
|
||||
const [
|
||||
bootstrapRegistry,
|
||||
facadeRuntime,
|
||||
bundledPluginMetadata,
|
||||
manifestRegistry,
|
||||
sharedRuntime,
|
||||
authCollectors,
|
||||
configCollectors,
|
||||
resolver,
|
||||
webTools,
|
||||
] = await Promise.all([
|
||||
import("../channels/plugins/bootstrap-registry.js"),
|
||||
import("../plugin-sdk/facade-runtime.js"),
|
||||
import("../plugins/bundled-plugin-metadata.js"),
|
||||
import("../plugins/manifest-registry.js"),
|
||||
import("./runtime-shared.js"),
|
||||
import("./runtime-auth-collectors.js"),
|
||||
import("./runtime-config-collectors.js"),
|
||||
import("./resolve.js"),
|
||||
import("./runtime-web-tools.js"),
|
||||
]);
|
||||
({ clearBootstrapChannelPluginCache } = bootstrapRegistry);
|
||||
({ resetFacadeRuntimeStateForTest } = facadeRuntime);
|
||||
({ clearBundledPluginMetadataCache } = bundledPluginMetadata);
|
||||
({ clearPluginManifestRegistryCache } = manifestRegistry);
|
||||
({ applyResolvedAssignments, createResolverContext } = sharedRuntime);
|
||||
({ collectAuthStoreAssignments } = authCollectors);
|
||||
({ collectConfigAssignments } = configCollectors);
|
||||
@@ -440,6 +464,10 @@ describe("secrets runtime target coverage", () => {
|
||||
(entry) => entry.configFile === "openclaw.json",
|
||||
);
|
||||
for (const batch of buildCoverageBatches(entries)) {
|
||||
clearBootstrapChannelPluginCache();
|
||||
clearBundledPluginMetadataCache();
|
||||
clearPluginManifestRegistryCache();
|
||||
resetFacadeRuntimeStateForTest();
|
||||
logCoverageBatch("openclaw.json", batch);
|
||||
const config = {} as OpenClawConfig;
|
||||
const env: Record<string, string> = {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { iterateBootstrapChannelPlugins } from "../channels/plugins/bootstrap-registry.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "../plugin-sdk/facade-runtime.js";
|
||||
import { listBundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js";
|
||||
import { loadBundledChannelSecretContractApi } from "./channel-contract-api.js";
|
||||
import type { SecretTargetRegistryEntry } from "./target-registry-types.js";
|
||||
|
||||
const SECRET_INPUT_SHAPE = "secret_input"; // pragma: allowlist secret
|
||||
@@ -19,16 +19,13 @@ function listChannelSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
|
||||
continue;
|
||||
}
|
||||
if (!metadata.publicSurfaceArtifacts?.includes("contract-api.js")) {
|
||||
continue;
|
||||
if (!metadata.publicSurfaceArtifacts?.includes("secret-contract-api.js")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const contractApi = loadBundledPluginPublicSurfaceModuleSync<{
|
||||
secretTargetRegistryEntries?: readonly SecretTargetRegistryEntry[];
|
||||
}>({
|
||||
dirName: metadata.dirName,
|
||||
artifactBasename: "contract-api.js",
|
||||
});
|
||||
entries.push(...(contractApi.secretTargetRegistryEntries ?? []));
|
||||
const contractApi = loadBundledChannelSecretContractApi(metadata.manifest.id);
|
||||
entries.push(...(contractApi?.secretTargetRegistryEntries ?? []));
|
||||
channelIds.forEach((channelId) => handledChannelIds.add(channelId));
|
||||
} catch {
|
||||
// Fall back to the full bootstrap plugin surface for channels that do not
|
||||
|
||||
@@ -1,37 +1,10 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SecretRefCredentialMatrixDocument } from "./credential-matrix.js";
|
||||
|
||||
function buildSecretRefCredentialMatrixInSubprocess(): SecretRefCredentialMatrixDocument {
|
||||
// Building the matrix pulls in bundled channel registry state. Keep that work out of the
|
||||
// Vitest worker so this docs-sync check does not pay the full module-graph cost in-process.
|
||||
const childEnv = { ...process.env };
|
||||
delete childEnv.NODE_OPTIONS;
|
||||
delete childEnv.VITEST;
|
||||
delete childEnv.VITEST_MODE;
|
||||
delete childEnv.VITEST_POOL_ID;
|
||||
delete childEnv.VITEST_WORKER_ID;
|
||||
|
||||
const stdout = execFileSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
"tsx",
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
'import { buildSecretRefCredentialMatrix } from "./src/secrets/credential-matrix.ts"; process.stdout.write(JSON.stringify(buildSecretRefCredentialMatrix()));',
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
env: childEnv,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
return JSON.parse(stdout) as SecretRefCredentialMatrixDocument;
|
||||
}
|
||||
import {
|
||||
buildSecretRefCredentialMatrix,
|
||||
type SecretRefCredentialMatrixDocument,
|
||||
} from "./credential-matrix.js";
|
||||
|
||||
describe("secret target registry docs", () => {
|
||||
it("stays in sync with docs/reference/secretref-user-supplied-credentials-matrix.json", () => {
|
||||
@@ -44,7 +17,7 @@ describe("secret target registry docs", () => {
|
||||
const raw = fs.readFileSync(pathname, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
|
||||
expect(parsed).toEqual(buildSecretRefCredentialMatrixInSubprocess());
|
||||
expect(parsed).toEqual(buildSecretRefCredentialMatrix());
|
||||
});
|
||||
|
||||
it("stays in sync with docs/reference/secretref-credential-surface.md", () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { iterateBootstrapChannelPlugins } from "../channels/plugins/bootstrap-registry.js";
|
||||
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
|
||||
import { listBundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { loadBundledChannelSecurityContractApi } from "./channel-contract-api.js";
|
||||
|
||||
const CORE_UNSUPPORTED_SECRETREF_SURFACE_PATTERNS = [
|
||||
"commands.ownerDisplaySecret",
|
||||
@@ -9,10 +11,26 @@ const CORE_UNSUPPORTED_SECRETREF_SURFACE_PATTERNS = [
|
||||
"auth-profiles.oauth.*",
|
||||
] as const;
|
||||
|
||||
function listBundledChannelIds(): string[] {
|
||||
return [
|
||||
...new Set(
|
||||
listBundledPluginMetadata({
|
||||
includeChannelConfigs: false,
|
||||
includeSyntheticChannelConfigs: false,
|
||||
}).flatMap((entry) => entry.manifest.channels ?? []),
|
||||
),
|
||||
].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function collectChannelUnsupportedSecretRefSurfacePatterns(): string[] {
|
||||
const patterns: string[] = [];
|
||||
for (const plugin of iterateBootstrapChannelPlugins()) {
|
||||
patterns.push(...(plugin.secrets?.unsupportedSecretRefSurfacePatterns ?? []));
|
||||
for (const channelId of listBundledChannelIds()) {
|
||||
const contract = loadBundledChannelSecurityContractApi(channelId);
|
||||
patterns.push(
|
||||
...(contract?.unsupportedSecretRefSurfacePatterns ??
|
||||
getBootstrapChannelPlugin(channelId)?.secrets?.unsupportedSecretRefSurfacePatterns ??
|
||||
[]),
|
||||
);
|
||||
}
|
||||
return patterns;
|
||||
}
|
||||
@@ -76,8 +94,13 @@ export function collectUnsupportedSecretRefConfigCandidates(
|
||||
}
|
||||
|
||||
if (isRecord(raw.channels)) {
|
||||
for (const plugin of iterateBootstrapChannelPlugins()) {
|
||||
const channelCandidates = plugin.secrets?.collectUnsupportedSecretRefConfigCandidates?.(raw);
|
||||
for (const channelId of Object.keys(raw.channels)) {
|
||||
const contract = loadBundledChannelSecurityContractApi(channelId);
|
||||
const channelCandidates =
|
||||
contract?.collectUnsupportedSecretRefConfigCandidates?.(raw) ??
|
||||
getBootstrapChannelPlugin(
|
||||
channelId,
|
||||
)?.secrets?.collectUnsupportedSecretRefConfigCandidates?.(raw);
|
||||
if (!channelCandidates?.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user