I was hardening this blog’s GitHub repository as part of a security series — adding CodeQL static analysis, Dependabot, and secret scanning. Everything went smoothly until I tried to run CodeQL in a workflow against my personal, private repository. The workflow ran. The analysis completed. And then it failed with this:
Warning: This run of the CodeQL Action does not have permission to access the
CodeQL Action API endpoints. Code scanning is not enabled for this repository.
The repository settings showed Code Scanning as available. The workflow had security-events: write. The YAML was valid. The workflow was not running from a fork. Nothing obvious was wrong.
After some digging I found the root cause: my personal GitHub account is a member of my workplace’s GitHub organisation. And that organisation is enrolled in a GitHub Enterprise plan. The enterprise had applied policies that restricted GitHub Advanced Security (GHAS) features — including the Code Scanning upload API — across all accounts that were members of the org. Including mine. Including on repositories that have nothing to do with work.
That felt surprising. Let me explain why it happens and what you can actually do about it.
How GitHub Permission Layers Work
GitHub has three distinct levels of policy control, and they are strictly hierarchical:
Enterprise
└── Organisation(s)
└── Repository
└── (individual users & tokens)
Most developers only think about the bottom two layers — repositories and users. But in a workplace context, the top two layers have significant and often invisible influence.
Enterprise Level
An enterprise is the top-level account for organisations that use GitHub Enterprise Cloud or GitHub Enterprise Server. Enterprise owners can set policies that:
- Apply to all organisations under the enterprise
- Cannot be overridden by organisation admins or repository owners below them
- May affect personal accounts that are members of any org in the enterprise
This is the layer where GHAS enablement, IP allow lists, OAuth app policies, and Actions permissions live.
Organisation Level
Organisation admins can apply policies within the bounds the enterprise allows. They can:
- Restrict which GitHub Actions can run (first-party only, specific allow-lists)
- Set default workflow permissions (read-only vs read/write)
- Require two-factor authentication
- Control Dependabot and code security features for org repositories
But they cannot grant capabilities that the enterprise has blocked.
Repository and User Level
Individual repos and users can configure things within what the org allows. If the org blocks an API, you cannot re-enable it at the repo level — even if it is your personal repo and you are the sole owner while also being an org member.
What Exactly Is GitHub Advanced Security?
GHAS is GitHub’s umbrella term for a set of security features that require a paid add-on (on private repositories):
- Code Scanning — static analysis via CodeQL or third-party tools, results published to the Security tab
- Secret Scanning — detects committed credentials
- Dependency Review — vulnerability diff on pull requests
On public repositories, these features are free and unrestricted regardless of org membership. On private repositories, you need a GHAS licence seat.
This is the important nuance: Code Scanning for public repos works fine even without GHAS. The CodeQL Action has a separate API path for public repos that does not require the GHAS upload endpoint. Private repos always go through the GHAS-gated API, which is where enterprise restrictions take hold.
Why Does an Org Policy Affect My Personal Repo?
This is the part that catches developers off guard.
When your personal account joins a GitHub organisation, GitHub associates your account identity with that enterprise context. Enterprise policies that restrict certain API endpoints or capabilities are enforced at the token and account level, not just the organisation level.
The Code Scanning API endpoint — the one CodeQL uses to upload results — verifies:
- Whether the repository has GHAS enabled
- Whether the account making the request is under an enterprise that permits GHAS access
If the enterprise has disabled or not licensed GHAS, step 2 fails even on a repository that has nothing to do with the org.
---
config:
theme: dark
---
sequenceDiagram
participant W as Your Workflow
participant API as GitHub Code Scanning API
participant E as Enterprise Policy Check
W->>API: POST /repos/:owner/:repo/code-scanning/sarif
API->>E: Is GHAS enabled for this account/enterprise?
E-->>API: No - org policy restricts GHAS
API-->>W: 403 / "Code scanning is not enabled" ❌
This is not a bug. It is working exactly as designed. Enterprises pay per GHAS seat and GitHub enforces that boundary. The unexpected part is that “per seat” applies to the human account, not just the repositories inside the org.
Is This Normal?
Yes. This is documented behaviour, though not prominently.
The relevant GitHub documentation describes it under enterprise policies for code security. Specifically:
Enterprise owners can set a policy to prevent organisation members from enabling or disabling the code security and analysis features.
When that policy is in effect, the enforcement extends to any repository owned by a member — including personal repos owned by that member’s personal account. The account, not the repository, is the unit of enforcement at the enterprise boundary.
What Are Your Options?
1. Use a SAST tool that does not use the Code Scanning API
This is the pragmatic fix. Tools like Semgrep run as a plain GitHub Actions job and do not need the Code Scanning upload API to function. Results are surfaced as workflow logs and optional JSON artefacts uploaded to Actions. The workflow can fail the job on findings without touching any GHAS endpoint.
This is what I did for this blog:
- name: Run Semgrep
run: >
semgrep ci
--config p/javascript
--config p/github-actions
--config p/secrets
--json
--output semgrep-results.json
Full static analysis coverage. No enterprise restrictions. No GHAS licence needed.
2. Remove CodeQL from required branch protection checks
If you want to keep the CodeQL workflow (for when org membership changes, or for informational purposes), you can allow it to run without requiring it as a merge gate. The workflow still attempts analysis; it just will not block PRs when it fails to upload results.
3. Use a separate GitHub account for personal work
Some developers maintain two GitHub accounts — one for personal projects and one for workplace use. This completely separates the policy contexts. The tradeoff is managing dual SSH keys, dual identities in your git config, and juggling access across repos.
4. Ask your org admin
If the restriction was applied broadly as a default, an org admin may be able to grant exceptions. This depends entirely on your enterprise plan and your org’s configuration policies.
What You Should Know Before Joining an Organisation
If you use a single GitHub account for both personal projects and work:
- Check your organisation’s Actions permissions — some orgs restrict workflows to first-party Actions only or require approval for all third-party Actions
- Expect GHAS to be unavailable on your private repos if the org does not have GHAS seats
- Workflow permissions may default to read-only in your personal repos if the org enforces that globally
- OAuth apps authorised on your account may be subject to org-level approval
None of this means you should not join orgs with a personal account. It just means the boundary between “personal” and “work” in GitHub is not as clean as you might assume. Your account is the shared identity across both, and policies travel with the account.
The Takeaway
GitHub’s permission model is sensible when you understand the full hierarchy. Enterprise policies cascade downward and are not limited to organisation-owned repositories. If your personal account is a member of an enterprise-managed organisation, the enterprise’s capability restrictions apply to you as an account holder — not just to the repos inside the org.
For this blog, the fix was straightforward: swap CodeQL (which requires GHAS API access) for Semgrep (which does not). The static analysis coverage is equivalent. The workflow is simpler. And it works regardless of what any organisation’s enterprise policies say.
If you are setting up security tooling for a personal project and hitting unexplained permission errors, check your organisation memberships first. The answer may have nothing to do with your repository settings.
This is part of a series on repository and application security. The previous post covered Phase 1 supply chain security for this same repository.
