fix(ci): least-privilege workflow permissions — clears 12 CodeQL Actions-hardening alerts - #1280
Merged
Merged
Conversation
…low jobs
Clears all 12 open CodeQL alerts on `development`. Every one is
`actions/missing-workflow-permissions`, severity MEDIUM, and every one is in
`.github/workflows/`. No production code is involved and nothing here is a
vulnerability that was exploitable — an absent `permissions:` block means the
job runs with the repository default rather than a stated grant.
Ten of the twelve jobs only CALL a reusable workflow in
ConductionNL/.github. For those the block is not a new grant: the callee's own
job already declares the same permissions, so the effective token is unchanged.
release.yml (3 jobs) / release-beta / release-development / release-stable
contents: write — callee `release` already declares exactly this
sync-to-beta contents: write + pull-requests: write — callee `create-pr`
issue-triage issues: write + contents: read — all 3 callee jobs
openspec-sync issues: write + contents: read — callee `sync`
documentation contents: write + packages: write — UNION of the callee's
build / deploy / image jobs
The two local jobs (pull-request-lint-check, spec-validation) are
checkout + `npm ci` + a lint/validate script, so `contents: read`. `packages:
read` is deliberately omitted: .npmrc points at the public registry and
package-lock.json has zero npm.pkg.github.com entries.
A caller block is a CEILING, not a grant: GitHub validates the callee's declared
job permissions against it, including for jobs an `if:` will skip. Tightening
any of these to `read` would make the call fail to START with zero jobs rather
than run with less — an outage quieter than the red it replaces.
Verified: all 14 workflows parse, and a job-level sweep of the tree reports 0
jobs without a block, against 12 before the change — the same 12 CodeQL names.
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
August 17, 2026 04:47
…image defaults to true The comment claimed `packages: write` was needed only because GitHub statically validates a callee's declared job permissions, and that the `image` job "never runs" here. That is wrong on the second half. `build-image` in ConductionNL/.github/.github/workflows/documentation.yml is `type: boolean, default: true`, and none of the callers pass it. So the `image` job DOES run on a push to `documentation`, and it really does `docker buildx` push to GHCR. `packages: write` is load-bearing at RUNTIME, not merely statically — dropping it would 403 that push. Comment only; the permissions block itself is unchanged and was already correct.
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 154/154 | |||
| npm | ✅ | ✅ 534/534 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-17 04:57 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 154/154 | |||
| npm | ✅ | ✅ 534/534 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-17 05:19 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 145/145 | |||
| npm | ✅ | ✅ 534/534 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-19 14:53 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is, accurately
The
CodeQLcheck-run ondevelopmentis red with the title"8 new alerts including 1 high severity security vulnerability". That title
overstates what is actually there. Measured distribution of the 12 open alerts
on
refs/heads/development:.github/workflows/*.ymlactions/missing-workflow-permissionstests/vitest/js/bad-tag-filterlib/orsrc/This is Actions-hardening debt. It is not a security incident, and no
production code is involved. The single "high severity security
vulnerability" in the title is a regex in a vitest helper (see below); it is not
reachable by any user and is not a sanitiser.
An absent
permissions:block does not mean a job was compromised — it meansthe job ran with the repository default grant instead of a stated one. This PR
states them.
The 12 workflow alerts
Ten of the twelve jobs only call a reusable workflow in
ConductionNL/.github.For those, the block is not a new grant: the callee's own job already
declares the same set, so the effective token is byte-for-byte what it is today.
release.ymlcontents: writereleasealready declares exactly thisrelease-beta/release-development/release-stablecontents: writeNEXTCLOUD_APPSTORE_TOKEN, notGITHUB_TOKENsync-to-betacontents: write,pull-requests: writegh pr createfor the standing dev to beta PRissue-triageissues: write,contents: readopenspec-syncissues: write,contents: readsyncdocumentationcontents: write,packages: writepull-request-lint-checkcontents: readnpm ci+npm run lintspec-validationcontents: readnpm ci+npm run check:specsThe trap this PR deliberately avoids
A caller block is a CEILING, not a grant. GitHub validates the called
workflow's declared job permissions against it — including for jobs an
if:will skip. Capping any of these at
contents: readdoes not make the job runwith less; it makes the call fail to start, with zero jobs and no
annotations — an outage quieter than the red it replaces.
packages: writeondocumentationis load-bearing at runtime as well: thecallee's
build-imageinput isdefault: trueand no caller overrides it, sothe
imagejob really does run on a push todocumentationand really does pushto GHCR. (An earlier revision of this PR's comments claimed that job "never
runs" — that was wrong and is corrected in the second commit.)
packages: readis deliberately omitted from the two local jobs:.npmrcpoints at the public registry and
package-lock.jsoncontains zeronpm.pkg.github.comentries, sonpm cinever authenticates to GitHub Packages.The 1 test alert — not fixed here, dismissed with a reason
js/bad-tag-filter(high) attests/vitest/editorModalSlotContract.spec.js:54:CodeQL's finding is "this regular expression does not match upper case
<SCRIPT>tags" — a real observation about a sanitiser. This is not asanitiser. It is a parser that reads a first-party
.vuefile off disk withfs.readFileSyncto extract the component's own<script>block for a contractassertion. There is no untrusted input and no HTML being filtered for safety;
Vue SFCs require a lowercase
<script>block to compile at all.Dismissed as
used in testswith the reasoning recorded on the alert. The ruleis left enabled — no blanket suppression.
Verification
yaml.safe_load).permissions:block atworkflow or job level.
developmenttree reports 12, and they are the same 12 job names CodeQL flagged. The
check can say NO.