From 7dd8e6b8458ea59926ca62a2b3955ef645b97104 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 29 Oct 2026 11:48:44 +0100 Subject: [PATCH 1/4] feat: improve Certificates examples --- .../docs/reference/policies/Certificates.mdx | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/content/docs/reference/policies/Certificates.mdx b/src/content/docs/reference/policies/Certificates.mdx index 1382025..cacb036 100644 --- a/src/content/docs/reference/policies/Certificates.mdx +++ b/src/content/docs/reference/policies/Certificates.mdx @@ -43,6 +43,39 @@ Install and manage certificates in Firefox. +### Trusting root certificates from the operating system + +On Windows and macOS, deploy your organization's root certificate to the operating system certificate store, for example with group policy or an MDM profile, and have Firefox trust it from there. +Firefox reads these certificates from the operating system each time it starts, so when you replace or remove a certificate in the operating system store, Firefox follows. +Certificates added with `Install` stay in each user's profile even after you remove them from the policy. +This example also locks the setting, so users can't turn it off: + +```json +{ + "policies": { + "Certificates": { + "ImportEnterpriseRoots": true + } + } +} +``` + +### Installing a root certificate on Linux + +This example uses a full path to a directory that only administrators can write to. +If you give only a filename, Firefox checks each user's `~/.mozilla/certificates` directory before the system directory, so a user's file with the same name would be installed instead. +Firefox installs the certificate into each user's profile, and doesn't remove it if you later take it out of the policy: + +```json +{ + "policies": { + "Certificates": { + "Install": ["/usr/lib/mozilla/certificates/example-root-ca.pem"] + } + } +} +``` + ## Windows (GPO) ``` From dc9584f681396e54fa4c2c4d2e5ae325837a8253 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 29 Oct 2026 11:56:01 +0100 Subject: [PATCH 2/4] feat: improve Certificates examples --- src/content/docs/reference/policies/Certificates.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/content/docs/reference/policies/Certificates.mdx b/src/content/docs/reference/policies/Certificates.mdx index cacb036..89b6f14 100644 --- a/src/content/docs/reference/policies/Certificates.mdx +++ b/src/content/docs/reference/policies/Certificates.mdx @@ -33,7 +33,7 @@ Install and manage certificates in Firefox. We do not support using `%USERPROFILE%` or other environment variables on Windows. If you are specifying the path in the policies.json file on Windows, you need to escape your backslashes (`\\`) which means that for UNC paths, you need to escape both (`\\\\`). - If you use group policy, you only need one backslash. + If you use Group Policy, you only need one backslash. Certificates are installed using the trust string `CT,CT,`. @@ -45,8 +45,10 @@ Install and manage certificates in Firefox. ### Trusting root certificates from the operating system -On Windows and macOS, deploy your organization's root certificate to the operating system certificate store, for example with group policy or an MDM profile, and have Firefox trust it from there. -Firefox reads these certificates from the operating system each time it starts, so when you replace or remove a certificate in the operating system store, Firefox follows. +On Windows and macOS, deploy your organization's root certificate to the operating system certificate store, for example with Group Policy or an MDM profile, and have Firefox trust it from there. +Firefox loads these certificates into memory from the operating system each time it starts, rather than adding them to the profile. +When you replace or remove a certificate in the operating system store, the change takes effect the next time Firefox starts. + Certificates added with `Install` stay in each user's profile even after you remove them from the policy. This example also locks the setting, so users can't turn it off: @@ -63,7 +65,7 @@ This example also locks the setting, so users can't turn it off: ### Installing a root certificate on Linux This example uses a full path to a directory that only administrators can write to. -If you give only a filename, Firefox checks each user's `~/.mozilla/certificates` directory before the system directory, so a user's file with the same name would be installed instead. +If you give only a filename, Firefox checks each user's `~/.mozilla/certificates` directory before the system certificate directory, so a user's file with the same name would be installed instead. Firefox installs the certificate into each user's profile, and doesn't remove it if you later take it out of the policy: ```json From be4720a1fcad0706f70e5a5c6029e582b8e9e68e Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 4 Nov 2026 12:44:58 +0100 Subject: [PATCH 3/4] feat: add a basic coverage script --- .github/workflows/schema-sync.yml | 10 +++++++ package.json | 1 + scripts/check-schema-coverage.mjs | 45 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 scripts/check-schema-coverage.mjs diff --git a/.github/workflows/schema-sync.yml b/.github/workflows/schema-sync.yml index e0af002..612f992 100644 --- a/.github/workflows/schema-sync.yml +++ b/.github/workflows/schema-sync.yml @@ -76,3 +76,13 @@ jobs: --label schema-sync \ --label "automated pr" fi + + # Keep a coverage comment on the PR, updated on each sync + npm run --silent schema:coverage > coverage.md + if [ ! -s coverage.md ]; then + printf '## Docs coverage\n\nEvery policy in the schema has a reference.\n' > coverage.md + fi + gh pr comment automation/schema-sync \ + --body-file coverage.md \ + --edit-last \ + --create-if-none diff --git a/package.json b/package.json index d747f4f..582168a 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "astro preview", "astro": "astro", "schema:sync": "node scripts/sync-schema.mjs", + "schema:coverage": "node scripts/check-schema-coverage.mjs", "release-dates:sync": "node scripts/sync-release-dates.mjs" }, "dependencies": { diff --git a/scripts/check-schema-coverage.mjs b/scripts/check-schema-coverage.mjs new file mode 100644 index 0000000..1162cb9 --- /dev/null +++ b/scripts/check-schema-coverage.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +// Compare policies in schema/policies-schema.json against policy docs pages +// and print as markdown. Used by .github/workflows/schema-sync.yml +import { readFile, readdir } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve, join, parse } from "node:path"; + +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const SCHEMA_PATH = join(REPO_ROOT, "schema", "policies-schema.json"); +const DOCS_DIR = join(REPO_ROOT, "src", "content", "docs", "reference", "policies"); +const DOCS_PATH = "src/content/docs/reference/policies"; + +// Docs pages that intentionally have no schema entry. +const NOT_IN_SCHEMA = new Set([ + // macOS plist-only switch, read before policies.json is parsed. + "EnterprisePoliciesEnabled", +]); + +const schema = JSON.parse(await readFile(SCHEMA_PATH, "utf8")); +const schemaPolicies = new Set(Object.keys(schema.properties ?? {})); + +const docPages = new Set( + (await readdir(DOCS_DIR)).filter((f) => /\.mdx?$/.test(f)).map((f) => parse(f).name), +); + +const missingDocs = [...schemaPolicies].filter((p) => !docPages.has(p)).sort(); +const missingSchema = [...docPages] + .filter((p) => !schemaPolicies.has(p) && !NOT_IN_SCHEMA.has(p)) + .sort(); + +// Print nothing when docs and schema match +if (missingDocs.length === 0 && missingSchema.length === 0) process.exit(0); + +const lines = ["## Docs coverage", ""]; + +if (missingDocs.length > 0) { + lines.push(`**In the schema, no docs page** (add one under \`${DOCS_PATH}/\`):`, ""); + lines.push(...missingDocs.map((p) => `- \`${p}\``), ""); +} +if (missingSchema.length > 0) { + lines.push("**Docs page, not in the schema** (removed or renamed upstream?):", ""); + lines.push(...missingSchema.map((p) => `- \`${p}\``), ""); +} + +console.log(lines.join("\n").trimEnd()); From 6e625cbabd9ab9d1d20a96a1370b2a38d98f947d Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 4 Nov 2026 12:53:35 +0100 Subject: [PATCH 4/4] feat: add a basic coverage script, better wording --- .github/workflows/schema-sync.yml | 2 +- scripts/check-schema-coverage.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/schema-sync.yml b/.github/workflows/schema-sync.yml index 612f992..70497e5 100644 --- a/.github/workflows/schema-sync.yml +++ b/.github/workflows/schema-sync.yml @@ -80,7 +80,7 @@ jobs: # Keep a coverage comment on the PR, updated on each sync npm run --silent schema:coverage > coverage.md if [ ! -s coverage.md ]; then - printf '## Docs coverage\n\nEvery policy in the schema has a reference.\n' > coverage.md + printf '## Docs coverage\n\nPolicy docs and schema entries match.\n' > coverage.md fi gh pr comment automation/schema-sync \ --body-file coverage.md \ diff --git a/scripts/check-schema-coverage.mjs b/scripts/check-schema-coverage.mjs index 1162cb9..d85d7c6 100644 --- a/scripts/check-schema-coverage.mjs +++ b/scripts/check-schema-coverage.mjs @@ -34,11 +34,11 @@ if (missingDocs.length === 0 && missingSchema.length === 0) process.exit(0); const lines = ["## Docs coverage", ""]; if (missingDocs.length > 0) { - lines.push(`**In the schema, no docs page** (add one under \`${DOCS_PATH}/\`):`, ""); + lines.push(`**Policies without docs** (add a page under \`${DOCS_PATH}/\`):`, ""); lines.push(...missingDocs.map((p) => `- \`${p}\``), ""); } if (missingSchema.length > 0) { - lines.push("**Docs page, not in the schema** (removed or renamed upstream?):", ""); + lines.push("**Docs without a schema entry** (removed or renamed upstream?):", ""); lines.push(...missingSchema.map((p) => `- \`${p}\``), ""); }