Forge 1.0 security and privacy review
Decision
Approved for the 0.9.0-rc checkpoint with documented dispositions.
The review found no critical or high-severity issue that is exploitable through
Forge’s supported generator, package, template, or automation paths. The one
medium defense-in-depth finding was resolved through
#140: repository secret
scanning and push protection were enabled before stable 1.0.0.
This decision covers source commit f0a7a166453b84249e86b719c0c08d0e45965742,
@rm-industries/content-model@1.0.0-rc.1, and
@rm-industries/create-forge@1.0.0-rc.2. Re-run the affected review sections
after a security-sensitive implementation or dependency change.
Scope and threat model
The review covered:
- command construction and child-process execution;
- destination resolution, path traversal, symbolic links, overwrites, rollback, and interruption;
- npm lifecycle behavior, dependency advisories, package contents, provenance, and release immutability;
- GitHub Actions triggers, permissions, credential persistence, secret handling, untrusted input, and action pinning;
- generated URLs, browser-side network behavior, analytics, cookies, telemetry, CMS access, and public metadata;
- source, generated output, and published packages for credentials, maintainer identifiers, and unresolved template tokens.
The primary attacker models are malicious CLI input, a hostile existing destination tree, compromised or unexpected dependency behavior, untrusted pull request content, forged automation input, and accidental credential or personal data publication. A user who explicitly approves overwriting files or installing npm dependencies remains responsible for those deliberate local actions.
Evidence
| Evidence | Result |
|---|---|
Repository npm run audit | Pass: 0 vulnerabilities across the workspace dependency graph |
Generated-template npm run audit | Pass under the reviewed allowlist; 13 transitive development findings, with no production dependency finding |
CodeQL (actions, javascript-typescript) | Pass; no open code-scanning alert |
| Dependency review | Pass on the release and public test-site pull requests |
| Workflow security | Zizmor and actionlint pass after #137 corrected the generated action annotation |
| Generator tests | Traversal, root destination, symlink collision, overwrite refusal, rollback, interruption, and fixed command execution are covered |
| Published package inspection | 15 files in content-model and 95 files in create-forge; expected contents only |
| Registry integrity | content-model sha512-AqsJ8X0WLjR2VXEpl45Thbfl54w4PzVAUqf2WXexpppOVEELl0VvgCVgkyIUbHpmlxb2ZbClNifY8qJoMbw4kw==; create-forge sha512-f7m2E5EF7Md17JoYyljVGHkyptg1Yvysoos3BhIZ23xXsoF7PFQj3bQkOLbwuc4AuH1Hn21cC555BoFlRCCq1A== |
| Package and identifier scan | No private-key marker, common service token pattern, or Rahul-specific identifier in either registry tarball |
| Registry-backed test site | All applicable CI, CodeQL, dependency-review, browser, accessibility, Lighthouse, and workflow-security checks pass on rm-industries.github.io#1 |
The package list and integrity values came from downloading the exact public npm versions with lifecycle scripts disabled. Both publications carry npm provenance and corresponding immutable GitHub release tags.
Manual review checklist
Generator and filesystem
- The generator invokes only fixed
npmandgitexecutables with argument arrays; it does not invoke a shell or interpolate user input into a command. - Destinations containing a parent segment are rejected, and a resolved filesystem root cannot be selected.
- Template paths are checked to remain beneath the destination.
- Symbolic-link or non-regular-file collisions are rejected before copying.
- A non-empty destination requires interactive confirmation. Non-interactive execution cannot silently opt into overwrite behavior.
- Unrelated existing files are preserved, replaced files are backed up, and failed or interrupted materialization rolls back invocation-owned changes.
- Generated TypeScript strings escape backslashes, quotes, and Unicode line separators; control characters are rejected from plain-text inputs.
- Package names and repository identifiers use constrained validators.
- Canonical site URLs require HTTP(S); generated site validation rejects URL credentials, query strings, and fragments.
Residual note: filesystem checks and writes are not atomic against a separate local process deliberately racing the destination tree. This is accepted as a low local threat because generation is a user-owned local operation, symlinks present during inspection are rejected, and no elevated privilege is used.
Dependencies and lifecycle scripts
- Neither published package defines
preinstall,install, orpostinstall. - Release packing and tarball inspection use
--ignore-scripts. - Template lockfile regeneration uses
--ignore-scripts. - A generated project’s requested
npm installuses npm’s normal lifecycle policy and visibly reports install-script approvals. This is an intentional package-manager boundary, not hidden generator execution. - Runtime package dependencies have no current npm audit finding.
- Dependabot and the weekly configured audit retain ongoing dependency visibility.
Workflows and secrets
- Workflows default to no permissions and grant job-specific permissions.
- Pull-request validation uses read-only contents access; deployment runs
only after a push to
mainand receives onlypages: writeplusid-token: write. - npm publication requires a package-scoped immutable tag, a protected environment, OIDC, a previously inspected artifact, matching registry integrity, and no persistent npm token.
- Checkout credentials are not persisted, and third-party actions are pinned to reviewed commits.
- Peer and template automation accept credentials only in trusted scheduled or manual jobs. GitHub App tokens request only contents and pull-request write access and are not exposed to pull-request code.
- Repository-dispatch data is validated against a numeric source run, a verified publication artifact, exact package identity, semantic version, tag, and registry integrity before it can produce a pull request.
- User-controlled workflow values are passed through environment variables and constrained before branch or package use rather than interpolated directly into workflow source.
- GitHub secret scanning and push protection are enabled. The initial repository-history scan reported no open secret alert. See #140.
Privacy and generated network behavior
- The generated public site contains no analytics, telemetry, tracking pixel, cookie, browser storage, beacon, remote font, or remote script service.
- Fonts and default visual assets are packaged locally.
- External links are configuration or documentation examples; Forge does not transmit visitor data to them.
- The
/admin/route is markednoindex, nofollow. Sveltia CMS is bundled as a package and accesses GitHub only after a maintainer opens the admin tool and authenticates for repository content operations. - Canonical, Open Graph, RSS, sitemap, and manifest URLs derive from the validated site configuration.
- The nonvisual Forge attribution is a static generator meta tag and causes no network request.
Findings and dispositions
| ID | Upstream or assessed severity | Finding | Disposition |
|---|---|---|---|
| SEC-01 | Medium | GitHub secret scanning and push protection were disabled. | Resolved through #140 on 2026-09-03. Both controls are enabled, and the initial repository-history query reported no open secret alert. |
| SEC-02 | Medium upstream, low in context | uuid@8.3.2 is present through development-only @lhci/cli. The advisory affects caller-provided buffers in UUID v3/v5/v6; LHCI calls uuid.v4() without a buffer for a temporary filename. | Tolerable development-tool risk. Keep visible in Dependabot and re-evaluate when LHCI updates its dependency. |
| SEC-03 | High upstream, low in context | Lighthouse’s Puppeteer browser-download path includes vulnerable extract-zip. Forge and generated CI install supported browsers explicitly and pass the installed browser to Lighthouse; no attacker-provided archive is extracted by the reviewed path. | Time-bounded allowlist through 2026-11-30. Remove or renew only after re-review. |
| SEC-04 | High and low upstream, low in context | LHCI’s legacy tmp dependencies have path and symlink advisories. The reviewed Lighthouse path uses tool-controlled temporary names; the optional interactive report-opening path is not run by Forge CI. | Time-bounded high-advisory allowlist through 2026-11-30; low advisory remains visible below the configured high failure threshold. |
The upstream severities describe potentially affected packages in other usage contexts. The assessed severity describes Forge’s reachable invocation paths. There is no open critical or high assessed finding.
Release conclusion
The 0.9.0-rc security and privacy gate passes. Secret protection is enabled;
re-review any unexpired dependency disposition that changes before the stable
checkpoint.