mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-29 07:01:40 +03:00
fix(scripts): harden clawtributors updater
This commit is contained in:
@@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Security/Exec approvals: prevent safeBins allowlist bypass via shell expansion (host exec allowlist mode only; not enabled by default). Thanks @christos-eth.
|
- Security/Exec approvals: prevent safeBins allowlist bypass via shell expansion (host exec allowlist mode only; not enabled by default). Thanks @christos-eth.
|
||||||
- Security/Gateway: block `system.execApprovals.*` via `node.invoke` (use `exec.approvals.node.*` instead). Thanks @christos-eth.
|
- Security/Gateway: block `system.execApprovals.*` via `node.invoke` (use `exec.approvals.node.*` instead). Thanks @christos-eth.
|
||||||
- Security/Exec: harden PATH handling by disabling project-local `node_modules/.bin` bootstrapping by default, disallowing node-host `PATH` overrides, and spawning ACP servers via the current executable by default. Thanks @akhmittra.
|
- Security/Exec: harden PATH handling by disabling project-local `node_modules/.bin` bootstrapping by default, disallowing node-host `PATH` overrides, and spawning ACP servers via the current executable by default. Thanks @akhmittra.
|
||||||
|
- Scripts: harden clawtributors updater against command injection via untrusted commit metadata. Thanks @scanleale.
|
||||||
- CLI: fix lazy core command registration so top-level maintenance commands (`doctor`, `dashboard`, `reset`, `uninstall`) resolve correctly instead of exposing a non-functional `maintenance` placeholder command.
|
- CLI: fix lazy core command registration so top-level maintenance commands (`doctor`, `dashboard`, `reset`, `uninstall`) resolve correctly instead of exposing a non-functional `maintenance` placeholder command.
|
||||||
- Telegram: when `channels.telegram.commands.native` is `false`, exclude plugin commands from `setMyCommands` menu registration while keeping plugin slash handlers callable. (#15132) Thanks @Glucksberg.
|
- Telegram: when `channels.telegram.commands.native` is `false`, exclude plugin commands from `setMyCommands` menu registration while keeping plugin slash handlers callable. (#15132) Thanks @Glucksberg.
|
||||||
- Security/Agents: scope CLI process cleanup to owned child PIDs to avoid killing unrelated processes on shared hosts. Thanks @aether-ai-agent.
|
- Security/Agents: scope CLI process cleanup to owned child PIDs to avoid killing unrelated processes on shared hosts. Thanks @aether-ai-agent.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { execSync } from "node:child_process";
|
import { execFileSync, execSync } from "node:child_process";
|
||||||
import { readFileSync, writeFileSync } from "node:fs";
|
import { readFileSync, writeFileSync } from "node:fs";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import type { ApiContributor, Entry, MapConfig, User } from "./update-clawtributors.types.js";
|
import type { ApiContributor, Entry, MapConfig, User } from "./update-clawtributors.types.js";
|
||||||
@@ -290,6 +290,27 @@ function parseCount(value: string): number {
|
|||||||
return /^\d+$/.test(value) ? Number(value) : 0;
|
return /^\d+$/.test(value) ? Number(value) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidLogin(login: string): boolean {
|
||||||
|
if (!/^[A-Za-z0-9-]{1,39}$/.test(login)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (login.startsWith("-") || login.endsWith("-")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (login.includes("--")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLogin(login: string | null): string | null {
|
||||||
|
if (!login) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trimmed = login.trim();
|
||||||
|
return isValidLogin(trimmed) ? trimmed : null;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAvatar(url: string): string {
|
function normalizeAvatar(url: string): string {
|
||||||
if (!/^https?:/i.test(url)) {
|
if (!/^https?:/i.test(url)) {
|
||||||
return url;
|
return url;
|
||||||
@@ -307,8 +328,12 @@ function isGhostAvatar(url: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fetchUser(login: string): User | null {
|
function fetchUser(login: string): User | null {
|
||||||
|
const normalized = normalizeLogin(login);
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const data = execSync(`gh api users/${login}`, {
|
const data = execFileSync("gh", ["api", `users/${normalized}`], {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
});
|
});
|
||||||
@@ -334,45 +359,45 @@ function resolveLogin(
|
|||||||
emailToLogin: Record<string, string>,
|
emailToLogin: Record<string, string>,
|
||||||
): string | null {
|
): string | null {
|
||||||
if (email && emailToLogin[email]) {
|
if (email && emailToLogin[email]) {
|
||||||
return emailToLogin[email];
|
return normalizeLogin(emailToLogin[email]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (email && name) {
|
if (email && name) {
|
||||||
const guessed = guessLoginFromEmailName(name, email, apiByLogin);
|
const guessed = guessLoginFromEmailName(name, email, apiByLogin);
|
||||||
if (guessed) {
|
if (guessed) {
|
||||||
return guessed;
|
return normalizeLogin(guessed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (email && email.endsWith("@users.noreply.github.com")) {
|
if (email && email.endsWith("@users.noreply.github.com")) {
|
||||||
const local = email.split("@", 1)[0];
|
const local = email.split("@", 1)[0];
|
||||||
const login = local.includes("+") ? local.split("+")[1] : local;
|
const login = local.includes("+") ? local.split("+")[1] : local;
|
||||||
return login || null;
|
return normalizeLogin(login);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (email && email.endsWith("@github.com")) {
|
if (email && email.endsWith("@github.com")) {
|
||||||
const login = email.split("@", 1)[0];
|
const login = email.split("@", 1)[0];
|
||||||
if (apiByLogin.has(login.toLowerCase())) {
|
if (apiByLogin.has(login.toLowerCase())) {
|
||||||
return login;
|
return normalizeLogin(login);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = normalizeName(name);
|
const normalized = normalizeName(name);
|
||||||
if (nameToLogin[normalized]) {
|
if (nameToLogin[normalized]) {
|
||||||
return nameToLogin[normalized];
|
return normalizeLogin(nameToLogin[normalized]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const compact = normalized.replace(/\s+/g, "");
|
const compact = normalized.replace(/\s+/g, "");
|
||||||
if (nameToLogin[compact]) {
|
if (nameToLogin[compact]) {
|
||||||
return nameToLogin[compact];
|
return normalizeLogin(nameToLogin[compact]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiByLogin.has(normalized)) {
|
if (apiByLogin.has(normalized)) {
|
||||||
return normalized;
|
return normalizeLogin(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiByLogin.has(compact)) {
|
if (apiByLogin.has(compact)) {
|
||||||
return compact;
|
return normalizeLogin(compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user