mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -1,4 +1,21 @@
|
||||
---
|
||||
# Security note: this workflow intentionally uses `pull_request_target` so the
|
||||
# `reward user` label can fire on PRs from forks (the plain `pull_request`
|
||||
# trigger cannot see labels applied to fork PRs and runs under a read-only
|
||||
# GITHUB_TOKEN that cannot post comments / add labels).
|
||||
#
|
||||
# The job is bounded:
|
||||
# - it never checks out fork code (no `actions/checkout` with `pull_request.head.ref`)
|
||||
# - it never executes arbitrary scripts from the PR
|
||||
# - the only side effects are: posting a templated comment and adding a label
|
||||
# - permissions are minimal (contents:read, issues:write, pull-requests:read)
|
||||
# - all PR/issue user metadata interpolated into search queries or comment
|
||||
# bodies is treated as untrusted and normalized through safeLogin/safeUrl
|
||||
# before use (see the inline script below).
|
||||
#
|
||||
# Do NOT broaden permissions, add a fork checkout, or remove the input
|
||||
# normalization without a security review.
|
||||
|
||||
name: Contributor Rewards
|
||||
|
||||
on:
|
||||
@@ -47,6 +64,25 @@ jobs:
|
||||
const merchUrl = (process.env.CONTRIBUTOR_REWARD_MERCH_URL || '').trim();
|
||||
const customMessage = (process.env.CONTRIBUTOR_REWARD_MESSAGE || '').trim();
|
||||
|
||||
// Treat all PR/issue metadata as untrusted display-only data — this
|
||||
// workflow runs under `pull_request_target` so anything coming from
|
||||
// a fork (login, html_url) crosses a trust boundary. Normalize
|
||||
// before interpolating into search queries or comment bodies.
|
||||
// GitHub usernames are 1-39 chars, alphanumeric + hyphen.
|
||||
const safeLogin = (s) => String(s || '').replace(/[^A-Za-z0-9-]/g, '').slice(0, 39);
|
||||
// Only allow github.com / *.github.com (covers GitHub Enterprise).
|
||||
// Strip query/fragment to defeat comment-body injection via crafted
|
||||
// URLs. Return '' if the URL is unparseable or off-host.
|
||||
const safeUrl = (s) => {
|
||||
try {
|
||||
const u = new URL(String(s || ''));
|
||||
const okHost = u.hostname === 'github.com' || u.hostname.endsWith('.github.com');
|
||||
return okHost ? `${u.origin}${u.pathname}` : '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const normalize = value => String(value || '').trim().toLowerCase();
|
||||
const triggerLabelKey = normalize(triggerLabel);
|
||||
|
||||
@@ -94,12 +130,19 @@ jobs:
|
||||
return null;
|
||||
}
|
||||
|
||||
const login = safeLogin(pr.user?.login);
|
||||
if (!login) {
|
||||
core.info(`PR #${pr.number} has an unrecognized author login shape; skipping.`);
|
||||
return null;
|
||||
}
|
||||
const htmlUrl = safeUrl(pr.html_url);
|
||||
|
||||
return {
|
||||
kind: 'pull request',
|
||||
number: pr.number,
|
||||
htmlUrl: pr.html_url,
|
||||
login: pr.user.login,
|
||||
userType: pr.user.type,
|
||||
htmlUrl,
|
||||
login,
|
||||
userType: pr.user?.type,
|
||||
reason: 'first merged pull request',
|
||||
manualOverride: false,
|
||||
requireFirstMergedPr: true,
|
||||
@@ -112,12 +155,19 @@ jobs:
|
||||
return null;
|
||||
}
|
||||
|
||||
const login = safeLogin(pr.user?.login);
|
||||
if (!login) {
|
||||
core.info(`PR #${pr.number} has an unrecognized author login shape; skipping.`);
|
||||
return null;
|
||||
}
|
||||
const htmlUrl = safeUrl(pr.html_url);
|
||||
|
||||
return {
|
||||
kind: 'pull request',
|
||||
number: pr.number,
|
||||
htmlUrl: pr.html_url,
|
||||
login: pr.user.login,
|
||||
userType: pr.user.type,
|
||||
htmlUrl,
|
||||
login,
|
||||
userType: pr.user?.type,
|
||||
reason: `maintainer-applied "${triggerLabel}" label on a pull request`,
|
||||
manualOverride: true,
|
||||
requireFirstMergedPr: false,
|
||||
@@ -139,12 +189,19 @@ jobs:
|
||||
return null;
|
||||
}
|
||||
|
||||
const login = safeLogin(issue.user?.login);
|
||||
if (!login) {
|
||||
core.info(`Issue #${issue.number} has an unrecognized author login shape; skipping.`);
|
||||
return null;
|
||||
}
|
||||
const htmlUrl = safeUrl(issue.html_url);
|
||||
|
||||
return {
|
||||
kind: 'issue',
|
||||
number: issue.number,
|
||||
htmlUrl: issue.html_url,
|
||||
login: issue.user.login,
|
||||
userType: issue.user.type,
|
||||
htmlUrl,
|
||||
login,
|
||||
userType: issue.user?.type,
|
||||
reason: `maintainer-applied "${triggerLabel}" label on an issue`,
|
||||
manualOverride: true,
|
||||
requireFirstMergedPr: false,
|
||||
|
||||
Reference in New Issue
Block a user