apache / apache/fineract-backoffice-ui
CI never proves the production build works offline, so a new build-time network fetch would pass review unnoticed
- Dominant language
- TypeScript
- Stars
- 15
- Forks
- 60
- Avg merge
- 10h 15m
- Merged PRs (30d)
- 108
Description
The `build` job runs with full network access:
```yaml
build:
name: Production Build
needs: dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@...
- uses: actions/setup-node@...
- run: npm ci
- run: npm run build
```
Nothing asserts that `npm run build` itself reaches the network. It always has it, so a build step that quietly fetches a remote resource passes CI and looks identical to one that does not.
That is not hypothetical: it is exactly how the Google Fonts dependency survived. Angular's font inlining downloaded `fonts.googleapis.com` during every production build and hard-failed without it — and CI was green throughout, because CI is never offline. It was found by an audit, not by a check. #362 / #361 fix that one instance; **nothing stops the next one.**
## Why it matters
ASF release policy expects a release to be buildable from its source package. A build that silently depends on a third-party host is not reproducible by a verifier working from the tarball, and the dependency is invisible until someone builds in a restricted environment — typically after a release, not before.
Ways a build-time fetch can reappear, none of which CI would currently notice:
- any webfont `` in `src/index.html`, via `optimization.fonts.inline` (the original case)
- a remote `@import` or `url()` in a stylesheet
- a `curl`/`fetch` in a build script under `scripts/`
- a dependency `postinstall` that downloads a binary
- a remote asset URL resolved at build time
## Proposal
Add a CI job that installs dependencies **with** network and then runs the production build **without** it.
The distinction matters and should be stated explicitly wherever this lands: `npm ci` legitimately needs the registry, and a mirror or cache satisfies it. The build must not need anything.
```yaml
offline-build:
name: Offline Production Build
needs: dependencies
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@...
- uses: actions/setup-node@...
- run: npm ci # network allowed: registry only
- name: Build with no network access
run: npm run build -- --configuration production
```
### Isolation mechanism — needs deciding, and verifying on the runner
Listed with the caveats rather than a recommendation, because the first one has a real question mark over it:
1. **`unshare -rn npm run build`** — what the audit used locally, and the smallest change. But `ubuntu-latest` is now Ubuntu 24.04, which restricts unprivileged user namespaces via `kernel.apparmor_restrict_unprivileged_userns`. **This needs testing on the runner before being adopted**; it may need `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0` first, which weakens the case for it being the "small" option.
2. **`docker run --network none`** with the workspace and `node_modules` mounted, e.g. against `node:24-alpine` to match `deploy/Dockerfile`'s builder stage. Most robust and unambiguous — no network namespace tricks, no sysctl. Costs an image pull and a mount.
3. **`sudo iptables` egress block** after `npm ci`. Runners have passwordless sudo. Blunt, and needs care to keep loopback up.
Option 2 is the least likely to break silently. Whatever is chosen, the job must fail loudly and surface the underlying error — the original failure was legible (`Inlining of fonts failed … over the internet`) and that legibility is the point.
### Optional: a matching `ga:check` gate
`scripts/ga-check.mjs` already has a precedent for this shape in the `headers` gate, which inspects `deploy/nginx.conf` statically rather than making HTTP requests. A gate asserting that `.github/workflows/ci.yml` contains an offline build job would keep the invariant visible in `npm run ga:check` alongside `external-fonts`, without making the script itself run a build.
Worth doing only if the CI job is added first; the gate is documentation of the guard, not the guard.
## Acceptance
- A CI job proves `npm run build -- --configuration production` succeeds with no network access, after `npm ci`.
- Reintroducing a build-time fetch fails that job. Verifiable by temporarily restoring a Google Fonts `` in `src/index.html` and confirming the job goes red — the same negative test used for the `external-fonts` gate in #361.
- `DOCS/CI_CHECKS.md` documents the job and, explicitly, why `npm ci` is exempt.
## Context
- #362 — the Google Fonts dependency this would have caught
- #361 — the fix, and the `external-fonts` gate that guards the *source* but not the *build*
- `audit/GOOGLE-FONTS-ASF-AUDIT.md` §6 — the online/offline build results, and why an unpinned build-time fetch is a reproducibility problem distinct from the offline failure itself
Contributor guide
Research direction
Start with .github/workflows/ci.yml and the existing dependencies job; verify an isolation mechanism on ubuntu-latest, using deploy/Dockerfile as the builder reference if relevant. Check scripts/ga-check.mjs and DOCS/CI_CHECKS.md for existing guard and documentation conventions. Done means the production build succeeds without network access after npm ci, fails when a remote fetch is reintroduced, and the exemption is documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, docker, github-actions, node.js, typescript
- Domain
- build-system, ci-cd, documentation
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100