Skip to content

Commit 84998bf

Browse files
committed
fix(policy): normalize model provider conformance
1 parent 5ed7c62 commit 84998bf

3 files changed

Lines changed: 85 additions & 4 deletions

File tree

extensions/policy/src/doctor/register.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,81 @@ describe("registerPolicyDoctorChecks", () => {
914914
]);
915915
});
916916

917+
it("normalizes model provider refs before deny policy comparison", async () => {
918+
const configPath = join(workspaceDir, "openclaw.jsonc");
919+
const cfg = {
920+
...cfgWithPolicy(),
921+
models: {
922+
providers: {
923+
"aws-bedrock": {},
924+
},
925+
},
926+
agents: {
927+
defaults: {
928+
model: "OpenRouter/openai/gpt-5.5",
929+
},
930+
},
931+
} as unknown as OpenClawConfig;
932+
await fs.writeFile(configPath, "{}", "utf-8");
933+
await fs.writeFile(
934+
join(workspaceDir, "policy.jsonc"),
935+
JSON.stringify({
936+
models: {
937+
providers: { deny: ["openrouter", "amazon-bedrock"] },
938+
},
939+
}),
940+
"utf-8",
941+
);
942+
943+
const result = await runPolicyDoctorLint(ctx(configPath, cfg));
944+
945+
expect(result.findings).toEqual([
946+
expect.objectContaining({
947+
checkId: "policy/models-denied-provider",
948+
severity: "error",
949+
ocPath: "oc://openclaw.config/models/providers/aws-bedrock",
950+
requirement: "oc://policy.jsonc/models/providers/deny",
951+
}),
952+
expect.objectContaining({
953+
checkId: "policy/models-denied-provider",
954+
severity: "error",
955+
ocPath: "oc://openclaw.config/agents/defaults/model",
956+
requirement: "oc://policy.jsonc/models/providers/deny",
957+
}),
958+
]);
959+
});
960+
961+
it("normalizes model provider refs before allow policy comparison", async () => {
962+
const configPath = join(workspaceDir, "openclaw.jsonc");
963+
const cfg = {
964+
...cfgWithPolicy(),
965+
models: {
966+
providers: {
967+
"aws-bedrock": {},
968+
},
969+
},
970+
agents: {
971+
defaults: {
972+
model: "OpenRouter/openai/gpt-5.5",
973+
},
974+
},
975+
} as unknown as OpenClawConfig;
976+
await fs.writeFile(configPath, "{}", "utf-8");
977+
await fs.writeFile(
978+
join(workspaceDir, "policy.jsonc"),
979+
JSON.stringify({
980+
models: {
981+
providers: { allow: ["openrouter", "amazon-bedrock"] },
982+
},
983+
}),
984+
"utf-8",
985+
);
986+
987+
const result = await runPolicyDoctorLint(ctx(configPath, cfg));
988+
989+
expect(result.findings).toEqual([]);
990+
});
991+
917992
it("reports model refs outside the policy allowlist", async () => {
918993
const configPath = join(workspaceDir, "openclaw.jsonc");
919994
const cfg = {

extensions/policy/src/doctor/register.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { basename, isAbsolute, resolve } from "node:path";
22
import JSON5 from "json5";
3+
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
34
import {
45
registerHealthCheck as registerPluginHealthCheck,
56
type HealthCheck,
@@ -871,8 +872,8 @@ function modelProviderFindings(
871872
policyDocName: string,
872873
evidence: PolicyEvidence,
873874
): readonly HealthFinding[] {
874-
const denied = new Set(readStringList(policy, ["models", "providers", "deny"]));
875-
const allowed = readStringList(policy, ["models", "providers", "allow"]);
875+
const denied = new Set(readModelProviderPolicyList(policy, ["models", "providers", "deny"]));
876+
const allowed = readModelProviderPolicyList(policy, ["models", "providers", "allow"]);
876877
const allowedSet = new Set(allowed);
877878
const findings: HealthFinding[] = [];
878879

@@ -886,6 +887,10 @@ function modelProviderFindings(
886887
return findings;
887888
}
888889

890+
function readModelProviderPolicyList(policy: unknown, path: readonly string[]): readonly string[] {
891+
return readStringList(policy, path).map((provider) => normalizeProviderId(provider));
892+
}
893+
889894
function modelProviderConformanceFindings(
890895
provider: PolicyEvidence["modelProviders"][number],
891896
denied: ReadonlySet<string>,

extensions/policy/src/policy-state.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createHash } from "node:crypto";
2+
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
23

34
export type PolicyAttestation = {
45
readonly checkedAt: string;
@@ -208,7 +209,7 @@ export function scanPolicyModelProviders(
208209
return Object.keys(configuredModelProviders(cfg))
209210
.toSorted((a, b) => a.localeCompare(b))
210211
.map((id) => ({
211-
id,
212+
id: normalizeProviderId(id),
212213
source: `oc://openclaw.config/models/providers/${id}`,
213214
}));
214215
}
@@ -586,7 +587,7 @@ function parseModelRef(
586587
return undefined;
587588
}
588589
return {
589-
provider: trimmed.slice(0, slash),
590+
provider: normalizeProviderId(trimmed.slice(0, slash)),
590591
model: trimmed.slice(slash + 1),
591592
};
592593
}

0 commit comments

Comments
 (0)