Repository navigation
Enterprise security enforcement: one configuration and rulesets for every repo #94
Description
Activity
Step 1 done: enterprise security configuration, enforced
Configuration:
Bundu enterprise security baseline(enterprise config id 281854, enforcementenforced). ViewFeature Setting Dependency graph enabled Dependabot alerts enabled Dependabot security updates enabled (new: config 17 had it not_set)CodeQL default setup enabled, standard runners, advanced setup allowed where a repo already runs CodeQL itself Secret scanning, push protection, validity checks, non-provider patterns, extended metadata enabled Private vulnerability reporting enabled (GitHub offers it on public repos only) Code Security + Secret Protection (GHAS) enabled - Attached to all 89 repos in the 7 orgs (scope
all). All 89 reportenforced, including bundu-labs/marketing, which had failed under the old "GitHub recommended" config. - Set as the enterprise default for new repos (
all), replacing "GitHub recommended" (id 17). Config 17 was not deleted. - Nothing was switched off. I snapshotted
security_and_analysison all 89 repos before and after attaching, and no enabled feature changed. Dependabot security updates went from 50 repos to 86. The other 3 are archived and read-only: mukoko-dev/mukoko-auth, nyuchi/iconic-expeditions-landing and nyuchi/r2-brand-explorer. - Verified on nyuchi/toddle-enhancement-extension: CodeQL default setup covers
actions, javascript-typescript; Dependabot alerts and security updates are on; secret scanning, push protection and validity checks are on.
Licensing and cost
GHAS on private repos is metered and already active. Before this change: Code Security had 1 active committer across 33 repos and Secret Protection 1 across 32. The old config already enabled GHAS on every private repo, so attaching the new one added no billable repos or committers. At list price that is US$30 (Code Security) plus US$19 (Secret Protection) per active committer per month, so about US$49 a month today. Each extra person who commits to a private repo adds about US$49 a month. Nothing was bought.
Open: CodeQL language gaps (owner)
Now that the configuration is enforced, a repo's default-setup languages can only be changed in the UI. Through the API the language list rejects
rust, so Rust can't be added that way at all. My attempt to lift enforcement briefly, re-run detection and re-enforce was denied by the permission system, so I left it there. These repos run CodeQL on fewer languages than they contain:- nyuchi/nyuchi-platform: only
actions; also has TypeScript, Rust and Astro. - nyuchi/nyuchi-docs: only
actions; also has TypeScript and Astro. - nyuchi/toddle-enhancement-extension:
pythonmissing (a small amount of Python). - mzizi-dev/mzizi-registry:
java-kotlinandswiftmissing (Kotlin and Swift present).
Fix, per repo: Settings → Code security → CodeQL → Edit, then tick the languages.
Rust is covered by default setup elsewhere: bundu-standards, kweli, mukoko-circles, mukoko-weather, nyuchi-identity, super-app-web, mzizi, mzizi-console, mzizi-registry, workspace-tools, openNTL/ntl, siafudb and siafudb-kuzu.
Not covered:
.astroand.sveltefiles themselves. CodeQL's JavaScript extractor doesn't read them, only the.ts/.jsthey import. The Semgrep workflow (step 2c) extracts Astro frontmatter and<script>blocks to cover this. PHP has no CodeQL support (nyuchi/auto-seo-manager, seo-manager, nyuchi-travel-addons).- Attached to all 89 repos in the 7 orgs (scope
- added a commit that references this issue
on Oct 7, 2026 Step 5: DAST and chaos, the design (nothing built yet)
1.
nyuchi/.githubreusable-zap-baseline.yml- Triggers:
workflow_call(from a repo's deploy workflow, after its beta or staging deploy succeeds), plus a weeklyschedulein each repo. - Inputs:
target-url(required, https only);rules-file(optional, default.zap/rules.tsvin the caller repo, so known false positives can be set to IGNORE or WARN);fail-on:high(default) ormedium;ajax-spider(bool, default false; true for SPA hosts);max-minutes(default 10).
- Job:
- Run
zaproxy/action-baseline, pinned by SHA, with theghcr.io/zaproxy/zaproxy:stableimage pinned by digest,cmd_options: -a -j -m <max-minutes> -I,allow_issue_writing: falseandfail_action: false. - Then a small step parses
report_json.jsonand fails on any alert at or abovefail-onthat the rules file doesn't IGNORE. - Reports (
report_html.html,report_md.md,report_json.json) are uploaded as an artifact (retention 14 days), and the markdown goes to the job summary. - No SARIF upload at first, because ZAP's alerts don't map cleanly to code locations. Revisit when the evaluate numbers are in.
- Run
- Baseline only, never the active scan. A baseline scan spiders the site and checks it passively, with no attack traffic. That matters because the betas share production D1, KV, Mongo and the Nyuchi API (beta-shares-prod-bindings), so an active
zap-full-scanagainst a beta would write junk into production data. An active scan only runs against a host with its own isolated data, and today there is none. That decision is the owner's. - Auth: none at first; public surfaces only. An authenticated scan later needs a test account per app (WorkOS), with the session held in an org secret and passed through ZAP's replacer. That's an owner step.
- Pinned and token-free: no ZAP cloud, and
GITHUB_TOKENgetscontents: readonly.
2. Where it runs: repos with a deployed non-production host
Verified today from GitHub deployment statuses, with a curl of each stable host.
Stable beta or staging hosts (scan these first):
Repo Host mzizi-dev/mzizi-docs https://docs.mzizi.dev (staging env, 200) nyuchi/mintlify-docs https://docs.nyuchi.com (staging env, 200) shamwari-ai/docs https://docs.shamwari.ai (staging env, 200) siafuDB/siafudb, siafuDB/docs https://siafudb.org (staging env, 308) openNTL/ntl https://openntl.org (staging-docs env, 308) bundu-labs/zimbabwe-information https://www.travel-info.co.zw (staging env, 308) mukoko-dev/kweli betaenv, deploys to a Vercel URL per buildnyuchi/nyuchi-platform staging – nyuchi-console-rsenv, Vercel URL per buildVercel previews only (scan the preview URL from the
deployment_statusevent):- bundu-labs/marketing: 6 projects
- mukoko-dev: mukoko, mukoko-news, mukoko-lingo (+ console), nyuchi-identity, mukoko-events, mukoko-home, bushtrade, mukoko-weather, mukoko-events-admin
- mzizi-dev/mzizi-registry
- nyuchi: learning, Nyuchi-rentals, zti-app, agentgateway, nyuchi-platform (nyuchi-console)
- shamwari-ai/shamwari: platform and web
For these, a caller workflow runs
on: deployment_status(statesuccess, environment not Production) and passesgithub.event.deployment_status.environment_url, after checking that it matches^https://[a-z0-9-]+-nyuchi(tech)?\.vercel\.app$. Vercel Deployment Protection, if it's on, needs a bypass token as an org secret (owner step).Not yet deployed:
- beta.mukoko.com and app.mukoko.com (super-app-web) don't resolve today.
- beta.events/news/weather.mukoko.com don't resolve either.
- Add them when they go live.
- APIs (api.nyuchi.com, api.mzizi.dev) need ZAP's
zap-api-scanagainst their OpenAPI documents. That's a second workflow,reusable-zap-api.yml, in passive mode for the same production-data reason.
Rollout:
- The pilot is nyuchi/toddle-enhancement-extension#64, handled by its own agent. It has no host, so it pilots the chaos part and the pre-commit and editor standard.
- Then the four docs hosts, which are static and lowest risk.
- Then the Vercel previews, one org at a time.
- Each runs in report-only mode for two weeks (
fail-onset abovehighvia areport-only: trueinput), then fails on high.
3. Chaos in e2e: Mzizi
chaoshttps://api.mzizi.dev/v1/ui/chaos(install withnpx shadcn@latest add https://api.mzizi.dev/v1/ui/chaos; depends onobservability) provides:withChaos(fn, { enabled, errorRate, latencyMs }), which injects random errors and latency;- a middleware form with fixed configuration;
ChaosError.
It is disabled by default.
- Where: in e2e (Playwright) suites only. Wrap the app's outbound calls (Nyuchi API client, provider fetches) with
withChaos, gated onprocess.env.MZIZI_CHAOS === "1", which only the e2e job sets on a preview or local build. It's never enabled in production, and never on a beta, because betas share production bindings. - Suite: a
chaosPlaywright project re-runs the critical journeys (sign-in, the main read path, one write path) aterrorRate: 0.3andlatencyMs: [200, 1500], and asserts on behaviour, not success:- error boundaries render;
- retries and fallbacks fire;
- nothing hangs past the timeout;
- no unhandled rejection appears in the console;
- a seeded run is reproducible: the error decision should come from an injectable RNG, which the registry component lacks today. Per the Mzizi-upstream rule, that goes upstream to mzizi-registry as
chaoswith aseedoption and a contract test, not a local fork.
- Reusable:
nyuchi/.githubreusable-e2e-chaos.ymlinstalls, builds, starts the preview or local server and runsplaywright test --project chaos. Runs are nightly and on PRs labelledchaos. It's advisory until each app's chaos journeys are stable. - Rust services: the same idea as a
towerlayer behind a cargo featurechaos, never in release builds. That needs amzizi-rootscrate counterpart, which also goes upstream.
Owner decisions for step 5
- Baseline-only against betas that share production data. Recommended: yes. Isolated data is needed before any active scan.
- Test accounts and secrets for authenticated scans, and a Vercel protection bypass, if previews are protected.
- Whether to give super-app-web a real beta host (beta.mukoko.com doesn't resolve).
- Triggers:
- added a commit that references this issue
on Oct 7, 2026 Step 2 done: three new enterprise rulesets, all in evaluate mode
They use the same conditions as
enterprise-release-version-check: every org, every repo exceptsandbox-*andarchive-*, refs~DEFAULT_BRANCHandrefs/heads/staging. Enterprise owners can bypass. None of them is active.Ruleset id Rule enterprise-code-scanning24653328 code_scanning: CodeQL, alerts thresholderrors, security alertshigh_or_higherenterprise-dependency-review24655436 required workflow nyuchi/.github.github/workflows/dependency-review.yml@refs/heads/mainenterprise-semgrep24655437 required workflow nyuchi/.github.github/workflows/reusable-semgrep.yml@refs/heads/mainWorkflows
#95 added the workflows; it was merged to
stagingunder the merge gate (4 review rounds, the last one clean, and runtime-tested in #96). #97 released it tomain.dependency-review.yml, two jobs:review: dependency-review-action v5.0.0, pinned; fails on new high or critical advisories. The dependency graph covers npm, pnpm, Yarn, Cargo, pip, Poetry and uv.audit: runs only on the lockfiles a change touches.npm auditandpnpm auditfail on high and above.cargo auditfails on any RustSec advisory.pip-auditruns onuv.lockandrequirements*.txt.- Each auditor runs on copies of the files in an empty directory, so a PR's
.npmrc,.pnpmfile.cjs,pnpm.auditConfigor.cargo/audit.tomlcan't silence it or run code.
- It passes when nothing changed. Every action is pinned to a commit.
reusable-semgrep.yml: the open-source Semgrep CLI 1.179.0, image pinned by digest. No token, metrics off,--error.- Diff-aware on pull requests and merge queue groups:
--baseline-commitagainst the base, so only new findings count. - Packs:
p/typescript p/javascript p/react p/python p/rust p/secrets p/owasp-top-ten, plus a repo's own.semgrep.yml. - Astro: the frontmatter and
<script>blocks of changed.astrofiles are extracted to.tsfiles (line numbers kept), and both base and head versions are scanned with the same baseline. test: planted findings for the security workflows (do not merge) #96 caught a hard-coded JWT secret in Astro frontmatter this way.
- Diff-aware on pull requests and merge queue groups:
Coverage by stack
Stack CodeQL (config 281854) Semgrep Dependency audit TypeScript / JavaScript / React javascript-typescriptp/typescript, p/javascript, p/react review + npm / pnpm audit Astro only the .tsit imports;.astrofiles aren't extracted by CodeQLfrontmatter and scripts extracted as TS Rust rust(default setup, where detected)p/rust review + cargo audit Python pythonp/python review + pip-audit (uv.lock, requirements) Mzizi repos as above, per language as above as above Svelte the imported TS only not extracted (gap) as TS Yarn / Bun / Poetry lockfiles – – review only (no second auditor) PHP (3 nyuchi repos) no CodeQL support no PHP pack enabled review only Next: step 3. The numbers will be measured from rule-suite insights once real PRs and merges have run against these rulesets. Nothing will be activated.
Owner, 2026-10-07: "We need security to be properly enforced across the entire enterprise." This follows the shift-left audit: an issue was opened in all 83 repos, "Shift-left security: what this repo runs today, and the gaps".
Baseline (2026-10-07, 83 repos in the 7 enterprise orgs; nyuchiGOV excluded)
actions)The only org-required check is
org-lint.yml(lint plus Vite+), which contains no security check.Target: enforced from the enterprise, not copied per repo
staging:code_scanningrule (CodeQL: block on high/critical alerts and on high-severity security issues);nyuchi/.github.Start each in evaluate mode, then switch to active once green.
.vscode/extensions.json(Semgrep, SonarQube for IDE, and the repo's linters) through the same path.chaos(fault injection) in e2e. These apply per deployed service, from reusable workflows.The Toddle extension is the pilot for steps 3–5 (nyuchi/toddle-enhancement-extension#64).
Status