Securing Your Repository
PolicyPress stores your policies in Git β which means your security depends on who can read the repo and who can merge changes. This guide covers three areas: access control for the repository itself, a compliant policy-revision workflow, and controlling who can view the published website.
Private vs. public repositoriesπ
| Setting | When to use |
|---|---|
| Private | Policies contain internal procedures, personnel details, or pre-publication drafts. Default for most organizations. |
| Public | Policies are intentionally published for transparency (e.g. a vendorβs privacy policy). Use redact_web = true in [extra.policypress] to hide sensitive sections. |
A private repository keeps the Markdown source private but does not automatically restrict access to the published website β those are controlled separately (see Deployment options below).
Repository branch protectionπ
Branch protection turns the main branch into an audit trail by requiring that every policy change passes through a documented review.
Recommended settings for mainπ
In Settings β Branches β Add rule for the main branch:
- Require a pull request before merging β no direct pushes to
main - Required approvals: 2 β one must be the CISO (enforced via CODEOWNERS, below)
- Dismiss stale pull request approvals when new commits are pushed β re-approval required if the policy text changes after review
- Require status checks to pass β select the PolicyPress CI check so the build must succeed before merging
- Do not allow bypassing the above settings β applies the rules to administrators too
CODEOWNERSπ
Create a .github/CODEOWNERS file to ensure the CISO is always a required reviewer for policy files:
# All policy files require CISO sign-off
content/policies/ @your-org/ciso
# Config and templates require a maintainer review
config.toml @your-org/policypress-maintainers
Replace @your-org/ciso with the GitHub team or username for your CISO. GitHub will block merges until that person approves.
Branch policies for mainπ
In Project Settings β Repos β [your repo] β Policies, add branch policies for main:
- Minimum number of reviewers: 2 β disable βAllow requestors to approve their own changesβ
- Reset all approval votes when there are new changes β re-approval required after new commits
- Required reviewers β add your CISOβs account or AD group; this makes their approval mandatory on every PR
- Build validation β add your PolicyPress pipeline as a required check; PRs canβt merge if the build fails
Unlike GitHub CODEOWNERS, ADO enforces required reviewers through the Required Reviewers policy rather than a file in the repository. The CODEOWNERS file still works for suggesting reviewers automatically, but enforcement comes from the policy settings.
To restrict who can push or create branches at all: Project Settings β Repos β [repo] β Security β set βContributeβ to Deny for anyone who should not push directly.
Policy revision workflowπ
This workflow creates a complete, auditable record of every policy change β which is what makes a Git-based policy system defensible in ISO 27001 or SOC 2 audits.
Creating a revisionπ
-
Branch from
mainusing a descriptive name:policy/access-control-annual-review-2026 policy/incident-response-add-ransomware-section -
Make your changes to the policy Markdown. Update the
major_revisionslist in the policy front matter with the new version, date, and approval information. -
Open a pull request with:
- A clear title summarizing the change
- A description explaining why the change was made β the business or compliance reason, not just βwhatβ was changed
- A link to any related issue, incident, or external requirement
-
Request review from the CISO and one other designated reviewer.
Handling non-technical reviewersπ
Reviewers β especially the CISO β may not be comfortable using GitHub or Azure DevOps directly. That is expected and acceptable, as long as the record exists in Git. The recommended workflow:
- Share the diff link from the pull request over email or Slack for out-of-band review
- Hold a meeting to discuss the changes if needed
- The reviewer must then formally approve the PR in the platform (or ask a delegate to do so on their behalf)
- Any feedback, discussion, or decisions made outside the platform should be summarized in a PR comment before merging, so the audit trail is complete
The key principle: out-of-band communication is fine, but the decision and its rationale must land in the PR before the merge.
Commit and PR message guidanceπ
Auditors read commit messages. Write them as if explaining the change to someone unfamiliar with the context:
# Good
fix(access-control): require MFA for all admin accounts per CIS v8 control 6.5
Following the Q1 pen test finding that admin SSH access was password-only,
this revision adds mandatory MFA for all privileged accounts. Approved by
CISO in the 2026-04-10 security steering meeting.
# Not useful
updated policyMinimum viable approval gateπ
For small teams, the simplest compliant configuration is:
- 2 required approvers on
main - Approver 1: CISO (required via branch policy)
- Approver 2: any other named reviewer who is not the policy author β this prevents self-approval
Even if the CISO and the second reviewer are the same two people every time, the dual-approval requirement is what satisfies the control.
Deployment optionsπ
The published website needs its own access controls, separate from the repository. Choose the option that matches your infrastructure.
Best for: organizations using Microsoft 365 and Azure AD. This is the recommended deployment for PolicyPress in enterprise environments.
Azure Static Web Apps (SWA) natively integrates with Azure Active Directory. You register the app, configure an access policy, and only users in your Azure AD tenant (or specific groups) can reach the site.
Steps:
-
Create the Static Web App in the Azure Portal:
- Resource type: Static Web App
- Plan: Standard (required for custom auth)
- Region: your preference
- Deployment source: connect to your policy repository
-
Add a
staticwebapp.config.jsonto your repository root (committed alongsideconfig.toml):{ "auth": { "identityProviders": { "azureActiveDirectory": { "registration": { "openIdIssuer": "https://login.microsoftonline.com/<YOUR_TENANT_ID>/v2.0", "clientIdSettingName": "AAD_CLIENT_ID", "clientSecretSettingName": "AAD_CLIENT_SECRET" } } } }, "routes": [ { "route": "/*", "allowedRoles": ["authenticated"] } ], "responseOverrides": { "401": { "statusCode": 302, "redirect": "/.auth/login/aad" } } } -
Register an application in Azure AD:
- Azure Portal β Azure Active Directory β App Registrations β New Registration
- Name:
PolicyPress(or your org name) - Supported account types: Accounts in this organizational directory only
- Redirect URI:
https://<your-swa-url>/.auth/login/aad/callback - Under Certificates & secrets, create a client secret
- Copy the Application (client) ID and Tenant ID
-
Set application settings in the SWA resource:
AAD_CLIENT_ID= Application (client) ID from aboveAAD_CLIENT_SECRET= the client secret value
-
Restrict to specific users or groups (recommended):
- App Registrations β your app β Enterprise applications β Users and groups
- Add only the users or Azure AD groups that should have access
-
Add the deploy step to your pipeline. See Deploying to Production for the GitHub Actions and Azure DevOps pipeline snippets.
Users who visit the site are redirected to Microsoft login. Only members of your Azure AD tenant (or the groups you specified) can authenticate.
Best for: small teams already on GitHub, minimal setup.
Limitation: GitHub Pages visibility controls only work on GitHub Enterprise Cloud (GHEC) or public repositories. For private repos on GitHub Free/Pro, the site is either public or requires GHEC to restrict to org members.
Steps (GHEC):
- In Settings β Pages, set source to the
gh-pagesbranch (or/ (root)of a dedicated branch as output by the build) - Under Visibility, select Private to restrict access to members of the organization
- Members must be signed in to GitHub to view the site
No additional configuration is required on the PolicyPress side.
Best for: teams already using Cloudflare, or who want granular per-user/group access policies without Azure.
Deploy the built site to Cloudflare Pages, then wrap it with a Cloudflare Zero Trust Access application that enforces identity before serving any content.
Steps:
-
Deploy to Cloudflare Pages:
- Cloudflare dashboard β Pages β Create a project
- Connect your repository
- Build command: leave blank (PolicyPress pre-builds to
public/) - Build output directory:
public
-
Create a Zero Trust Access application:
- Cloudflare Zero Trust dashboard β Access β Applications β Add an application
- Type: Self-hosted
- Application domain: your Cloudflare Pages URL or custom domain
- Session duration:
8 hours(recommended)
-
Configure an identity provider: Zero Trust supports Azure AD, GitHub, Google Workspace, Okta, and others. For Azure AD:
- Zero Trust β Settings β Authentication β Add new provider β Azure AD
- Follow the on-screen instructions to register the Cloudflare app in your Azure tenant
-
Define an access policy:
- Add a Policy (e.g.
Employees only) - Rule action: Allow
- Include: Emails ending in
@yourdomain.comβ or use Azure Groups for granular control - Add a Catch-all deny rule for everyone else
- Add a Policy (e.g.
-
Set a custom domain (optional):
- Cloudflare Pages β your project β Custom domains β Add a custom domain
- Cloudflare automatically provisions TLS
Users are redirected to your identity provider before they can view any page.
Audit log and access reviewsπ
- Repository access: Review collaborators and team membership quarterly. Remove access for staff who have left or changed roles.
- Pipeline secrets: Rotate deployment tokens annually or immediately after team changes.
- Merge history: The
git logonmainis your policy change audit trail. Export it periodically or reference it during audits withgit log --all --oneline --no-merges content/policies/. - Azure AD / Cloudflare: Review user and group assignments in your identity provider when running access reviews. Remove individuals who no longer need access.