https://github.com/coroboros/ci
Reusable GitHub Actions CI for the Coroboros stack.
https://github.com/coroboros/ci
ci-cd composite-actions coroboros github-actions gitleaks npm-package oidc pnpm reusable-workflows trusted-publishing
Last synced: 15 days ago
JSON representation
Reusable GitHub Actions CI for the Coroboros stack.
- Host: GitHub
- URL: https://github.com/coroboros/ci
- Owner: coroboros
- License: other
- Created: 2026-05-11T08:27:58.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-02T03:57:50.000Z (17 days ago)
- Last Synced: 2026-06-02T05:23:28.787Z (17 days ago)
- Topics: ci-cd, composite-actions, coroboros, github-actions, gitleaks, npm-package, oidc, pnpm, reusable-workflows, trusted-publishing
- Homepage: https://coroboros.com
- Size: 142 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
- Security: security/.gitleaks.toml
Awesome Lists containing this project
README

# coroboros/ci
**Reusable GitHub Actions CI for the Coroboros stack.**
Drop into any `@coroboros/*` repo via `uses: coroboros/ci/.github/workflows/.yml@v0`, or compose around the composite actions under `.github/actions/`.
[](https://github.com/coroboros/ci/releases)
[](https://github.com/coroboros/ci/actions/workflows/self.yml)
[](https://github.com/coroboros/ci)
[](LICENSE.md)
[](https://github.com/coroboros/ci)
[](https://github.com/coroboros/agent-skills)
[](https://coroboros.com)
**Imposed, not proposed.** Pipelines expose zero `inputs:` — same install flags, same publish auth, same security baseline across every Coroboros repo. Consumers wire it in.
- [Pipelines](#pipelines)
- [Composable actions](#composable-actions)
- [Development flow](#development-flow)
- [Environment](#environment)
- [Security](#security)
- [Examples](#examples)
- [License](#license)
---
## Pipelines
### `javascript-npm-packages.yml`
Bundled NPM CI.
Consumer requirements:
- `.node-version`
- `package.json` with
- `packageManager: "pnpm@X.Y.Z"`, `scripts.lint`, `scripts.test`.
- `scripts.build` is optional (auto-detected).
- `pnpm-lock.yaml` — required for `--frozen-lockfile`.
preflight
**Trigger**: `branch push`
**Sequence**:
1. Checkout
2. Run [`check-docs`](#composable-actions)
3. Run [`javascript/base`](#composable-actions)
publish
**Trigger**: `tag push`
**Sequence**:
1. Checkout `main` with full history
2. Verify `main` HEAD matches the tag SHA
3. Run [`check-docs`](#composable-actions)
4. Run [`javascript/base`](#composable-actions)
5. Pin `package.json` version to the tag
6. Generate `CHANGELOG.md` section via [`release/generate-changelog`](#composable-actions)
7. Publish to npm — OIDC + provenance or token-based via `.npmrc` (see [Security](#security))
8. Create GitHub Release via [`release/github-release`](#composable-actions)
9. Commit release artifacts back to `main` as `chore: release ${tag}`
10. Move rolling major tag `vN` to the release commit (skipped on pre-release tags)
security
**Trigger**: `every call`
Calls `security.yml` — see [Security](#security).
### `security.yml`
Reusable sub-workflow with three parallel scans:
- **`gitleaks`** — Installs `v8.30.1` (SHA-256 verified), scans git history with the [`security/.gitleaks.toml`](security/.gitleaks.toml) ruleset, fails on detected leaks. Emits SARIF as the `gitleaks-report` artifact (30-day retention).
- **`dependency-review`** — PR-only; needs repo's **Dependency graph** enabled. Fails on high-severity CVE introduced by the dep diff. Uses `actions/dependency-review-action@v4`.
- **`osv-scanner`** — Scans lockfiles recursively against [OSV.dev](https://osv.dev/) via `google/osv-scanner-action@v2`. Fails on any known vulnerability.
Imposed on every Coroboros workflow. Standalone wire-up — see [Examples](#examples).
---
**Notes** — pin via `@v0` (rolling major, auto-bumped on each release) or `@x.y.z` (immutable). Pipelines don't chain via `needs:`; the only sub-workflow call is `security` → `security.yml`.
---
## Composable actions
| Action | Type | Purpose |
| :--- | :--- | :--- |
| `check-docs` | transverse | Context dump + documentation check. |
| `javascript/base` | JavaScript | Sets up Node + corepack pnpm, caches the store, writes `.npmrc` from env, then installs, lints, builds (when present), tests. |
| `release/generate-changelog` | transverse | SemVer-strict tag guard + generates or reuses the `## vX.Y.Z` section in `CHANGELOG.md` from Conventional Commits. Outputs `body`. Idempotent. |
| `release/github-release` | transverse | Creates the GitHub Release for the current tag. Body typically chained from `release/generate-changelog` (see [Examples](#examples)). |
---
## Development flow
Develop with Conventional Commits → tag → push. No manual CHANGELOG, no version bump.
Tags follow **SemVer strict** — `1.2.3`, never `v1.2.3`.
Branch models
**main-only** — feature branch → PR → squash-merge to `main` → tag the merge commit → push.
**develop + main** — PR into `develop` → tag → `release/x.y.z` branch → merge to `main` → `main` reflects production.
Nobody pushes directly to protected branches (`main`, `develop`, `release/x.y.z`).
Conventional Commits → CHANGELOG
| Commit type | CHANGELOG subsection |
| :--- | :--- |
| `feat` | Features |
| `fix` | Fixes |
| `refactor` | Refactor |
| `perf` | Performance |
| `docs` | Documentation |
| `chore` / `ci` / `build` | Configuration |
| `test` | Tests |
| `style` | Style |
| Other / non-standard | Others |
| `!:` or `BREAKING CHANGE:` | Breaking Changes (always first) |
Section format: `## vX.Y.Z - DD/MM/YYYY`. Idempotent. Reuses an existing hand-curated section for the tag if present.
---
## Environment
Zero inputs on pipelines and on every composite — imposed, not proposed. Configuration flows through the caller's `secrets:` block. Every npm-publish-related value is a **secret** (encrypted at rest, masked in logs); none of them are GitHub `vars`.
Secrets (caller's secrets: block)
| name | required | description |
| :--- | :---: | :--- |
| `NPM_CONFIG_FILE` | ✔ | `.npmrc` content. Written to repo root by `javascript/base`. `${VAR}` references inside are expanded by npm at install time. |
| `NPM_EXTRA_CONFIG` | | Extra `.npmrc` lines appended after `NPM_CONFIG_FILE`. A **secret** — it lands in `.npmrc`, so it can carry auth material and must stay masked. |
| `NPM_PACKAGE_REGISTRY` | ✔ | npm package registry URL. |
| `NPM_PACKAGE_PROXY_REGISTRY` | | Optional npm proxy registry URL. |
| `NPM_PACKAGE_REGISTRY_TOKEN` | | npm Granular Access Token, scoped to the publishing organization with create-new-package permission. Required only for the token bootstrap (first publish of a new scoped package, before npm Trusted Publisher is bound). Absent → OIDC. |
javascript/base env contract (standalone composition)
| env | required | description |
| :-- | :---: | :--- |
| `NPM_CONFIG_FILE` | ✔ — fail if missing | `.npmrc` content |
| `NPM_EXTRA_CONFIG` | | Appended after `NPM_CONFIG_FILE` |
Set both at the caller's workflow- or job-level `env:`.
release/* composites I/O
**`release/generate-changelog`** — no inputs, no secrets. Reads `GITHUB_REF_NAME`. Requires `fetch-depth: 0` for `git describe`. Output:
| output | description |
| :--- | :--- |
| `body` | CHANGELOG section body — use as release notes. |
**`release/github-release`** — input:
| input | required | description |
| :--- | :---: | :--- |
| `body` | ✔ | Release notes body, typically `steps..outputs.body` from `release/generate-changelog`. |
Caller job needs `permissions: contents: write`. Uses `${{ github.token }}` internally via `GH_TOKEN`.
---
## Security
Supply chain — pnpm install flags
`pnpm install --frozen-lockfile --ignore-scripts` runs inside `javascript/base`.
- `--frozen-lockfile` — fails on stale or tampered `pnpm-lock.yaml`. Gate against transitive-dependency injection.
- `--ignore-scripts` — skips lifecycle scripts (`preinstall`, `install`, `postinstall`) of every dependency. Cuts the postinstall supply-chain vector.
pnpm CLI resolved via corepack from `packageManager`. No floating version reaches the runner.
Recommended NPM_CONFIG_FILE contents
Minimal hardened `.npmrc` for every Coroboros consumer. Stored as a **secret** (encrypted; carries `${VAR}` expansions resolved at install time):
```ini
@coroboros:registry=https:${NPM_PACKAGE_REGISTRY}
save-exact=true
fund=false
audit=false
ignore-scripts=true
package-lock=false
lockfile=true
prefer-online=true
```
| Line | Why |
| :--- | :--- |
| `@coroboros:registry=https:${NPM_PACKAGE_REGISTRY}` | Scope-resolved registry — `${NPM_PACKAGE_REGISTRY}` expands from the same-named secret. |
| `save-exact=true` | Pin exact versions on `add` / `install`. |
| `fund=false` | Suppress funding noise in CI logs. |
| `audit=false` | `osv-scanner` (in `security.yml`) covers vulnerability scans natively. |
| `ignore-scripts=true` | Belt-and-suspenders against postinstall supply-chain attacks — backs up the `--ignore-scripts` flag already passed by `javascript/base` on every `pnpm install`. |
| `package-lock=false` | Prevent `npm` from emitting a parasitic `package-lock.json` in pnpm repos. |
| `lockfile=true` | Explicit `pnpm-lock.yaml` enablement. Required on pnpm `< 11.0.0` consumers, where the preceding `package-lock=false` is interpreted as `lockfile=false` and collides with `pnpm install --frozen-lockfile`. Pnpm `>= 11` already defaults to `true` and ignores `package-lock` for `pnpm-lock.yaml`, so the line is harmless there. |
| `prefer-online=true` | Re-fetch dep metadata each install — local cache cannot mask a yanked or republished version. |
Publish — OIDC vs token bootstrap
Auto-detected by `NPM_PACKAGE_REGISTRY_TOKEN` **secret** presence on the consumer repo:
| Token secret | Mode | Command |
| :--- | :--- | :--- |
| absent | **OIDC + provenance** (default) | `pnpm publish --provenance --no-git-checks` |
| present | **Token bootstrap** | `npm publish --ignore-scripts --access public` |
**OIDC + provenance** — no long-lived token in the repo; npm trusts a per-run id-token issued by GitHub Actions for `coroboros//ci.yml`. Requires the npm Trusted Publisher form, which only accepts an existing package — so the very first publish has to take the token bootstrap below.
**Token bootstrap** — publishes the first version of a new scoped package. Set two additional **secrets** on the consumer (encrypted; forwarded via the caller's `secrets:` block):
| Secret | Contents |
| :--- | :--- |
| `NPM_PACKAGE_REGISTRY_TOKEN` | npm Granular Access Token scoped to the publishing organization with create-new-package permission. Long-lived; revoke after migrating to OIDC. |
| `NPM_EXTRA_CONFIG` | `${NPM_PACKAGE_REGISTRY}:_authToken=${NPM_PACKAGE_REGISTRY_TOKEN}` — appended to `.npmrc` by `javascript/base`. Stored as a **secret** because it carries auth expansion. |
`npm publish` is used on the bootstrap path (not `pnpm publish`) because pnpm `>= 11.1.3` in CI auto-attempts the OIDC token exchange and does not fall back to the `.npmrc` token if OIDC fails. `--ignore-scripts --access public` skips publish-time lifecycle hooks (`prepublishOnly` excepted — known `npm` behavior). The published tarball is identical to `pnpm publish`'s.
After the first publish, configure the npm Trusted Publisher form (Publisher type: GitHub Actions; Organization: the publishing org; Repository: consumer repo; Workflow filename: `ci.yml`; Environment: empty), then open a `chore(ci):` PR dropping `NPM_PACKAGE_REGISTRY_TOKEN` + `NPM_EXTRA_CONFIG` from the caller's `secrets:` block. Revoke the npm token. `1.0.1+` publishes via OIDC + provenance.
Secret isolation
Each `workflow_call.secrets:` block declares ONLY the secrets the job consumes. No `secrets: inherit` anywhere.
Action pinning + Dependabot
Third-party actions across workflows + composites are pinned to a commit SHA with an inline `# vX` comment. Floating refs (`@master`, `@main`, `@vX`) are banned.
Self-CI binaries pinned by version. `actionlint` and `gitleaks` install from release tarballs with SHA-256 verification; `yamllint` via `pip install` with version pin. No `curl | bash`.
`.github/dependabot.yml` opens weekly grouped auto-PRs to bump pinned SHAs across `.github/workflows/*` and `.github/actions/**/action.yml`. Consumers should add their own ecosystem entries (e.g., `npm`).
Canonical gitleaks config
Canonical ruleset at `security/.gitleaks.toml` in this repo. Stack-specific rules cover Resend, Neon Postgres, PostHog, and GitHub fine-grained PATs on top of the gitleaks defaults.
`security.yml` sparse-checks the file out of `coroboros/ci` at runtime — imposed, no consumer override.
---
## Examples
javascript-npm-packages.yml wire-up
```yaml
# consumer-repo/.github/workflows/ci.yml
name: CI
on:
push:
branches: [develop, main]
tags: ['*']
pull_request:
workflow_dispatch:
jobs:
ci:
uses: coroboros/ci/.github/workflows/javascript-npm-packages.yml@v0
permissions:
contents: write # GitHub Release on tag
id-token: write # npm OIDC publish on tag
secrets:
NPM_CONFIG_FILE: ${{ secrets.NPM_CONFIG_FILE }}
NPM_PACKAGE_REGISTRY: ${{ secrets.NPM_PACKAGE_REGISTRY }}
NPM_PACKAGE_PROXY_REGISTRY: ${{ secrets.NPM_PACKAGE_PROXY_REGISTRY }}
# Token bootstrap (drop both after npm Trusted Publisher is wired — see Security):
NPM_EXTRA_CONFIG: ${{ secrets.NPM_EXTRA_CONFIG }}
NPM_PACKAGE_REGISTRY_TOKEN: ${{ secrets.NPM_PACKAGE_REGISTRY_TOKEN }}
```
security.yml standalone (non-npm repo)
```yaml
# consumer-repo/.github/workflows/security.yml
name: Security
on:
push:
branches: [develop, main]
pull_request:
schedule:
- cron: '0 0 * * 0' # weekly — catches CVEs published after last push
permissions:
contents: read
jobs:
scan:
uses: coroboros/ci/.github/workflows/security.yml@v0
```
Compose with javascript/base
```yaml
jobs:
custom:
runs-on: ubuntu-latest
permissions:
contents: read
env:
NPM_CONFIG_FILE: ${{ secrets.NPM_CONFIG_FILE }}
NPM_EXTRA_CONFIG: ${{ secrets.NPM_EXTRA_CONFIG }}
steps:
- uses: actions/checkout@v4
- uses: coroboros/ci/.github/actions/check-docs@v0
- uses: coroboros/ci/.github/actions/javascript/base@v0
- run: pnpm run my-custom-script
shell: bash
```
Compose a custom release pipeline (non-npm artifact)
```yaml
jobs:
publish:
if: ${{ github.ref_type == 'tag' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- id: changelog
uses: coroboros/ci/.github/actions/release/generate-changelog@v0
# ...your publish step (docker push, gh release upload, etc.)...
- uses: coroboros/ci/.github/actions/release/github-release@v0
with:
body: ${{ steps.changelog.outputs.body }}
```
---
## License
All Rights Reserved. See [LICENSE.md](LICENSE.md).