Repository Host Security Controls
This article documents the GitHub-host controls used to protect the AsiBackbone
source and release path. These controls protect repository changes and supply-chain
workflow entry points; they do not add runtime security behavior to the
AsiBackbone.* packages.
The repository currently operates under the bootstrap solo-maintainer model
documented in GOVERNANCE.md and .github/CODEOWNERS. The control set therefore
avoids pretending that self-review is independent review while still requiring a
pull-request trail, release-blocking CI, and an explicit emergency-bypass path.
Canonical desired state
The machine-readable main-branch ruleset is committed at
eng/repository-controls/main-branch-ruleset.json. The live GitHub configuration
is expected to match it.
| Control | Selected posture | Rationale |
|---|---|---|
| Secret scanning | Enabled | Detect supported credentials committed to repository history. |
| Secret-scanning push protection | Required and enabled | Block supported secrets before they land in the repository. A real secret should be removed/revoked rather than bypassed. |
| Non-provider patterns | Enable when the repository/plan exposes the control | Generic private keys and credential-bearing connection strings are relevant to a public .NET repository. Unsupported availability is reported as a warning, not treated as a reason to disable provider push protection. |
| Validity checks | Enable when the repository/plan exposes the control | Validity information improves alert triage for supported provider tokens. GitHub may contact the issuing service to determine validity, so this is kept as an explicit recorded decision. |
| Main-branch ruleset | Active | Makes the effective control set reviewable instead of relying only on legacy branch-protection defaults. |
| Pull request before merge | Required | All ordinary and emergency changes retain a PR/audit trail. |
| Required approvals | 0 during bootstrap solo-maintainer operation |
GitHub does not permit an author to supply independent approval of their own PR. Requiring one approval with one active maintainer would create a permanent self-lock or force routine bypass. |
| Code Owner approval | Not required during bootstrap solo-maintainer operation | .github/CODEOWNERS still records ownership and review routing. Re-enable required Code Owner review when a second active maintainer can provide independent approval. |
| Last-push approval | Not required during bootstrap solo-maintainer operation | This control also requires a second person. Revisit with the review-count decision. |
| Review-thread resolution | Required | Blocking review conversations must be resolved before ordinary merge. |
| Merge method | Squash only | Keeps main linear and ensures normal GitHub merges create one reviewable merge result. |
| Linear history | Required | Prevents merge commits from being pushed to main. |
| Force push and deletion | Blocked | Protects stable history from destructive updates. |
| Required signed commits | Deferred | GitHub evaluates commits introduced by a PR; unsigned local feature-branch commits can block squash merge even when GitHub would sign the final squash commit. The current local/Visual Studio workflow is not yet consistently signed. See the compensating controls below. |
| Emergency bypass | @cdcavell only, pull-request mode only |
Replaces a broad implicit administrator exemption with one repository-specific, auditable bypass actor that still must use a PR. |
Required status checks
The ruleset requires these release-blocking GitHub Actions checks:
Dependency reviewBuild, test, and packCodeQL analysisValidate version consistencyExternal consumer package smoke testRestore, build, test, docs, pack, and smokeValidate workflows with actionlintAnalyze workflows with zizmorAnalyze dependencies with OWASP Dependency-Check
The checks are bound to the GitHub Actions integration and use strict status-check
policy so the pull request must be validated against the current target branch.
Each required workflow reports for every pull request targeting main; required
workflows must not use path filters because a skipped workflow cannot satisfy its
required context.
Public API baseline validation is also pull-request blocking. It runs inside both
the required Build, test, and pack and Restore, build, test, docs, pack, and smoke jobs. The repository intentionally does not expose a separate public API
status context because doing so would duplicate the same validation rather than
strengthen the gate.
Signed-commit decision and compensating controls
Required commit signatures are intentionally not enabled by this issue. GitHub's signed-commit rule evaluates commits introduced by the pull request, not only the final squash commit. Enabling it before the maintainer's local commits and automation identities are consistently signed would turn normal pull requests into bypass-only merges, weakening rather than strengthening the intended model.
Until local signing is adopted and verified end to end, the compensating controls are:
- every ordinary change reaches
mainthrough a pull request; - only squash merge is allowed by the ruleset;
- all nine release-blocking checks must pass unless the documented emergency bypass is explicitly used;
- force pushes and deletion are blocked;
- the emergency bypass is limited to one user and to pull requests only;
- GitHub's merge result and the ruleset-bypass event remain visible in repository history/Rule Insights.
Revisit required_signatures when local GPG/SSH signing and relevant automation
identities have been exercised successfully on a non-default branch. Do not enable
it only to make the settings page appear stricter if the practical result is that
every normal PR must be bypassed.
Applying or auditing the controls
Repository settings are not versioned by Git, so the patch commits the desired state and the tool used to compare/apply it. The live setting must still be changed through GitHub.
Authenticate GitHub CLI with an account that has repository Administration permission, then run the read-only audit:
gh auth status
./scripts/Manage-RepositorySecurityControls.ps1
Preview mutations before applying them:
./scripts/Manage-RepositorySecurityControls.ps1 -Apply -WhatIf
Apply the repository security settings and create/update the canonical ruleset:
./scripts/Manage-RepositorySecurityControls.ps1 -Apply
The apply path enables secret scanning and push protection first. It then attempts to enable non-provider patterns and validity checks independently; if GitHub does not expose one of those optional controls for the repository/plan, the script warns without weakening mandatory push protection. Finally it creates or updates the named main-branch ruleset and reruns the audit.
The legacy branch-protection rule may remain in place as defense in depth while the ruleset is active. Its administrator exemption is not the canonical bypass mechanism: ordinary administrators are still constrained by the active ruleset, and the ruleset contains only the explicit pull-request-only bypass actor above.
Project automation GitHub App
The project-status workflows use a dedicated GitHub App installation token rather
than a long-lived personal access token. The App token is minted only for the job
that needs it and is passed directly to GitHub CLI through GH_TOKEN.
The workflow-level permissions: block applies only to the built-in
GITHUB_TOKEN; it does not constrain a GitHub App installation token. Both
project automation workflows therefore use permissions: {} and request only
the App permissions required for their GraphQL operations.
Required App installation and permissions
Create a dedicated GitHub App for AsiBackbone project automation and install it
on the AsiBackbone organization with repository access limited to
AsiBackbone.
Grant only these App permissions:
- Organization permissions:
- Projects: Read and write
- Repository permissions:
- Issues: Read-only
- Pull requests: Read-only
- Metadata: Read-only (GitHub grants this baseline permission)
The pull-request status workflow requests Projects write, Issues read, and Pull requests read. The branch-status workflow requests only Projects write and Issues read. Neither workflow requests repository contents write, administration, or other unrelated permissions.
Configure these repository values:
| Type | Name | Purpose |
|---|---|---|
| Repository variable | PROJECT_APP_CLIENT_ID |
GitHub App client ID. This identifier is not secret. |
| Repository secret | PROJECT_APP_PRIVATE_KEY |
PEM private key used only to mint short-lived installation tokens. |
Keep the existing project configuration variables:
PROJECT_OWNERPROJECT_NUMBERPROJECT_REVIEW_STATUSPROJECT_IN_PROGRESS_STATUS
Do not store an installation access token as a repository or organization
secret. actions/create-github-app-token creates a short-lived token for each
job, and the workflows pass that value directly to gh through GH_TOKEN.
Dependabot and fork pull requests
The pull-request project automation deliberately skips Dependabot pull requests and pull requests whose head repository is a fork. Those event types do not receive repository Actions secrets under the normal GitHub security model, so they must not attempt to mint the project App token.
Skipping the project-status mutation does not skip normal validation of those pull requests. It only prevents the credentialed organization-project mutation from running in an event context that cannot receive the App private key.
The branch-status workflow runs on create events in this repository and also
excludes dependabot/ branches. A branch created only in an external fork does
not create a branch in AsiBackbone/AsiBackbone and therefore does not receive
this repository's project-automation credential.
Migration from PROJECT_TOKEN
After the GitHub App is installed and both project workflows have completed successfully:
- Delete the legacy
PROJECT_TOKENrepository secret. - Revoke the personal access token that previously backed that secret.
- Confirm no other repository or organization automation still depends on the PAT before removing any associated authorization.
There is no PAT fallback in the project-status workflows. A future fallback requires separate security review and must not silently restore a broad, long-lived classic PAT.
Rotation and validation
Rotate the App private key by generating a new key in the GitHub App settings,
updating PROJECT_APP_PRIVATE_KEY, validating both automation workflows, and
then deleting the previous key from the App. For emergency revocation, delete
the active private key or suspend/uninstall the App installation.
After configuration or rotation, validate both behavioral paths:
- create an
issue-<number>-workbranch and confirm the corresponding project item moves toPROJECT_IN_PROGRESS_STATUS; - open a non-draft pull request with
Closes #<number>and confirm the linked issue moves toPROJECT_REVIEW_STATUS; - confirm a Dependabot pull request and a fork pull request do not run the credential-minting step;
- run the repository workflow-security checks, including actionlint and zizmor.
Emergency bypass procedure
Bypass is for an urgent failure of the repository control plane, not a shortcut around an inconvenient test.
- Open or retain a pull request. Direct push to
mainis not the emergency path. - Record which rule/check is being bypassed and why waiting for repair is more dangerous than merging the reviewed change.
- Keep the change as narrow as possible and use squash merge.
- Never use ruleset bypass as a substitute for secret-scanning push-protection remediation. Remove the secret, revoke/rotate it when appropriate, and retry.
- After the emergency merge, repair the failing control and rerun
Manage-RepositorySecurityControls.ps1. - Preserve the PR and Rule Insights/bypass event as the audit trail.
If a second active Core Maintainer is appointed, the first repository-control change should reevaluate the bypass list, required approval count, Code Owner review, and last-push approval before the bootstrap exception is retired.
Automation compatibility check
The ruleset targets refs/heads/main; release tags are not targeted. Package
publishing, provenance/attestation, and release workflows triggered by version
tags therefore keep their existing tag path. Normal pull requests run all nine
required checks recorded in the canonical ruleset.
After first applying or materially changing the live controls:
- run the read-only repository-control audit;
- open a normal PR and confirm all required checks are present;
- confirm the PR can merge normally by squash after checks pass;
- confirm the resulting
maincommit is recorded as expected; - run/observe Stable Release Validation and the external consumer smoke workflow;
- on the next planned release, verify the tag-triggered package/release path still runs without requiring a branch-ruleset bypass.
GitHub documents repository rulesets, pull-request-only bypass actors, signed commit behavior, and secret-scanning push protection in its repository and code security documentation. The committed manifest is the AsiBackbone-specific decision record; GitHub documentation remains authoritative for platform semantics.