Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/standards/runner-policy/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@
},
"fixedRunsOn": ["ubuntu-24.04"]
},
"melodic-software/ci-workflows/.github/workflows/standards-sync.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5": {
"routing": "runner-input",
"runnerInput": "runner",
"allowedInputs": ["runner", "manifest", "standards-ref", "dry-run", "targets"],
"allowedSecrets": {
"app-client-id": "${{ secrets.STANDARDS_SYNC_APP_CLIENT_ID }}",
"app-private-key": "${{ secrets.STANDARDS_SYNC_APP_PRIVATE_KEY }}"
}
},
"melodic-software/ci-workflows/.github/workflows/zizmor.yml@99ac2f8c5b09dbb785d4eaf18465cbd96c30290c": {
"routing": "hosted-only",
"allowedInputs": ["paths"],
Expand Down
116 changes: 104 additions & 12 deletions .github/standards/runner-policy/runner-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,28 @@ function auditLocalPermissionFlow({
target?.kind === "hosted-reusable" ||
target?.kind === "hosted-local-reusable";
if (hostedExecution && capability !== "read-only") {
// The direct audit of this same job classifies a declared packages-only
// write map (with no other privileged surface) as publication — with the
// structural container categories taking precedence over that downgrade;
// this flow pass must demand the same category or the two checks
// contradict each other on one exception key. Anything else — a broader
// declared map, an additional privileged surface, or an undeclared map
// that merely inherits the caller's write capability — stays privileged.
const declaredRequirement = privilegedHostedRequirement(
record.workflow,
job,
selector,
target,
policy,
undefined,
undefined,
undefined,
);
let requiredReason = "privileged-control-plane";
if (declaredRequirement?.reason === "publication") {
const structuralRequirement = structuralHostedRequirement(job);
requiredReason = structuralRequirement ? structuralRequirement.reason : "publication";
}
const key = `${record.file}#${jobId}`;
const exception = config.exceptions.get(key);
if (!exception) {
Expand All @@ -775,18 +797,18 @@ function auditLocalPermissionFlow({
"hosted-exception-required",
record.file,
jobId,
"a fixed-hosted called job inherits write-capable caller permissions and requires a privileged-control-plane exception",
`a fixed-hosted called job inherits write-capable caller permissions and requires a ${requiredReason} exception`,
),
);
} else {
consumedExceptions.add(key);
if (exception.reason !== "privileged-control-plane") {
if (exception.reason !== requiredReason) {
findings.push(
finding(
"hosted-exception-category",
record.file,
jobId,
`inherited write-capable caller permissions require exception reason privileged-control-plane, not ${exception.reason}`,
`inherited write-capable caller permissions require exception reason ${requiredReason}, not ${exception.reason}`,
),
);
}
Expand Down Expand Up @@ -2356,6 +2378,18 @@ function permissionHostedRequirement(workflow, job, { requireExplicitReadOnly =
}
return undefined;
}
// packages is registry-publication authority, not repository/organization
// state: a job whose only write scope is packages belongs to the durable
// publication category, so artifact provenance can stay on hosted
// infrastructure after the control-plane reasons retire. Any additional
// write scope keeps the job in the privileged category.
if (writable.length === 1 && writable[0] === "packages") {
return {
reason: "publication",
description: "write GITHUB_TOKEN permissions (packages)",
rule: "privileged-hosted-only",
};
}
return {
reason: "privileged-control-plane",
description: `write GITHUB_TOKEN permissions (${writable.join(", ")})`,
Expand Down Expand Up @@ -2576,7 +2610,12 @@ function grantedSecretName(value, secretNames) {
return match !== null && secretNames.has(match[1]) ? match[1] : undefined;
}

function localCredentialRequirement(workflow, job, grantAllowance) {
function localCredentialRequirement(
workflow,
job,
grantAllowance,
{ admitGitHubToken = false } = {},
) {
if (containsCredentialExpression(workflow.env)) {
return "a credential expression in workflow-level env";
}
Expand Down Expand Up @@ -2611,8 +2650,12 @@ function localCredentialRequirement(workflow, job, grantAllowance) {
EXACT_GITHUB_TOKEN_EXPRESSIONS.has(value) &&
// A grant pins the job's exact effective permission map, so the
// GitHub-provided token those permissions describe is admitted even
// when the pinned map holds a write scope.
(readOnly || grantAllowance !== undefined)
// when the pinned map holds a write scope. admitGitHubToken extends
// the same reasoning to the publication category: the packages-only
// permission map the exception reviews is exactly the capability the
// GitHub-provided token carries, so referencing that token adds no
// credential surface beyond the already-categorized permissions.
(readOnly || grantAllowance !== undefined || admitGitHubToken)
) {
continue;
}
Expand Down Expand Up @@ -2712,6 +2755,14 @@ function privilegedHostedRequirement(
// and a grant keyed to such a job surfaces as local-routing-grant-drift.
const grantApplies =
grant !== undefined && target?.kind === "selector-output" && typeof job.uses !== "string";
// The publication downgrade holds only while packages:write is the job's
// entire privileged surface: a deployment environment, credential
// expression, or credential-minting action found below still demands the
// privileged category, so the weaker requirement is held until every later
// check passes rather than returned at the permission check. The one
// admission is the exact GitHub-provided token expression, which carries
// only the already-categorized packages-only permission map.
let publicationRequirement;
if (grantApplies) {
const permissionError = exactCanonicalMap(
effectivePermissions(workflow, job),
Expand All @@ -2734,17 +2785,43 @@ function privilegedHostedRequirement(
: permissionHostedRequirement(workflow, job, {
requireExplicitReadOnly: target?.kind === "selector-output",
});
if (permissionRequirement) {
if (permissionRequirement?.reason === "publication") {
publicationRequirement = permissionRequirement;
} else if (permissionRequirement) {
return permissionRequirement;
}
}

// The selector's one exact observer secret is part of its reviewed hosted
// reusable-workflow contract. Exact hosted-only reusable secret mappings are
// likewise governed by approvedReusableWorkflowContracts rather than this
// local-workload boundary.
// local-workload boundary. A pending publication downgrade still scans the
// caller outside that reviewed secrets mapping first: a contract allowlists
// input names, not values, so a credential expression smuggled through a
// `with:` value would otherwise ride the weaker category.
if (selector.isSelector || target?.kind === "hosted-reusable") {
return undefined;
if (publicationRequirement === undefined) {
return undefined;
}
const boundaryJob = Object.fromEntries(
Object.entries(job).filter(([name]) => name !== "secrets"),
);
const callerCredentialRequirement = localCredentialRequirement(
workflow,
boundaryJob,
undefined,
{
admitGitHubToken: true,
},
);
if (callerCredentialRequirement) {
return {
reason: "privileged-control-plane",
description: callerCredentialRequirement,
rule: "privileged-hosted-only",
};
}
return publicationRequirement;
}

if (
Expand Down Expand Up @@ -2778,7 +2855,14 @@ function privilegedHostedRequirement(
const grantAllowance = grantApplies
? { secretNames: new Set(grant.secrets ?? []), usedSecretNames: new Set() }
: undefined;
const credentialRequirement = localCredentialRequirement(workflow, credentialJob, grantAllowance);
const credentialRequirement = localCredentialRequirement(
workflow,
credentialJob,
grantAllowance,
{
admitGitHubToken: publicationRequirement !== undefined,
},
);
if (credentialRequirement) {
return {
reason: "privileged-control-plane",
Expand Down Expand Up @@ -2824,7 +2908,7 @@ function privilegedHostedRequirement(
];
}

return undefined;
return publicationRequirement;
}

function structuralHostedRequirement(job) {
Expand Down Expand Up @@ -3146,7 +3230,15 @@ export async function auditRepository({
grantUsage,
)
: undefined;
const hostedRequirement = privilegedHosted ?? structuralHostedRequirement(job);
// A held publication downgrade must not mask the structural container
// categories: a containerized packages-only publisher stays in the
// job-container/service-container inventory. Privileged requirements
// keep their ordinary precedence over structural ones.
const structuralHosted = structuralHostedRequirement(job);
const hostedRequirement =
privilegedHosted?.reason === "publication"
? (structuralHosted ?? privilegedHosted)
: (privilegedHosted ?? structuralHosted);
if (
grant &&
routingEnabled &&
Expand Down
2 changes: 2 additions & 0 deletions lychee.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ exclude = [
'^https?://(www\.)?linkedin\.com/',
'^https?://bsky\.app/',
'^https?://(www\.)?medium\.com/@ziobrando/the-rise-and-fall-of-the-dungeon-master-c2d511eed12f/?([?#].*)?$',
'^https?://(www\.)?medium\.com/fortmatic/postmortem-service-disruption-from-expired-ssl-certificate-a993a59272a0/?([?#].*)?$',
'^https?://dev\.mysql\.com/doc/refman/8\.4/en/innodb-transaction-isolation-levels\.html/?([?#].*)?$',
'^https?://help\.miro\.com/hc/en-us/articles/31624028247058/?([?#].*)?$',
'^https?://isdown\.app/status/anthropic/?([?#].*)?$',
'^https?://(www\.)?npmjs\.com/package/(firecrawl-cli|@mirohq/miro-api)/?([?#].*)?$',
Expand Down
Loading