Skip to content

πŸŽ™οΈ task - feat(org): declare GitHub org security lockdown β€” actions policy, workflow token defaults, fork-PR policy, 2FAΒ #66

Description

@ehm-a-beaver

πŸ¦«πŸŽ™οΈ dispatch to foreman

πŸ’§ task enqueued
   β”œβ”€ priority = ?
   β”œβ”€ yieldage = ?
   └─ leverage = ?

title
feat(org): declare GitHub org security lockdown β€” actions policy, workflow token defaults, fork-PR policy, 2FA
description

.what

ehmpathy/declastruct-aws needs to declare an org-level GitHub lockdown policy, and
four of the required resources do not exist in this package yet. this task asks for the
domain objects + DAOs + acceptance that make the whole policy declarable.

audit run against the live ehmpathy org on 2026-08-02 (read-only, via gh api -X GET).

.why

ehmpathy/declastruct-aws is a PUBLIC repo that provisions live AWS accounts. its
ehmpathy-demo-oidc role carries demoPermissionsPolicy (ec2:*, iam:*Role, budgets:*,
ce:*) and its trust policy uses StringLike on repo:ehmpathy/*:* β€” so any repo, any
branch, any workflow in the org can assume it
.

that AWS-side hole gets fixed in declastruct-aws. but the GitHub-side controls that bound the
same blast radius are ALSO wide open, and today they can only be clicked, not declared:

live setting value why it matters
default_repository_permission write every member holds write on every repo -> can push a branch anywhere -> can assume the AWS role
two_factor_requirement_enabled false one phished member = org write = the AWS role
default_workflow_permissions write GITHUB_TOKEN is read-write by default across the org
can_approve_pull_request_reviews true a bot token can approve a PR (OWASP CICD-SEC-1)
allowed_actions all any third-party action, from anywhere, in any workflow
sha_pinning_required false tags are mutable pointers (OWASP CICD-SEC-3 / -4)

a control that can only be clicked drifts silently and leaves no diff for a reviewer. these
belong in a declared wish like every other resource.

.what already exists here (verified, no work needed)

  • DeclaredGithubOrgMemberPrivileges already covers defaultRepositoryPermission,
    membersCanCreateRepositories, membersCanDeleteRepositories,
    membersCanChangeRepoVisibility, membersCanForkPrivateRepositories,
    membersCanInviteOutsideCollaborators
  • DeclaredGithubBranchProtection, DeclaredGithubOrgRuleset, DeclaredGithubRepoRuleset
  • DeclaredGithubOrg READS twoFactorRequirementEnabled

.the gaps β€” 4 asks

1. DeclaredGithubOrgActionsPermissions

GET/PUT /orgs/{org}/actions/permissions

  • enabledRepositories: all | none | selected
  • allowedActions: all | local_only | selected
  • shaPinningRequired: boolean
  • and the companion GET/PUT /orgs/{org}/actions/permissions/selected-actions
    (githubOwnedAllowed, verifiedAllowed, patternsAllowed)

shaPinningRequired is the highest-value single field in this whole task β€” it enforces
rule.forbid.insecure.via=unpinned-third-party-code for every repo at once.

2. DeclaredGithubOrgActionsWorkflowDefaults

GET/PUT /orgs/{org}/actions/permissions/workflow

  • defaultWorkflowPermissions: read | write
  • canApprovePullRequestReviews: boolean

3. DeclaredGithubOrgActionsForkPrPolicy

GET/PUT /orgs/{org}/actions/permissions/fork-pr-workflows (and the repo-level peer)

  • the approval-required policy for fork PRs, plus whether a fork PR may read secrets or hold
    a write token

this is the one that lets outside contributors fork and contribute WITHOUT our resources,
which is exactly the posture we want β€” but it must be declared, not assumed.

4. twoFactorRequirementEnabled -> settable

it is readonly on DeclaredGithubOrg today. make it declarable, either by a move to
DeclaredGithubOrgMemberPrivileges or as its own resource. note it may need a plan-time
preflight
: GitHub removes members who lack 2FA when it is turned on, so a set should
surface who would be dropped.

.acceptance

each of the four owes the full four, per
rule.require.dao-and-acceptance-per-declared-resource:

  1. a domain object with unique keyed on the org
  2. a DAO via genDeclastructDao
  3. provider registration (type map + factory) and sdk export
  4. acceptance coverage: plan-inclusion + post-apply KEEP

plus:

  • name them symmetric with the extant DeclaredGithubOrg* peers
    (rule.require.symmetry-with-peer-resources)
  • secure defaults where a default is shipped β€” a consumer who declares one of these
    without thought should land in the pit of success, not on GitHub\x27s permissive default
  • set must be idempotent (these are all PUT-shaped, so upsert is natural)

.the desired end state, for reference

DeclaredGithubOrgActionsPermissions.as({
  org, enabledRepositories: "all",
  allowedActions: "selected", shaPinningRequired: true,
});
DeclaredGithubOrgActionsWorkflowDefaults.as({
  org, defaultWorkflowPermissions: "read", canApprovePullRequestReviews: false,
});

.sources


filed by beaver on behalf of vlad, out of the ehmpathy-camp grove behavior in
ehmpathy/declastruct-aws

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions