mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 23:21:17 +00:00
* feat(channels): bundle Reef guarded claw-to-claw channel Moves the Reef channel extension from openclaw/reef into the bundled extensions tree with the wire protocol vendored under extensions/reef/protocol. Includes channelConfigs manifest metadata, runtime status reporting via setStatus/buildAccountSnapshot, abort-aware inbox shutdown, monotonic device-auth timestamps, and immutable-model guard admission (dated snapshots plus documented gpt-5.6 ids). * fix(reef): pin zod exactly per dependency pin guard * style(reef): satisfy extension lint gates Curly braces in vendored protocol, explicit type re-exports, typed catch callbacks, Object.assign over map-spread, abort-aware loop restructure. * fix(reef): CI gates — knip workspace, boundary tsconfig, facade imports, cycle break, contract baselines - knip: reef workspace project includes vendored protocol/ (owns noble deps) - tsconfig: conform to canonical extension package-boundary include/exclude; add @openclaw/plugin-sdk devDependency - imports: channel-inbound/channel-outbound facades instead of deprecated subpaths - protocol: home ReplayStore contract in envelope.ts to break the envelope<->replay type cycle - baselines: reef in unguarded runtime-api list; regenerate bundled channel config metadata * feat(reef): plugin catalog cover art, channel label, changelog revert * fix(reef): drop unused error-class exports, register lint suppression * fix(reef): knip entry surface for vendored protocol, explicit WebSocketLike export
87 lines
2.5 KiB
TypeScript
87 lines
2.5 KiB
TypeScript
import { decodeUtf8, utf8 } from "./encoding.js";
|
|
|
|
export interface CheckFinding {
|
|
code: string;
|
|
decision: "deny";
|
|
}
|
|
|
|
export interface CheckResult {
|
|
allowed: boolean;
|
|
text?: string;
|
|
findings: CheckFinding[];
|
|
}
|
|
|
|
const MAX_BYTES = 32 * 1024;
|
|
const rules: Array<[string, RegExp]> = [
|
|
["private_key", /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/],
|
|
["openai_key", /\bsk-[A-Za-z0-9_-]{16,}\b/],
|
|
["github_token", /\b(?:ghp|gho)_[A-Za-z0-9]{20,}\b/],
|
|
["aws_access_key", /\bAKIA[0-9A-Z]{16}\b/],
|
|
["slack_token", /\bxox[bap]-[A-Za-z0-9-]{12,}\b/],
|
|
["jwt", /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/],
|
|
];
|
|
|
|
export function deterministicChecks(input: string | Uint8Array): CheckResult {
|
|
let text: string;
|
|
let bytes: Uint8Array;
|
|
try {
|
|
if (typeof input === "string") {
|
|
text = input;
|
|
bytes = utf8(input);
|
|
if (decodeUtf8(bytes) !== input) {
|
|
throw new Error();
|
|
}
|
|
} else {
|
|
bytes = input;
|
|
text = decodeUtf8(input);
|
|
}
|
|
} catch {
|
|
return { allowed: false, findings: [{ code: "invalid_utf8", decision: "deny" }] };
|
|
}
|
|
if (bytes.length > MAX_BYTES) {
|
|
return { allowed: false, text, findings: [{ code: "too_large", decision: "deny" }] };
|
|
}
|
|
const findings: CheckFinding[] = [];
|
|
for (const [code, pattern] of rules) {
|
|
if (pattern.test(text)) {
|
|
findings.push({ code, decision: "deny" });
|
|
}
|
|
}
|
|
if (hasHighEntropyToken(text)) {
|
|
findings.push({ code: "high_entropy_token", decision: "deny" });
|
|
}
|
|
return { allowed: findings.length === 0, text, findings };
|
|
}
|
|
|
|
function hasHighEntropyToken(text: string): boolean {
|
|
const hexCandidates = text.match(/\b[A-Fa-f0-9]{32,}\b/g) ?? [];
|
|
if (
|
|
hexCandidates.some((candidate) => {
|
|
if (/^(?:[0-9]+|[a-f]+)$/i.test(candidate) && new Set(candidate.toLowerCase()).size < 8) {
|
|
return false;
|
|
}
|
|
return shannonEntropy(candidate) >= 3.5;
|
|
})
|
|
) {
|
|
return true;
|
|
}
|
|
const looseCandidates = text.match(/\b[A-Za-z0-9+_=]{32,}\b/g) ?? [];
|
|
return looseCandidates.some(
|
|
(candidate) =>
|
|
/[A-Za-z]/.test(candidate) && /[0-9]/.test(candidate) && shannonEntropy(candidate) >= 4,
|
|
);
|
|
}
|
|
|
|
export function shannonEntropy(value: string): number {
|
|
const counts = new Map<string, number>();
|
|
for (const character of value) {
|
|
counts.set(character, (counts.get(character) ?? 0) + 1);
|
|
}
|
|
let entropy = 0;
|
|
for (const count of counts.values()) {
|
|
const probability = count / value.length;
|
|
entropy -= probability * Math.log2(probability);
|
|
}
|
|
return entropy;
|
|
}
|