mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-21 01:51:39 -06:00
fix(ci): trust maintainer-authored dependency changes
This commit is contained in:
@@ -324,7 +324,7 @@ export function renderTrustedDependencyComment({ actor, headSha }) {
|
||||
"",
|
||||
"### Dependency graph changes noted",
|
||||
"",
|
||||
"This PR includes dependency graph changes. The dependency guard is informational because the PR author is a repository admin or a member of `@openclaw/openclaw-secops`.",
|
||||
"This PR includes dependency graph changes. The dependency guard is informational because the PR author is a repository admin, a member of `@openclaw/openclaw-secops`, or an OpenClaw organization member with Maintain or Admin repository access.",
|
||||
"",
|
||||
`- Current SHA: ${markdownCode(headSha ?? "<head-sha>")}`,
|
||||
`- Trusted actor: @${sanitizeGuardDisplayValue(actor.login)}`,
|
||||
@@ -497,12 +497,27 @@ export function dependencyGuardTrustedActorCandidates({ pullRequest, event, curr
|
||||
/**
|
||||
* @param {{
|
||||
* candidates: GuardActorCandidate[],
|
||||
* pullRequest: { author_association?: string },
|
||||
* isDependencyApprover: (login: string) => Promise<string | null>,
|
||||
* getRepositoryRoleName: (login: string) => Promise<string | null>,
|
||||
* }} options
|
||||
*/
|
||||
export async function findTrustedDependencyGuardActor({ candidates, isDependencyApprover }) {
|
||||
export async function findTrustedDependencyGuardActor({
|
||||
candidates,
|
||||
pullRequest,
|
||||
isDependencyApprover,
|
||||
getRepositoryRoleName,
|
||||
}) {
|
||||
for (const candidate of candidates) {
|
||||
const role = await isDependencyApprover(candidate.login);
|
||||
let role = await isDependencyApprover(candidate.login);
|
||||
if (!role && pullRequest.author_association === "MEMBER") {
|
||||
// GitHub's MEMBER association excludes outside collaborators. Keep this role path separate
|
||||
// from override approvers so Maintain authors cannot authorize another contributor's PR.
|
||||
const repositoryRole = await getRepositoryRoleName(candidate.login);
|
||||
if (repositoryRole === "maintain" || repositoryRole === "admin") {
|
||||
role = `OpenClaw organization member with repository ${repositoryRole} role`;
|
||||
}
|
||||
}
|
||||
if (role) {
|
||||
return {
|
||||
login: candidate.login,
|
||||
@@ -787,7 +802,7 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isSecurityMember, isRepositoryAdmin } = createGuardApproverChecks({
|
||||
const { getRepositoryRoleName, isSecurityMember, isRepositoryAdmin } = createGuardApproverChecks({
|
||||
api,
|
||||
owner,
|
||||
repo,
|
||||
@@ -820,7 +835,9 @@ async function main() {
|
||||
}
|
||||
const trustedActor = await findTrustedDependencyGuardActor({
|
||||
candidates: dependencyGuardTrustedActorCandidates({ pullRequest, event, currentHeadSha }),
|
||||
pullRequest,
|
||||
isDependencyApprover,
|
||||
getRepositoryRoleName,
|
||||
});
|
||||
if (trustedActor) {
|
||||
if (mode === "detect") {
|
||||
|
||||
@@ -157,7 +157,7 @@ export function createGuardApproverChecks({
|
||||
warn = console.warn,
|
||||
}) {
|
||||
const membershipCache = new Map();
|
||||
const permissionCache = new Map();
|
||||
const repositoryRoleCache = new Map();
|
||||
const isSecurityMember = async (login) => {
|
||||
const normalizedLogin = login.toLowerCase();
|
||||
if (explicitSecurityApprovers.has(normalizedLogin)) {
|
||||
@@ -181,27 +181,28 @@ export function createGuardApproverChecks({
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const isRepositoryAdmin = async (login) => {
|
||||
const getRepositoryRoleName = async (login) => {
|
||||
const normalizedLogin = login.toLowerCase();
|
||||
if (permissionCache.has(normalizedLogin)) {
|
||||
return permissionCache.get(normalizedLogin);
|
||||
if (repositoryRoleCache.has(normalizedLogin)) {
|
||||
return repositoryRoleCache.get(normalizedLogin);
|
||||
}
|
||||
try {
|
||||
const result = await api.request(
|
||||
`/repos/${owner}/${repo}/collaborators/${encodeURIComponent(login)}/permission`,
|
||||
);
|
||||
const allowed = result?.permission === "admin";
|
||||
permissionCache.set(normalizedLogin, allowed);
|
||||
return allowed;
|
||||
const roleName = typeof result?.role_name === "string" ? result.role_name : null;
|
||||
repositoryRoleCache.set(normalizedLogin, roleName);
|
||||
return roleName;
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
warn(`Could not verify repository permission for ${login}: ${error.message}`);
|
||||
}
|
||||
permissionCache.set(normalizedLogin, false);
|
||||
return false;
|
||||
repositoryRoleCache.set(normalizedLogin, null);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return { isSecurityMember, isRepositoryAdmin };
|
||||
const isRepositoryAdmin = async (login) => (await getRepositoryRoleName(login)) === "admin";
|
||||
return { getRepositoryRoleName, isSecurityMember, isRepositoryAdmin };
|
||||
}
|
||||
|
||||
function githubErrorBodyTooLarge(maxBytes) {
|
||||
|
||||
Reference in New Issue
Block a user